Error codes

Listings API HTTP status codes, the SYxxxxx error codes, and the two error-envelope shapes the v4 API returns.

The Listings API uses conventional HTTP status codes to report the outcome of a request. Error codes are SY-prefixed and travel inside the error message string, formatted as SYxxxxx: <human-readable reason>.

HTTP status codes

StatusMeaningTypical SY codePython SDK exception
200 OKRequest succeeded. For writes, also check the mutation body (see below).--
400 Bad RequestMalformed request, an unparseable filter, an invalid enum value, or a malformed ID.SY90006, SY90001ValidationError
401 UnauthorizedThe API key is missing, malformed, or revoked.SY90005AuthenticationError
403 ForbiddenThe key is valid but not permitted to access the resource.SY90003PermissionDeniedError
404 Not FoundThe path, or a resource addressed by ID, does not exist.SY90002NotFoundError
422 Unprocessable EntityThe request was well-formed, but a value could not be processed (for example, an unknown or ambiguous category).-ValidationError
429 Too Many RequestsYou exceeded your plan's rate limit. Honor the Retry-After header.-RateLimitError
5xxA server-side or upstream error. Safe to retry with backoff.SY90007InternalServerError

Two envelope shapes

Every failure carries an errors array. Where that array lives depends on whether the request failed before or after it reached the resolver.

1. Request-level: top-level errors[]

Authentication, authorization, and malformed-request failures short-circuit before the operation runs. They return the matching 4xx status (above) with the error at the top level of the response.

{
  "data": { "createLocation": null },
  "errors": [{ "message": "SY90005: Invalid Token" }]
}

2. Mutation validation: nested data.<operation>.errors[]

A create, update, or publish request that is otherwise well-formed reaches the resolver, so the transport succeeds with HTTP 200. If the resolver rejects the input on a business rule, the mutation reports the failure nested under the operation's own payload in data.<operation>.errors. On success this array is present and empty ("errors": []).

{
  "data": {
    "createLocation": {
      "errors": [
        { "message": "SY10126: City is Mandatory" },
        { "message": "SY10010: state_iso" }
      ],
      "location": null
    }
  }
}

Always read data.<operation>.errors on a write, even on a 200 with a populated data.<operation> object: a mutation can partially fail and report it here.

Common codes

CodeMessageHTTP statusMeaning
SY90005Invalid Token401The API key is missing, malformed, or revoked. Re-issue a key from the dashboard and send it as Authorization: API <your-key>.
SY90001Not authorized to access this resource401 / 403The key was rejected for the requested resource. The Python SDK raises this as AuthenticationError.
SY90002Invalid Id404 / 400A path or argument ID could not be resolved (often a bad or non-base64 relay ID).
SY10126City is Mandatory200 (mutation body)A required field (city) was omitted from the input.
SY10010state_iso200 (mutation body)The stateIso value is missing or invalid. See the countries and states endpoint for valid values.

Validation codes in the SY10xxx range correspond to a specific input field; the message names the field or the rule that failed. Auth and authorization codes in the SY90xxx range are request-level and arrive at the top level with the matching status.

rate_limited

429 Too Many Requests: the account exceeded its plan's rate limit. The body is the standard error envelope carrying a retry_after_seconds hint, a correlation_id to quote in support requests, and a doc_url that links back to this section:

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many requests. Slow down.",
    "retry_after_seconds": 30,
    "correlation_id": "9f2c1e64-2b1a-4b0e-9f1a-2c7d5e8a1b3c",
    "doc_url": "https://docs.listingsapi.com/docs/error-codes#rate_limited"
  }
}

Wait for retry_after_seconds (or the Retry-After header when present), then retry with exponential backoff. See Rate limits for the per-plan limits and a reference backoff implementation.

Dev-portal control-plane errors

Account and key-management endpoints in the developer portal (the dashboard's own API routes) return a single, structured error envelope: distinct from the REST errors[] shapes above. Each code carries a correlation_id and a doc_url that links back to the matching section here.

{
  "error": {
    "code": "AUTH_INVALID_KEY",
    "message": "Session is invalid or expired.",
    "correlation_id": "9f2c1e64-2b1a-4b0e-9f1a-2c7d5e8a1b3c",
    "doc_url": "https://docs.listingsapi.com/docs/error-codes#auth_invalid_key"
  }
}

auth_missing_key

401: No credential was provided. Sign in again, or send your key as Authorization: API <your-key>.

auth_invalid_key

401: The session or key is invalid or expired. Re-authenticate or re-issue a key from the dashboard.

auth_insufficient_scope

403: The credential is valid but not permitted for this action. A read-scoped key cannot call write endpoints; issue a write key if you need one.

not_found

404: The requested resource, or a resource addressed by ID, does not exist.

conflict

409: The request conflicts with the current state of the resource (for example, revoking a key that is already revoked).

validation_failed

422: The request body failed validation. The envelope's field and details name the offending input.

internal_error

500: An unexpected server-side error. Safe to retry with exponential backoff; quote the correlation_id if you contact support.

dependency_unavailable

503: A required upstream dependency is temporarily unavailable. Retry with backoff.

For 429, see rate_limited above.

Handling errors in code

Check the HTTP status first; then, for writes, scan the mutation body for a nested errors array:

resp = client._request(method="POST", path="/api/v4/locations", body=payload)

# `_request` raises on a non-2xx status (AuthenticationError, ValidationError,
# and so on). On a 200, a mutation can still report field errors in its payload:
op = (resp.get("data") or {}).get("createLocation") or {}
if op.get("errors"):
    messages = [e["message"] for e in op["errors"]]
    raise RuntimeError(f"Listings API error: {messages}")

The Python SDK does both for you: it maps each HTTP status to a typed exception and also raises on error payloads returned inside a 200 body.