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
| Status | Meaning | Typical SY code | Python SDK exception |
|---|---|---|---|
200 OK | Request succeeded. For writes, also check the mutation body (see below). | - | - |
400 Bad Request | Malformed request, an unparseable filter, an invalid enum value, or a malformed ID. | SY90006, SY90001 | ValidationError |
401 Unauthorized | The API key is missing, malformed, or revoked. | SY90005 | AuthenticationError |
403 Forbidden | The key is valid but not permitted to access the resource. | SY90003 | PermissionDeniedError |
404 Not Found | The path, or a resource addressed by ID, does not exist. | SY90002 | NotFoundError |
422 Unprocessable Entity | The request was well-formed, but a value could not be processed (for example, an unknown or ambiguous category). | - | ValidationError |
429 Too Many Requests | You exceeded your plan's rate limit. Honor the Retry-After header. | - | RateLimitError |
5xx | A server-side or upstream error. Safe to retry with backoff. | SY90007 | InternalServerError |
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
| Code | Message | HTTP status | Meaning |
|---|---|---|---|
SY90005 | Invalid Token | 401 | The API key is missing, malformed, or revoked. Re-issue a key from the dashboard and send it as Authorization: API <your-key>. |
SY90001 | Not authorized to access this resource | 401 / 403 | The key was rejected for the requested resource. The Python SDK raises this as AuthenticationError. |
SY90002 | Invalid Id | 404 / 400 | A path or argument ID could not be resolved (often a bad or non-base64 relay ID). |
SY10126 | City is Mandatory | 200 (mutation body) | A required field (city) was omitted from the input. |
SY10010 | state_iso | 200 (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.