listings_limit_exceeded

The workspace has more active listings than its plan allows, or an activation would put it over. Deactivate listings or upgrade.

HTTP 402The workspace is over a plan cap. Trim usage or upgrade.

When it fires

The workspace's active-listing count is above the cap for its plan. Repull enforces a hard per-tier cap on how many listings a workspace can keep active at one time. When you exceed it, the API short-circuits every request that would create or operate on listings until you drop back under the cap or upgrade.

It also fires, without blocking anything else, when an activation would take you over the cap: PATCH /v1/listings/{id} with {"active": true} or POST /v1/listings/status with "active": true. Nothing is activated. For the bulk call, only listings that are currently inactive count toward the new total.

The cap by tier:

TierActive listings cap
free3
starter50
customunlimited

The error envelope tells you exactly where you stand: tier is the current plan, limit is the cap, and active_listings is the live count.

Response shape

Every Repull error follows the same envelope. The code is stable and safe to switch on.

{
  "error": {
    "code": "listings_limit_exceeded",
    "message": "Your account has 24 active listings but the 'free' tier is capped at 3...",
    "fix": "Either reduce your active listings to 3 or fewer (via DELETE endpoints — these are still served), or upgrade your plan at https://repull.dev/dashboard/billing to lift the cap immediately.",
    "docs_url": "https://repull.dev/docs/errors/listings_limit_exceeded",
    "tier": "free",
    "limit": 3,
    "active_listings": 24,
    "upgrade_url": "https://repull.dev/dashboard/billing",
    "request_id": "req_01HXY..."
  }
}

How to fix

  1. Read `error.tier`, `error.limit`, and `error.active_listings` — they give you the exact gap you need to close.
  2. Path A — deactivate listings you do not need: `POST /v1/listings/status` with `{"listingIds": [...], "active": false}` (up to 500 at once), or `PATCH /v1/listings/{id}` with `{"active": false}` / `DELETE /v1/listings/{id}` for one. All three keep working over-cap, so you can self-recover without paying. Deactivated listings keep syncing and can be activated again later.
  3. Path B — upgrade: send the operator to `https://repull.dev/dashboard/billing` (also surfaced as `error.upgrade_url`). The server-side usage cache is 60 seconds, so the first 200 response after upgrade may take up to a minute.
  4. While over-cap, `GET /v1/listings`, `GET /v1/listings/{id}`, `/v1/health`, `/v1/usage/summary`, `/v1/usage/logs` and `/v1/usage/tier` continue to return 200 — use them to pick listings to deactivate and to render the over-cap state without hitting the 402 wall.
  5. Do NOT implement a retry loop on 402. Unlike 429, this is not a transient condition — no `Retry-After` is set and there is nothing to wait for. The status only clears by deactivating listings or upgrading.

Common gotchas

  • 402 ≠ 429. Both block traffic, but 402 has no Retry-After header. SDK retry/backoff middleware that ignores the status code and just sleeps on any error will spin forever. Switch on error.code and short-circuit listings_limit_exceeded to a human-visible upgrade prompt instead of retrying.
  • Deactivating is always served. POST /v1/listings/status, PATCH /v1/listings/{id} and any DELETE keep working over-cap so you can never get stuck — you can always trim listings without paying. Activation through the same endpoints is still refused if it would exceed the cap. GET /v1/listings, /v1/health, /v1/usage/summary, /v1/usage/logs and /v1/usage/tier are also served.
  • Post-upgrade is not instant. The active-listings count is cached for 60 seconds. If you upgrade and immediately retry, you may see one more 402 before the first 200. Wait a minute or backoff once before declaring the upgrade failed.
  • The cap is on active listings. Inactive listings do not count, even though their data keeps syncing. See Active & inactive listings. The full set of states that count is defined by the API; check /v1/usage/tier for the canonical number — it returns used, limits and remaining for dynamicPricingListings.
  • The `custom` tier is uncapped, so this error is never returned to custom workspaces.

Examples

curl

# Over-cap free-tier workspace calling any non-DELETE endpoint
curl https://api.repull.dev/v1/listings \
  -H "Authorization: Bearer sk_live_FREE_TIER_KEY"

# HTTP/1.1 402 Payment Required
# {
#   "error": {
#     "code": "listings_limit_exceeded",
#     "message": "Your account has 24 active listings but the 'free' tier is capped at 3...",
#     "fix": "Either reduce your active listings to 3 or fewer (via DELETE endpoints — these are still served), or upgrade your plan at https://repull.dev/dashboard/billing to lift the cap immediately.",
#     "docs_url": "https://repull.dev/docs/errors/listings_limit_exceeded",
#     "tier": "free",
#     "limit": 3,
#     "active_listings": 24,
#     "upgrade_url": "https://repull.dev/dashboard/billing",
#     "request_id": "req_01HXY..."
#   }
# }

# Recovery path A — deactivate listings (served over-cap, still 200s)
curl -X POST https://api.repull.dev/v1/listings/status \
  -H "Authorization: Bearer sk_live_FREE_TIER_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "listingIds": ["4118", "4119"], "active": false }'

# Recovery path B — upgrade and wait for the 60s usage cache to refresh
open https://repull.dev/dashboard/billing
# … wait ~60s …
curl https://api.repull.dev/v1/listings \
  -H "Authorization: Bearer sk_live_FREE_TIER_KEY"
# HTTP/1.1 200 OK

# Allowlist — these continue to work over-cap
curl https://api.repull.dev/v1/health        -H "Authorization: Bearer ..."   # 200
curl https://api.repull.dev/v1/usage/tier    -H "Authorization: Bearer ..."   # 200

TypeScript

import { Repull } from '@repull/sdk'

const repull = new Repull({ apiKey: process.env.REPULL_API_KEY! })
const API = 'https://api.repull.dev'
const headers = {
  Authorization: `Bearer ${process.env.REPULL_API_KEY}`,
  'Content-Type': 'application/json',
}

// RepullError (thrown by the SDK) carries code, message, fix and docsUrl, but
// not tier / limit / active_listings / upgrade_url. Read the envelope directly
// when you need those numbers.
const res = await fetch(`${API}/v1/reservations`, { headers })

if (res.status === 402) {
  const { error } = await res.json()
  if (error.code !== 'listings_limit_exceeded') throw new Error(error.message)

  // 402 — NOT a "wait and retry" condition. Do not back off.
  // Two paths back to 200:
  //   1. Deactivate listings until active_listings <= limit
  //   2. Upgrade at error.upgrade_url
  console.error(
    `Over plan cap: ${error.active_listings} active / ${error.limit} on the '${error.tier}' tier.`,
  )

  const overflow = error.active_listings - error.limit
  if (overflow < 5) {
    // Path A — deactivate a few listings. GET /v1/listings and
    // POST /v1/listings/status are both served over-cap.
    const { data: listings } = await repull.listings.list({ limit: overflow })
    // One all-or-nothing call instead of a DELETE per listing.
    await fetch(`${API}/v1/listings/status`, {
      method: 'POST',
      headers,
      body: JSON.stringify({ listingIds: listings.map((l) => l.id), active: false }),
    })
  } else {
    // Path B — surface the upgrade URL to the operator
    return { needsUpgrade: true, upgradeUrl: error.upgrade_url, message: error.message }
  }
}

If you're an AI agent

The workspace is over its plan's active-listings cap. STOP. This is NOT a rate-limit-style transient error — there is no Retry-After and nothing to wait for. Tell the user error.message verbatim, then offer two paths: (a) deactivate listings until active_listings <= limit with POST /v1/listings/status {listingIds, active: false} (served over-cap; deactivated listings keep syncing and can be activated later), or (b) upgrade at error.upgrade_url. After an upgrade, wait ~60s before the next call to let the server-side usage cache refresh. Never retry in a loop.

Hit an error that isn't covered? Email hello@repull.dev with the request id from the response headers.

AI