Errors
The Synex API uses conventional HTTP status codes and always returns the same error envelope, so you can branch on machine-readable fields rather than parsing prose. A 2xx means success; 4xx means the request needs fixing; 5xx means something failed on our side.
The error envelope
Every non-2xx response has a single top-level error object.
- Name
type- Type
- string
- Description
Broad category — branch on this first. One of the types listed below.
- Name
code- Type
- string
- Description
Specific, machine-readable failure — branch on this second.
- Name
message- Type
- string
- Description
Human-readable explanation. Do not parse it; wording may change.
- Name
param- Type
- string
- Description
The offending request parameter, when applicable. Nullable.
- Name
request_id- Type
- string
- Description
A
req_identifier for the whole request, also returned as theX-Request-Idheader. Always present. Quote it when contacting support.
- Name
error_id- Type
- string
- Description
5xxonly. Anerr_identifier unique to this one failure. Quote it when reporting a server-side problem — it is the most direct way for support to find the exact incident.
- Name
sentry_id- Type
- string
- Description
Present when the failure was recorded in our error tracker — every
5xxand most4xx. Optional; see the note below.
- Name
errors- Type
- object
- Description
Per-field details on validation failures (for example, property id → failure details). Omitted on failures that are not per-field.
Example error
{
"error": {
"type": "invalid_request_error",
"code": "resource_missing",
"message": "Passport not found.",
"param": "passport",
"request_id": "req_9f2c1ab34d5e6f70",
"sentry_id": "6fb2138a0f364dffa0729f63abba46ac"
}
}
sentry_id is best-effort and not a severity signal. It is omitted for
failures rejected before they reach the API — unmatched routes, unsupported
methods, request validation, rate limits, and requests sent with no
credentials — and the same code can arrive with or without one depending on
where it was raised. Never branch on its presence; branch on type and
code. error_id is the reliable marker of a server-side failure.
Replayed responses
A response replayed from an Idempotency-Key — marked by the Idempotent-Replayed: true header — is the stored body of the original request, so its error.request_id is the original request's id, not the id of the call you just made. The X-Request-Id header always reflects the current request. Quote the header's id when asking about the replay itself and the body's id when asking about the original.
Error types
- Name
invalid_request_error- Type
- 400/404/405/409/413/422
- Description
Bad parameters, missing resources, or state conflicts. Synex Lab's PassQL tooling answers with
422— see below.
- Name
authentication_error- Type
- 401
- Description
Missing or invalid key, or the key's owner has left its team.
- Name
permission_error- Type
- 403
- Description
The key lacks the required ability — never granted, or no longer held by the member who minted it — or its ontology scope does not cover the resource; or the key's team is suspended or cannot issue passports.
- Name
rate_limit_error- Type
- 429
- Description
Too many requests — honor the
Retry-Afterheader.
- Name
idempotency_error- Type
- 409
- Description
An idempotency key was reused with a different payload, or is still in flight.
- Name
api_error- Type
- 5xx
- Description
Something failed on our side. Retry with backoff. Also Lab's
408 query_timeout.
Request validation failures come back as 400 parameter_invalid, with the
offending field in param and per-field detail in errors.
Synex Lab: PassQL tooling
Synex Lab returns the standard error envelope, and only that. Every non-2xx body from the Lab authoring endpoints is the envelope above, with the editor-oriented detail rows under error.errors — the positioned parse feedback an editor underlines with sits in the same place it does everywhere else.
Success bodies are a different shape: query/validate answers {valid: true, mode} and dsl/parse answers {valid: true, ast, kind} on a 200.
The status is 422 rather than 400. Handle it the same way — fix the input and resubmit; retrying it unchanged will fail again. 422 is not the only status these endpoints use, though: query/validate reports at whatever status the underlying failure carries — 403, for instance, when the query names another team's ontology — so branch on error.code rather than the status.
error.type is safe to branch on across the whole API. Lab's 403 forbidden_ontology is typed permission_error and its 408 query_timeout
is typed api_error, like every other code with those statuses.
Error codes
Every error.code the API returns, grouped by the surface that raises it.
Requests
- Name
parameter_invalid- Type
- 400
- Description
A request field failed validation;
paramanderrorssay which.
- Name
resource_missing- Type
- 404
- Description
The id doesn't exist in your team — resources in other teams read as missing. Also returned for unmatched routes.
- Name
method_not_allowed- Type
- 405
- Description
The HTTP method is not supported on this endpoint.
- Name
invalid_cursor- Type
- 400
- Description
The pagination cursor is malformed, tampered with, or replayed under a different set of filters. See pagination.
- Name
metadata_invalid- Type
- 400
- Description
The merged metadata bag broke its limits — 50 keys, 40-character keys, 500-character values, string values only.
paramnames the whole field or the offending entry.
- Name
metadata_too_large- Type
- 413
- Description
The bag fits its own limits but would push the object it is stored on past the 400 KB storage ceiling. Send fewer or shorter keys.
- Name
idempotency_key_too_long- Type
- 400
- Description
Idempotency-Keyexceeded 255 characters.
- Name
idempotency_key_reused- Type
- 409
- Description
Typed
idempotency_error. The idempotency key was already used with a different request payload.
- Name
idempotency_key_in_flight- Type
- 409
- Description
Typed
idempotency_error. The original request with this key is still running. CarriesRetry-After; wait and retry.
- Name
rate_limit_exceeded- Type
- 429
- Description
Typed
rate_limit_error. See rate limits.
- Name
internal_error- Type
- 500
- Description
Typed
api_error. Something failed on our side; retry with backoff and the sameIdempotency-Key.
Authentication and permissions
- Name
invalid_api_key- Type
- 401
- Description
No key was sent, the key is unknown, revoked or expired, or it is a credential the public API does not accept.
- Name
api_key_team_revoked- Type
- 401
- Description
The key's owner is no longer a member of the team the key is pinned to.
- Name
insufficient_permissions- Type
- 403
- Description
The key does not hold the ability the endpoint requires — either it was never granted, or the member who minted the key no longer holds it. The message names the ability. See authentication.
- Name
ability_not_scoped_to_resource- Type
- 403
- Description
The key's ontology scope doesn't cover this resource — including an ontology outside a scoped
ontologies:read, a team-wide list under a scoped ability, andPOST /v1/ontologiesunder a scopedontologies:write. See ontology scopes.
- Name
ability_missing- Type
- 403
- Description
The operation stands in for a passport write and the key holds no
passports:writeat all — opening or applying a data request or a bulk data request.paramispassport, orselectionon a bulk request. Refused before anything is looked up.
- Name
team_suspended- Type
- 403
- Description
The key's team is suspended, so the key is refused whatever it was granted — on
*:writeendpoints at the write tier, on creating endpoints at the create tier, and on every authenticated endpoint at the full tier. See suspended teams.
- Name
company_required- Type
- 403
- Description
The key's team has no company — it is a supplier team — so it cannot issue passports:
POST /v1/ontologies,POST /v1/ontologies/:ontology/passportsandPOST /v1/importsrefuse. Everything else the key was granted stays available.
API keys
- Name
ability_not_grantable- Type
- 400
- Description
abilitiesnames something the calling key does not hold. See monotonic minting.
- Name
scope_not_grantable- Type
- 400
- Description
The requested ontology scope is wider than the caller's — including omitting it, or unscoping.
paramisscopes.
- Name
grant_inconsistent- Type
- 400
- Description
The key's own grants contradict each other: an ability held without the one it requires, or a scoped dependent reaching ontologies its
ontologies:readdoes not.paramis the dependent ability. Reported only once the subset checks pass. See ontologies are the root.
- Name
api_key_limit_reached- Type
- 400
- Description
The team already holds its maximum of 32 live keys. Revoke one first; rolling is exempt.
- Name
key_not_rollable- Type
- 403
- Description
The target key reaches further than the caller, so rolling it would hand over abilities.
paramisapi_key.
- Name
key_not_patchable- Type
- 403
- Description
A
PATCHtouchingabilitiesorscopeson a key that reaches further than the caller.paramisapi_key.
Templates and ontologies
- Name
template_not_draft- Type
- 409
- Description
Completed templates are immutable; only drafts can be edited or deleted.
- Name
template_is_draft- Type
- 409
- Description
Drafts can't be forked or instantiated — complete the template first.
- Name
template_too_large- Type
- 413
- Description
The whole template — structure, names and descriptions — exceeds the 400 KB storage ceiling. Nothing was written.
- Name
draft_missing- Type
- 404
- Description
No draft is open on this ontology.
- Name
draft_exists- Type
- 409
- Description
A draft is already open.
GETit, orDELETEit first.
- Name
draft_conflict- Type
- 409
- Description
base_updated_atis stale. Re-read the draft and reapply your changes.
- Name
version_conflict- Type
- 409
- Description
expected_latest_versionis stale. Re-readlatest_versionand retry.
- Name
structure_invalid- Type
- 400
- Description
The draft breaks the additive-change rules, or names a child level the structure doesn't define. One message per violation in
error.errors.structure.
- Name
ontology_not_migrated- Type
- 409
- Description
The ontology predates versioning and has not been backfilled, so its draft cannot be published. Contact support.
Passports and releases
- Name
value_invalid- Type
- 400
- Description
A passport property value was rejected;
errorsmaps property id → failure details, each with its owncode— see codes inside errors.
- Name
value_too_deep- Type
- 400
- Description
A
rangeorfilevalue nests objects and arrays past the storage ceiling.
- Name
passport_too_large- Type
- 413
- Description
The passport would exceed the 400 KB storage ceiling — values and change history. Nothing in the patch was written.
- Name
level_missing- Type
- 404
- Description
The level named — or the passport's own level — is not in the ontology's schema version.
paramnames what was looked up.
- Name
passport_exists_at_level- Type
- 409
- Description
A singular level already has a passport under that parent.
- Name
friendly_id_taken- Type
- 409
- Description
The friendly id is already used within the parent.
- Name
passport_released- Type
- 409
- Description
The passport is released: its
valuesandfriendly_idare frozen, it cannot be archived directly, and it cannot be deleted. Setpublish_statustodraftfirst. Also returned when applying a data request onto a released passport.
- Name
ancestor_not_released- Type
- 409
- Description
A passport cannot be released while an ancestor is not; the message names the nearest one. Release from the top down.
- Name
descendants_released- Type
- 409
- Description
A released passport cannot be drafted while anything beneath it is still released. Draft the subtree first, deepest first.
- Name
release_in_progress- Type
- 409
- Description
A release run rooted at this passport or one of its ancestors is still running. Poll
GET /v1/release_runs/:iduntil it is terminal, then retry.
- Name
release_conflict- Type
- 409
- Description
The release moved under the request — cancelled, or released again — between the read and the write. Re-read the passport and decide again.
- Name
release_reported- Type
- 409
- Description
The release has already been reported for billing and can no longer be taken back. Contact support if it was reported in error.
- Name
ontology_locked- Type
- 409
- Description
A passport transfer is running on the passport's ontology, which freezes every write to its passports —
values,friendly_id,publish_status,metadata, creating one beneath and deleting one. Nothing was written; retry once the transfer finishes.
Compositions
- Name
composition_missing / composition_exists- Type
- 404 / 409
- Description
No composition exists yet · a composition already exists.
- Name
composition_too_large- Type
- 413
- Description
The composition layout — or one language's strings — exceeds the 400 KB storage ceiling. Nothing was written.
- Name
language_invalid- Type
- 400
- Description
langis not one of the 24 official EU language codes.paramislang.
- Name
language_is_default- Type
- 400
- Description
langis the ontology's default language, whose strings are the composition document itself.paramislang.
- Name
translation_invalid- Type
- 400
- Description
One or more translation strings broke the contract — a malformed key, a non-string, or a string over its cap.
paramisstrings, anderror.errorsis keyed by string key with one message each.
Suppliers and data requests
- Name
supplier_already_linked- Type
- 409
- Description
The address already holds a
pendingorlinkedsupplier link with your team, or a resend was tried on a link that has been accepted. A revoked link releases the address.
- Name
link_revoked- Type
- 409
- Description
A rename or resend on a revoked supplier link, or a data request opened against one. Invite the supplier afresh.
- Name
data_request_not_open- Type
- 409
- Description
A draft or submit on a data request that is not
openorchanges_requested, or a cancel on one already terminal.
- Name
data_request_not_submitted- Type
- 409
- Description
A review, apply or return on a data request that is not
submitted.
- Name
review_incomplete- Type
- 409
- Description
An apply with no value accepted, or a return with no value rejected.
- Name
selection_invalid- Type
- 400
- Description
An id in a bulk data request's selection cannot be taken — not your passport, another level or ontology, an ambiguous friendly id, another pinned version.
paramisselection, anderror.errorsis keyed by the offending id. Nothing is opened.
- Name
selection_too_large- Type
- 400
- Description
The selection names more than 500 passports.
- Name
data_request_set_creating- Type
- 409
- Description
An answers upload on a bulk request whose children are not made yet — poll its creation job first.
- Name
data_request_set_closed- Type
- 409
- Description
A cancel,
submit_all,apply_all, upload or fill on a bulk request that is alreadyclosed.
- Name
nothing_to_submit- Type
- 409
- Description
submit_allon a bulk request with no child left to answer.
- Name
nothing_to_apply- Type
- 409
- Description
apply_allon a bulk request with no submission waiting.
- Name
identifier_missing- Type
- 409
- Description
fulfil/matchon a bulk request the customer marked no identifier on — pair the rows by hand withfulfil/fill.
Webhooks
- Name
webhook_url_invalid- Type
- 400
- Description
The endpoint URL is not
https, carries userinfo, is too long, or resolves into a blocked range.
- Name
enabled_events_invalid- Type
- 400
- Description
An unknown event type, or
"*"mixed with concrete types.
- Name
webhook_endpoint_limit_reached- Type
- 400
- Description
The team already holds 16 endpoints.
Files and uploads
- Name
file_already_exists- Type
- 409
- Description
A concurrent upload took the filename; retry to get the next free name.
- Name
folder_already_exists- Type
- 409
- Description
A folder of that name already exists in the directory.
- Name
file_not_previewable- Type
- 404
- Description
The file exists but is not a spreadsheet the preview can parse — distinct from
resource_missing.
- Name
upload_incomplete- Type
- 400
- Description
completelisted fewer parts than the multipart upload was planned with; the message names the missing part numbers.
- Name
upload_completed- Type
- 409
- Description
A second
complete, or aDELETEafter completing, on a multipart upload.
Synex Lab
- Name
lab_not_enabled- Type
- 409
- Description
The team hasn't enabled Synex Lab.
- Name
transforms_not_enabled- Type
- 403
- Description
The deployment's transform allowlist does not include this team.
- Name
preview_mismatch- Type
- 409
- Description
A transform preview expired or no longer matches the submitted AST.
- Name
not_rollback_eligible- Type
- 409
- Description
Only the latest finished, non-rolled-back transform can be rolled back; check each run's
rollback_eligible.
- Name
not_exportable- Type
- 409
- Description
Only query runs can be exported.
- Name
export_not_ready- Type
- 409
- Description
The export is still being written. Poll the run rather than retrying the download.
The query pipeline — POST /v1/lab/query, and the compile stage of transform preview and execute — carries {path, message, …} rows in error.errors pointing at the offending stage of the AST:
- Name
invalid_ast- Type
- 422
- Description
The AST failed validation — a malformed or unknown node. Also a request body that is not an AST object.
- Name
compile_error- Type
- 422
- Description
The AST is well-formed but could not be compiled to a query.
- Name
ambiguous_property- Type
- 422
- Description
A property name resolves to conflicting types across the selected ontologies. Reference it by id;
errors[].candidateslists the conflicting definitions.
- Name
too_many_groups- Type
- 422
- Description
A grouped query produced more groups than the cap. Group by a lower-cardinality property, or add filters.
- Name
bad_cursor- Type
- 422
- Description
The
ast.cursorkeyset token is malformed or stale — restart from the first page.
- Name
query_timeout- Type
- 408
- Description
Typed
api_error. The query exceeded the statement timeout — narrow the filter, reduce the page size, or use an export.
- Name
forbidden_ontology- Type
- 403
- Description
Typed
permission_error. A source ontology is not accessible. An id that does not exist and one belonging to another team are deliberately indistinguishable.
The DSL authoring tools answer with five codes of their own, all 422:
- Name
query_empty- Type
- 422
- Description
dsl_textis blank onPOST /v1/lab/dsl/parse.
- Name
parse_error- Type
- 422
- Description
The PassQL failed to parse.
error.errorscarries positioned rows —message,offset,length,expected.
- Name
unknown_ontology- Type
- 422
- Description
A
fromclause named an ontology that does not exist in this team.offsetandlengthare null.
- Name
ambiguous_ontology- Type
- 422
- Description
An ontology name matched more than one ontology;
errors[].expectedlists the candidate ids. Address it by id.
- Name
unprintable_ast- Type
- 422
- Description
dsl/printwas handed an AST it cannot render back to PassQL.
Codes inside errors
A rejected passport value — on a passport PATCH, a data request submit, or an import row — is reported per property. The envelope's code is value_invalid; look for these in errors[<property id>][].code, each beside property_id, property_name, message and raw. A failure in one translation of a translated value also carries language:
- Name
invalid_text / invalid_number / invalid_boolean- Type
- nested
- Description
The value is not of the property's type.
- Name
invalid_measurement / invalid_range / invalid_file- Type
- nested
- Description
The value is not a numeric measurement, a range with a numeric start, or a file reference.
- Name
invalid_date- Type
- nested
- Description
A
datevalue or cell is not a calendar day — impossible, out of range, or relative. See passports.
- Name
ambiguous_date- Type
- nested
- Description
A numeric
datelike03/04/2026reads both ways; sendYYYY-MM-DD. See passports.
- Name
number_out_of_range- Type
- nested
- Description
A number is outside the storable magnitude
1E-130–9.9999…E+125.
- Name
value_too_deep- Type
- nested
- Description
The value nests past the storage ceiling.
- Name
value_too_long- Type
- nested
- Description
A translation is over its type's cap — 255 characters for
short_text, 7,000 forlong_text, 50 tags of at most 64 characters. Carrieslanguage.
- Name
value_too_large- Type
- nested
- Description
The value is too large to store. On a passport
PATCHthis is reported as413 passport_too_largeinstead.
- Name
unsupported_language- Type
- nested
- Description
A translated value is keyed by something other than one of the 24 official EU language codes, in lowercase.
- Name
not_importable- Type
- nested
- Description
The property carries no writable value — a category divider or a computed composite.
Type-check each element before reading .code. Naming a property id the passport's ontology does not define yields plain message strings rather than detail objects, and one request can return both element types in the same map. A data request submit mixes them the same way: a missing answer, a file that is not your team's and a property the request never asked for arrive as strings, while a value the definition refuses is a detail object. Treat a string element as a message and an object as a detail.
Over-deep template structures and composition layouts are caught by request validation before they reach storage, so they arrive as 400 parameter_invalid with the depth explained in errors — not as value_too_deep.
Handling guidance
Branch on type first, then code. Never parse message — it is meant for
humans and its wording can change without a version bump.
- 4xx — fix the request; retrying it unchanged will fail again. The two exceptions are a
409in-flight idempotency conflict and a429. - 429 — wait the number of seconds in
Retry-After, then retry. See Rate limits. - 5xx — retry with exponential backoff, reusing the same
Idempotency-Keyso a write can't be duplicated. One exception: a5xxfromPOST /v1/data_requestswith arecipientmeans the request and its pending supplier link already exist and only the invitation failed to send, so a retry is409 supplier_already_linked— find the request and resend the invitation on its link instead.
When something looks wrong on our side, log request_id alongside your own
correlation id, and include error_id if the response carried one. Those two
values let support identify the exact request and failure without asking you to
reproduce it.