Errors
The Auvral error format, every status code the API returns, and what to do about each one.
Every error response has the same shape:
{
"statusCode": 409,
"message": "Flow has no published version",
"code": "FLOW_NOT_PUBLISHED"
}| Field | Description |
|---|---|
statusCode | Repeats the HTTP status code. |
message | Human-readable. Written for a developer reading a log, not a stable machine-readable code. Never branch on this string. |
code | A stable machine-readable code for the condition. Present on every error the API returns except the 500. Branch on this. |
code is what you write your error handling against; message may be reworded at any
time. The field is absent where there is no code to give, so read it as optional.
A code that exists never changes meaning.
Successful responses use the same envelope with data in place of message:
{ "statusCode": 201, "data": { "sessionId": "..." } }The catalogue of POST /v1/sessions
400 Bad request
The body did not validate. These are bugs in your integration; retrying the same request will not help.
code | Cause |
|---|---|
MISSING_REQUIRED_FIELDS | flowId is missing or empty |
INVALID_REFERENCE_ID | referenceId was sent but is not a string, or is empty (or only whitespace, it is trimmed). Omit the field entirely to let Auvral assign one |
REFERENCE_ID_TOO_LONG | referenceId is above 128 characters. Shorten it, or hash your identifier |
INVALID_METADATA | metadata breaks one of its limits: at most 50 keys, keys of 1 to 40 characters, string values of at most 500 characters, no nesting. The message names the rule that failed |
INVALID_EXPIRES_IN | expiresIn is not an integer, not positive, or above 86400 |
INVALID_ALLOWED_ORIGINS | An entry of allowedOrigins is not an http/https origin or "*": a missing scheme, a path, a query, a trailing slash, an uppercase host. It also covers "*" sent alongside a concrete origin, more than 20 entries, and an entry above 255 characters. The message names the offending entry and the canonical form that would have been accepted |
INVALID_RETURN_URL | returnUrl is not an absolute http or https URL, or is above 2048 characters. A relative path, a bare host and any other scheme (javascript:, data:) are all refused |
INVALID_FLOW_VERSION | flowVersion is neither published nor draft |
DRAFT_REQUIRES_SANDBOX_KEY | flowVersion: "draft" was sent with a live key. Drafts are testable in sandbox only |
401 Unauthorized
{ "statusCode": 401, "message": "Invalid api key", "code": "INVALID_API_KEY" }Always this exact body, whatever went wrong: missing, malformed, unknown, revoked, or used against the wrong environment. Work down the checklist in Authentication.
403 Forbidden
code | Cause |
|---|---|
PLAN_EXHAUSTED | Your plan has no live verifications left, or its free window has ended. The message says which of the two it was |
Only live keys are refused: sandbox verifications are not counted and never run out, so your integration can keep being tested while the plan is exhausted. Nothing was created, and the same request works once the plan is lifted. Write to sales@auvral.com, or read the plan on the My plan page of the dashboard.
404 Not found
code | Cause |
|---|---|
FLOW_NOT_FOUND | The flowId does not exist, or it belongs to a different organization than your API key |
FLOW_DRAFT_NOT_FOUND | You asked for flowVersion: "draft" and the flow has no unpublished changes |
The most common cause of FLOW_NOT_FOUND is environment crossover: a sandbox flow id sent
with a live key, or the reverse. Flow ids are not shared between environments.
409 Conflict
The flow exists, and it cannot run right now. Both of these are fixed in the dashboard, not in your request.
code | Cause |
|---|---|
FLOW_NOT_PUBLISHED | The flow has never been published, so there is no published version to run. Publish it, or ask for flowVersion: "draft" with a sandbox key |
FLOW_NOT_ACTIVE | The flow was deactivated. It accepts no new sessions with either key kind until it is reactivated |
Sessions you already created are unaffected: each one pins the flow version it opened on, and runs it to the end whatever happens to the flow afterwards.
500 Server error
{ "statusCode": 500, "message": "Failed to create session" }Something went wrong on Auvral's side, and it carries no code: a server error is not a
condition to branch on. Retry with backoff. If it persists, get in touch and include the
timestamp and the referenceId you sent.
The catalogue of the reads
Both reads answer one 404 for every reason an id is not yours to see, and they never say
which reason it was.
GET /v1/sessions/{sessionId}
code | Status | Cause |
|---|---|---|
SESSION_NOT_FOUND | 404 | No session with this id belongs to your key: it never existed, it belongs to another organization, it was created in the other environment, or it expired without your user ever starting it |
INVALID_API_KEY | 401 | Same body as everywhere else |
A session that expired after your user started it is not a 404: it answers 200
with the verdict, rebuilt from the verification it produced. Only a session that left
nothing behind disappears.
GET /v1/verifications/{id}
code | Status | Cause |
|---|---|---|
VERIFICATION_NOT_FOUND | 404 | No verification with this id belongs to your key: unknown id, another organization, or the other environment |
INVALID_API_KEY | 401 | Same body as everywhere else |
Environment crossover is the common cause here too: a sandbox verificationId read with a
live key is a 404, and the reverse as well.
Retrying
| Status | Retry? |
|---|---|
| 400 | No, fix the request |
| 401 | No, fix the credential |
| 403 | No, the plan has to be lifted first |
| 404 | No, fix the id |
| 409 | No, fix the flow in the dashboard |
| 500 | Yes, with exponential backoff |
POST /v1/sessions is not idempotent: referenceId names the person, not the call, so it is not an idempotency key, and every successful call creates a new session.
A retry after a call that actually succeeded leaves an unused session behind, which is harmless because it expires on its own, but do not retry unless you are confident the first call did not succeed.
Errors that are not API errors
A rejected verification is not an error. verification.failed arrives with a 200
from your endpoint and means the checks did not pass. See
Event reference.
SDK errors are separate. onError({ code, message }) reports integration problems in
the browser (missing_url, already_open) and never carries a verification outcome. See
SDK reference.
An expired URL fails in the browser, not in your API calls: your user sees an expired message and your backend is not notified. Recovery is a new session.