Errors, limits and support
The shape
{ "error": { "code": "insufficient_stock", "message": "Only 4,000 gold left on this listing", "requestId": "…" } }
Switch on the code; show the message to a person; quote the request id to support. Validation refusals add details with the fields that failed.
Codes you will meet
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request | The body is not JSON; the message says where it broke. A body with a __proto__ key anywhere in it is refused the same way. |
| 400 | invalid_price | A price finer than its currency carries (a USD price has two decimal places), or one that is not above zero. On a sheet it lands on the line, as invalid. |
| 400 | delivery_method_required | A listing on a currency lane whose game lists delivery methods named none of them in attributes.deliveryMethods. The message lists the game's methods; join several with a comma and a space. A sheet line is staged all the same and fails with this code at commit. |
| 415 | http | The body is not application/json (or multipart, for the proof image). A POST with no body sends no Content-Type, or application/json. |
| 401 | unauthenticated | No key, or one that does not exist here. Check the prefix against the host. |
| 401 | key_revoked, key_expired | The key is over. Mint another. |
| 401 | signature_required, signature_invalid, signature_stale, signature_replayed | The key is set to signed requests and this one did not pass. See Signing. |
| 403 | insufficient_scope | The key lacks the route's scope; the body names it. |
| 403 | ip_not_allowed | The key has an address allowlist and this address is not on it. |
| 403 | account_suspended | Reads work; writes that start something do not. |
| 400 | validation_failed | The body or query failed its schema; details names each field and rule. A NUL byte anywhere is refused the same way. |
| 400 | unknown_game, unknown_server, unknown_category, unknown_package | A listing, an upsert or a sheet line named a placement the catalog does not have. A catalog or template lookup by slug answers 404 instead. |
| 400 | invalid_cursor | A cursor that is not one of ours. Start the walk again without one. |
| 400 | unsafe_url | A webhook URL that is not https, names a port other than 443, carries credentials or a fragment, does not resolve, or resolves to a private, loopback or link-local address. |
| 404 | not_found, template_not_found, listing_unavailable, sandbox_only | The id or slug is not ours, or not yours; a boost template named by slug where its id was expected; a paused or taken-down listing on a sandbox order; a sandbox route outside the sandbox. |
| 409 | external_id_taken | Another of your listings already carries that external id. |
| 409 | placement_locked | A listing cannot move to another game, lane or realm. Make a new line. |
| 409 | illegal_transition | The order is not in a state that allows the move; its state is in the body. |
| 409 | tiers_conflict | A price or minimum change left the volume ladder no longer descending. Send the ladder with it. |
| 409 | tiers_need_restating | A volume tier is a price for the batch (price_per_qty) the listing is quoted over, so a change to the batch has to send the tiers again in the same request, priced for the new batch, or an empty list to remove them. |
| 409 | endpoint_disabled | A test or a replay on a disabled endpoint. Set its status to active first, or the ping goes nowhere. |
| 409 | too_many_endpoints, too_many_keys | Five webhook endpoints and twenty keys per seller. Delete or revoke one first. |
| 409 | idempotency_in_progress | The first request under that key is still being answered. Retry in a moment and you get its response. |
| 409 | credentials_required | An account order cannot be delivered before the handover is stored through POST /v1/orders/{id}/credentials. |
| 409 | insufficient_stock | The order asks for more than the listing holds. |
| 409 | sync_empty, sync_expired, sync_not_open | A replace sync with no lines would pause every listing, so it is refused; a sync past its hour; a sync already committed or expired. |
| 422 | idempotency_key_reused | The same key on a different request. |
| 429 | rate_limited | The minute's budget is spent. Wait for Retry-After. |
Versioning
The document carries a date as its version. Fields are added, never removed or renamed, and a new event type is a new subscription, never a change to an existing body. Anything that would break a client gets a new path prefix and a year's notice on the old one.
Support
support@freeport.gg, with the request id. The delivery log in Integrations shows every webhook attempt with our answer and yours; the console's stock sync page shows every failed line with its code. Most questions are answered there before the email is written.