Error handling
Exception hierarchy, error attributes, HTTP 200 payload errors, client-side validation, and retry guidance for the listingsAPI Python SDK.
All exceptions are importable from the top-level listingsapi module.
Exception hierarchy
ListingsAPIError
├── APIError API-reported failure: non-2xx, or an error payload in HTTP 200
│ ├── AuthenticationError 401, or payload codes SY90005 / SY90001
│ ├── PermissionDeniedError 403, or payload code SY90003
│ ├── NotFoundError 404
│ ├── ValidationError 400 / 422, mutation failures, client-side validation
│ ├── RateLimitError 429, or payload code RATE_LIMITED (carries .retry_after)
│ └── InternalServerError 5xx (safe to retry)
└── APIConnectionError network failure: DNS, connection refused, timeout
Catch ListingsAPIError to handle everything the SDK can raise, APIError
for anything the API reported, or a specific subclass for targeted handling.
Exception attributes
APIError and its subclasses carry:
| Attribute | Type | Description |
|---|---|---|
status_code | int | None | HTTP status code (can be 200 for payload-level errors) |
code | str | None | First platform error code, e.g. SY10005 |
errors | list[dict] | Normalized error entries, each {"code", "message", "context"} |
response_body | str | None | Raw response text from the API |
retry_after | float | None | RateLimitError only; parsed from the Retry-After header |
APIConnectionError inherits directly from ListingsAPIError and has none
of these attributes; the exception message describes the network failure.
Catching exceptions
import listingsapi client = listingsapi.ListingsAPI() try: location = client.locations.retrieve(16808)except listingsapi.AuthenticationError as e: print("Invalid API key:", e.code)except listingsapi.PermissionDeniedError as e: print("Not allowed:", e.status_code)except listingsapi.NotFoundError as e: print("No such location:", e.status_code)except listingsapi.ValidationError as e: for err in e.errors: print(err["code"], err["message"], err["context"])except listingsapi.RateLimitError as e: print("Rate limited, retry in", e.retry_after, "seconds")except listingsapi.InternalServerError as e: print("Server error (safe to retry):", e.status_code)except listingsapi.APIConnectionError as e: print("Network failure:", e)except listingsapi.APIError as e: print("Unexpected API error:", e.status_code, e.code, e.response_body)Errors inside HTTP 200 responses
The platform sometimes reports failures inside a 200 response body. The
SDK inspects every response and raises, so you never check success flags
or scan errors arrays yourself:
- Top-level
errors[]bodies are mapped by code:SY90005andSY90001raiseAuthenticationError,SY90003raisesPermissionDeniedError,RATE_LIMITEDraisesRateLimitError, and any other code raisesAPIError. - Mutation envelopes (create, update, archive, post publishing) that
come back with
success: falseor a per-mutationerrors[]list raiseValidationError.
In both cases status_code is the actual HTTP status (often 200), code
is the first platform code, and errors holds every normalized entry:
import listingsapi client = listingsapi.ListingsAPI() try: result = client.locations.update({"id": 16808, "phone": "not-a-phone"})except listingsapi.ValidationError as e: print(e.status_code) # 200: the API accepted the request but rejected the mutation print(e.code) # first platform code, e.g. "SY10005" for err in e.errors: print(err["code"], err["message"], err["context"])Client-side validation
locations.add() and the post helpers (posts.create_announcement(),
posts.create_event(), posts.create_offer(), posts.bulk_publish())
validate their payload locally and raise ValidationError before any
network call, so a bad payload fails fast with a clear message instead of
a round trip.
For locations.add():
namemust be 2 to 150 charactersdescriptionmust be at least 200 characterssub_category_idis requiredcountry_isois requiredcityis required unlesshide_address=True(service-area businesses)
For the post helpers:
nameis required andlocation_idsmust not be emptysitesmust be a non-empty subset ofGOOGLE,FACEBOOKmessageis required; when it is a per-site dict, every target site needs textcta_typeandcta_urlmust be provided together, andcta_typemust be one ofBOOK,ORDER,SHOP,LEARN_MORE,SIGN_UP,GET_OFFERtitleis required for events and offers
On these locally raised errors status_code, code, and response_body
are None and errors is empty; str(e) lists every problem found:
import listingsapi client = listingsapi.ListingsAPI() try: client.locations.add( name="Acme Dental", description="Too short.", sub_category_id=1432, country_iso="US", city="New York", )except listingsapi.ValidationError as e: print(e) # Invalid location: description must be at least 200 characters (got 10) print(e.status_code) # None: raised before any network callAutomatic retries
The SDK retries 429, 500, 502, 503, and 504 automatically for
GET and POST methods (default 2 retries, 0.5-second exponential
backoff factor, Retry-After honoured). Configure via the max_retries
constructor argument; see Configuration.
Backing off on RateLimitError
When a 429 survives the built-in retries, RateLimitError.retry_after
carries the server's Retry-After value in seconds (or None if the
header was absent). A simple recipe for batch jobs:
import randomimport time import listingsapi client = listingsapi.ListingsAPI(max_retries=0) # manual retry control def with_backoff(call, max_attempts=5): for attempt in range(max_attempts): try: return call() except listingsapi.RateLimitError as e: if attempt == max_attempts - 1: raise if e.retry_after is not None: delay = e.retry_after else: delay = min(2 ** attempt, 30) + random.random() time.sleep(delay) page = with_backoff(lambda: client.locations.list(first=50))for location in page: print(location.name)Importing exceptions
# All exceptions from the top-level moduleimport listingsapi listingsapi.ListingsAPIErrorlistingsapi.APIErrorlistingsapi.AuthenticationErrorlistingsapi.PermissionDeniedErrorlistingsapi.NotFoundErrorlistingsapi.ValidationErrorlistingsapi.RateLimitErrorlistingsapi.InternalServerErrorlistingsapi.APIConnectionError # Or import individuallyfrom listingsapi import RateLimitError, ValidationErrorSee also
- Configuration:
max_retriesandtimeout - Quickstart: your first error-handled script