Every error from the SeatLayer API has the same shape: a JSON object whose
first key, error, is a stable machine code. Branch on error (and code
when present), never on message.
The error body
{
"error": "not_found"
}{
"error": "order_edit_in_progress",
"code": "order_edit_in_progress",
"retryable": true,
"conflicts": [{ "label": "A-12", "status": "blocked" }]
}| Field | Always present | Meaning |
|---|---|---|
error |
Yes | Stable machine code, never a sentence |
code |
No | A more specific reason when error is a broad class, such as error: "forbidden" with code: "missing_manage_capability". Often equal to error |
message |
No | Human-readable text for logs and operators. It can change; do not parse it |
retryable |
No | true when the same request can succeed later without changes |
retryAfterSeconds |
No | Seconds to wait, on rate limits and maintenance |
requestId |
No | Sent in the body on 500 and 503. Every response also carries an X-Request-ID header |
Routes add their own detail fields next to these, for example conflicts,
issues, maxBytes, or currentUpdatedAt. Treat unknown fields as extra
information, not as a different error.
The OpenAPI document lists the exact error codes each operation can return. This page groups them so you can write one handler.
Status codes
| Status | Meaning | Retry |
|---|---|---|
400 |
The request is missing a required field or is malformed | No. Fix the request |
401 |
No valid credential | No. Check the key |
402 |
Not enough credits to create inventory | No. Buy credits first |
403 |
The credential cannot do this, or the resource is in the other mode | No |
404 |
The resource does not exist for this account and mode | No |
409 |
The current state does not allow the change | Only when retryable: true |
413 |
Body or upload too large | No |
415 |
Unsupported file type | No |
422 |
The request is well formed but a value is invalid | No |
429 |
Rate limited | Yes, after Retry-After |
500 |
Unexpected server error | Reads: yes. Writes: see below |
502 |
A payment provider step needs a retry | Yes, with the same Idempotency-Key |
503 |
Temporarily unavailable or in maintenance | Yes, after Retry-After |
Codes by area
Authentication and access
| Status | error / code |
When |
|---|---|---|
401 |
unauthorized / bad_key |
Missing, revoked, or malformed sk_… secret key |
401 |
unauthorized / bad_manage_session, manage_session_expired |
Event manage token is invalid or expired |
403 |
mode_mismatch |
A test key used on a live resource, or the reverse |
403 |
forbidden / missing_manage_capability |
The manage token lacks the capability, such as event:block |
403 |
forbidden / event_scope_mismatch |
The manage token belongs to another event |
403 |
forbidden |
The dashboard role cannot make this change |
403 |
channel_access_denied |
The seats belong to a sales channel this caller cannot use |
403 |
module_not_enabled |
The feature is not turned on for your account, for example Seasons or Event Configurations |
Validation
| Status | Examples |
|---|---|
400 |
name_required, chartId_required, selections_required, holdId_required, qty_required, labels_required, labels_and_holdId_required, bookingRef_required, hold_or_labels_and_bookingRef_required, objects_required, rules_required, releaseAt_required, blockRef_required, invalid_zone, invalid_cursor, empty_body, nothing_to_update |
413 |
chart_too_large, image_too_large, payload_too_large |
415 |
unsupported_image |
422 |
invalid_request, invalid_selection, invalid_chart, invalid_currency, invalid_timezone, invalid_listing_id, invalid_availability_rule, unknown_zone, image_dimensions_unsupported |
Events and charts
| Status | error |
When |
|---|---|---|
402 |
insufficient_credits |
Creating or copying a live event, switching one to live, or adding Season performances, while the credit balance is 0 or below. The body also carries message (a sentence you can show) and balance, the current balance. Live events already on sale keep selling |
404 |
not_found, chart_not_found, chart_unavailable |
Unknown event or chart |
409 |
event_exists |
The workspace already has an event with this slug |
409 |
event_not_mutable, invalid_sales_transition, sales_revision_conflict |
The event’s sales state does not allow the change |
409 |
performance_group_locked, season_event_locked |
The event belongs to an active Performance Group or Season |
409 |
seats_would_vanish |
Updating the chart would leave out or change seats that are sold, held or blocked |
409 |
event_configuration_bound |
Unbind the Event Configuration before changing the chart |
422 |
chart_not_published, invalid_event_inventory, inventory_limit_exceeded |
The chart cannot become event inventory |
Holds, booking, and inventory
| Status | error |
When |
|---|---|---|
402 |
credit_limit_reached |
New live sales are stopped because the credit balance reached the account’s limit below zero. The body carries message, balance and floor. Holds already taken still book; topping up resumes sales at once |
404 |
hold_not_found |
The hold id is unknown |
409 |
conflict |
One or more seats are not available. conflicts lists each label and its status. Nothing in the request was applied |
409 |
event_closed |
Sales are closed or outside the sales window. A buyer whose sale is paused by the account’s credit limit gets this too, with paused: true and message |
409 |
hold_not_active |
The hold expired or was released before booking |
409 |
cannot_extend |
The hold is gone, expired, or at its extension limit |
409 |
booking_mismatch |
unbook named a bookingRef that does not own the seats |
409 |
not_booked |
Resale listing named a seat that is not booked |
409 |
resale_unsupported_on_seatlayer_checkout |
Resale on a Hosted Ticketing event or ticketed seat |
409 |
order_edit_in_progress |
An order edit is changing these seats. Sent with retryable: true |
409 |
legacy_hold_requires_restart |
The hold predates the current format; start a new hold |
422 |
hold_quantity_exceeded |
More seats than the hold limit allows |
Idempotency
| Status | error |
When |
|---|---|---|
400 |
invalid_idempotency_key |
The key is not 1 to 128 characters of A-Z a-z 0-9 . _ : - |
400 |
idempotency_not_supported_for_operation |
This operation does not accept Idempotency-Key |
400 |
idempotency_key_required |
Season order refunds and cancels need a key |
409 |
idempotency_conflict |
The key was used before with a different request |
409 |
idempotency_in_progress |
The first call with this key is still running. Retry after Retry-After |
See idempotency and conflicts.
Seasons and Performance Groups
| Status | Examples |
|---|---|
409 |
not_a_draft, revision_mismatch, not_active, sales_open, sales_not_ended, plan_not_published, invalid_transition, lifecycle_transition_pending, amendment_in_flight, payment_in_progress, book_operation_mismatch, booking_not_cancellable, hosted_order_use_refund, group_not_draft, group_not_active, member_set_mismatch |
422 |
invalid_notify_customers: notifyCustomers on a date change is not true or false. See Email the date’s customers |
502 |
refund_recovery_required: retry with the same Idempotency-Key |
503 |
channel_state_unavailable, participant_unavailable, recovery_pending, lifecycle_unavailable |
503 |
season_busy: the season had too many holds at once, or part of it did not answer in time. Nothing was written. Send the same request again after Retry-After (2 seconds). See when a hold can’t finish |
Long Season and Performance Group operations can answer 202 Accepted with
Location and Retry-After. That is progress, not an error: poll the
Location until the state is terminal.
Season seats
Season seats are the seats a season sets aside on every date. While they are set aside, some changes would make them wrong, and a few changes aren’t possible in block mode yet. Both are refused, and nothing changes.
| Status | Code | When |
|---|---|---|
409 |
season_block_not_enabled |
Season seats aren’t turned on for this season (inventoryMode: "block") |
409 |
season_block_packages |
Turning season seats on, or setting them aside, on a season with more than one package |
409 |
season_block_on_sale |
Turning season seats on once the season’s sales have opened |
409 |
season_block_exists |
inventoryMode: "per_hold" once seats are set aside, or once the season sells from its block |
422 |
invalid_inventory_mode |
inventoryMode is neither block nor per_hold |
409 |
block_exists |
The season already has its block |
409 |
block_not_ready |
Releasing before the season sells from its block, or opening sales while season seats are on and it doesn’t sell from its block yet |
409 |
season_not_draft_or_active |
Setting seats aside on a closed or archived season |
422 |
block_selection_empty |
No priced seat is left after the seats kept for single tickets |
422 |
seat_not_in_block |
A seat named in a release isn’t a season seat |
409 |
season_block_set_aside |
Seats are set aside and block mode isn’t on yet. Refused: adding or removing a date, another package, a package pricing other categories, publishing one package on its own (use publishSeason), and date amendments other than a new time. Set the seats aside again after the change. |
409 |
season_inventory_locked |
A change block mode doesn’t do yet. The body’s action names it: date_changes (adding or removing a package’s date), amendments (a postpone, a replacement or a cancel exception), holder_cancel (cancelling a booking), date_cancel (taking one date back from an order), returns (returning or reclaiming a date’s seats) or recovery (emergency recovery). |
409 |
date_leave_in_progress |
The date is already being taken off sale another way |
409 |
season_block_one_package |
Before Publish, seats are set aside for one package, and this draft has more than one. Add the others after Publish |
409 |
plan_not_published |
Setting seats aside on an active season that has no published package |
409 |
season_block_undoing |
Season seats are still being given back after an undo. Set them aside again in a moment |
409 |
release_mismatch |
This Idempotency-Key already started a different release |
422 |
chart_unavailable |
The season’s chart can’t be read, so no seat can be set aside |
422 |
dates_missing |
The package has no dates to sell |
503 |
season_busy, season_not_configured |
A shard or the season object did not answer. Send the same set-aside or release again with the same Idempotency-Key: it runs once. For retrieveSeasonBlock, read again |
202 |
block_date_leave_pending |
Not an error. A block season’s date cancel or past date is still leaving a shard. It finishes by itself: send the same request again after Retry-After to read how it ended. |
Old SDK versions surface these through their generic API error, with code.
Webhooks
| Status | error |
When |
|---|---|---|
403 |
forbidden |
Only an admin can change subscriptions |
404 |
not_found |
Unknown subscription |
422 |
invalid_url, invalid_events, invalid_mode, invalid_environment |
The endpoint must be public HTTPS and events must be known names |
Service-wide
| Status | error |
When |
|---|---|---|
400 |
invalid_request |
The request holds characters that cannot be stored, such as a NUL byte. Do not retry it unchanged |
404 |
not_found |
No such route |
429 |
rate_limited |
See rate limits |
500 |
internal_error |
Unexpected failure. Quote requestId to support |
503 |
temporarily_unavailable |
A temporary server condition; safe to retry. Sent with retryable: true and Retry-After: 1 |
503 |
maintenance_mode |
Writes are paused. Reads still work. The body has retryAfterSeconds, resumesAt, and message |
Which errors to retry
- Reads (
GET): retry on connection errors,408,429, and5xx, with backoff. An event’s report, objects, log, and seat history answer503withcode: event_temporarily_unavailableandRetry-After: 1while the event’s inventory restarts; repeat the read after that delay. - Writes with
Idempotency-Key: retry on the same failures with the same key. The first result is replayed, so nothing happens twice. - Writes without idempotency: holds, blocks, and most inventory changes.
Retry
429afterRetry-After, because it did nothing. A503whosecodeisevent_temporarily_unavailablemeans the event’s inventory did not answer in time, and the change may still have been made. Before you repeat a hold, block, extend, or best-available call, read the hold or the seats. After a timeout or500, also read the current state before trying again. - Book: on a
503withretryable: true, wait forRetry-Afterand send the samebookrequest once more, with the samebookingRef. One reference cannot make a second sale, so this repeat is always safe. If it answers503again, keep the order as payment in progress and retry it from your queue. 409withretryable: true: wait briefly and send the same request.- Every other
4xx: do not retry unchanged. Fix the request, pick other seats, or show the conflict to the operator.
A booking call keyed by one bookingRef is safe to repeat: the same reference
cannot create a second sale. Releasing a hold is safe to repeat too: it only
frees seats still held under that holdId. See
idempotency and conflicts.