Skip to main content

Error Handling

Standard HTTP status codes, with a JSON body describing what went wrong.

Status codes

CodeMeaning
200Success
201Created
400Invalid parameters or body
401No valid credential — missing, malformed, expired or revoked
403Authenticated, but not allowed: a scope your key lacks, or an organization you are not in
404Not found, or not visible to you
409Conflict — it already exists
429Rate limited
500Server error

Error shape

{
"statusCode": 401,
"message": "Unauthorized",
"error": "Unauthorized"
}

Validation failures return message as an array, one entry per field:

{
"statusCode": 400,
"message": [
"email must be an email",
"url must be a URL address"
],
"error": "Bad Request"
}

Handle both shapes — message is a string on most errors and an array on validation errors. Assuming a string and concatenating it produces a useless log line exactly when you need a good one.

The failure with no error

The one that costs the most time does not produce an error at all.

Validation runs with whitelist: true, so a body field the endpoint does not recognise is silently discarded and the request succeeds without it. Post address where the endpoint wanted addressLine1 and you get 201 for a project with no address. There is no warning, and nothing in the response says a field was dropped.

Check names against the API Reference — or use vremly_describe_endpoint if an assistant is writing the call.

Common cases

401 on every request

The credential is not being read. With an API key the header is x-api-key; with a token it is Authorization: Bearer <token>. If you send both, the Bearer token wins — which fails if that token has expired while the key is fine.

403 naming a scope

{
"statusCode": 403,
"message": "API key missing required scope. Needs one of: WRITE. This key holds: READ."
}

A credential problem, not a permissions bug. Issue a new key with the scope named — scopes cannot be added to an existing key.

403 on an organization

The organization exists but you are not a member. Note that omitting x-org-id does not cause this — the server falls back to your personal organization and the request succeeds against the wrong one. See Organization Context.

404 that should be 200

Either the id is wrong, or it belongs to another organization. An API key is locked to one organization, so an id copied from a different workspace reads as missing rather than forbidden — deliberately, since a 403 would confirm the record exists.

429

Three windows apply at once: 3/second, 20/10 seconds, 100/minute. The burst limit catches most callers first. Respect Retry-After rather than retrying immediately. See Rate Limits.

Retrying

Retry 429 and 5xx, with backoff. Do not retry 4xx — the request will fail the same way until you change it.

For anything that creates a record, send an Idempotency-Key header so a retry after a timeout does not create a second one.