API reference

Error codes

Every code the ArbiterQA API returns, with its HTTP status and meaning. Codes are stable — handle them by code, not by message text.

CodeStatusMeaning
AI_MISTAKE_NOTES_REQUIRED400Provide aiMistakeNotes when humanVerdict differs from the stored AI status.
CLIENT_SELECTION_NOT_IN_JOB400clients was supplied for a validation id that no email line in this job selects. details.validationId names it. Select the validation on the job, or remove its clients entry. Refused before pricing, so nothing is charged and no job is created.
CLIENT_SELECTION_RENDERLESS_CHECK400clients was supplied for a functional validation. Functional checks read the MIME, which is identical across mail clients, so they render nothing and accept no client selection. details.validationId names it. Remove the clients entry. Refused before pricing, so nothing is charged and no job is created.
CONFLICTING_PARAMETERS400Two validations[] entries set the same parameter name to values that differ. A job carries one flat parameter map, so per-validation values for the same name cannot be expressed. Align the values or drop one. details.path points at the second entry.
DUPLICATE_VALIDATION400The same validation id appears more than once in validations[] with configurations that are not identical. Identical duplicates are collapsed automatically; differing ones are refused so neither is silently dropped. details.path names the repeat entry.
EMPTY_CLIENT_SELECTION400clients was supplied as an empty object. "Render nothing" is expressed by not selecting the validation, not by an empty map. details.validationId names the entry. Add viewport keys with client ids (or an empty array for the native default), or remove the entry. Refused before pricing, so nothing is charged and no job is created.
FEEDBACK_ANCHOR_REQUIRED400This set anchors to real work: validation-scope sets require jobId and validationId; a per-standard set requires the jobId of a job that ran the standard. details.needs lists what is missing. Take the ids from your own job record (GET /api/jobs).
FEEDBACK_ANSWERS_INVALID400One or more answers fail their question's answer-shape schema, or the submission does not answer the set exactly (every question in the set answered once, nothing extra). details.problems lists each question with its complaints; GET /api/feedback/questions serves each question's schema.
FEEDBACK_RATIONALE_REQUIRED400Provide verdictComments or feedbackNotes when humanVerdict is fail or error.
INVALID_CAPTURE_SIZE400Use one of the supported capture size IDs: mobile-sm, tablet-portrait, or desktop-md.
INVALID_CLIENT_SELECTION400clients on a validation entry must be an object keyed by a known viewport, each holding an array of client id strings. details.path names the offending key. Client id validity and plan gating are enforced separately.
INVALID_ENUM_VALUE400Use one of the allowed values listed in details.allowedValues.
INVALID_PARAMETER_VALUE400Check the submitted value against the Parameter format requirements returned in details.
INVALID_REQUEST_BODY400Check the request schema, required fields, content type, and JSON formatting.
JOB_ACCEPTS_ONE_SUBJECT400POST /api/jobs and POST /api/jobs/estimate accept exactly one entry in validations; details.subjectCount reports how many were sent. Send one request per subject and each returns its own jobId. Refused before pricing, so nothing is charged and no job is created.
JOB_SUBJECT_REQUIRED400Supply url on a url line, or html and baseUrl on a static_html line. details names the line and the missing field. Refused before pricing, so nothing is charged and no job is created.
LEGACY_REQUEST_SHAPE400The legacy validations line-array body is no longer accepted. Send the unified job request: top-level type ("url" | "static_html" | "email") with its subject fields (url, or html + baseUrl, or subject), validations[] as string ids or { id, severity, parameters, clients } objects, and optional validationSetId. details.hint maps each legacy field this body used to its unified home. See docs/api/API-REFERENCE.md (POST /api/jobs).
MISSING_REQUIRED_FIELD400Add the missing field listed in details.field and retry.
NO_VALIDATIONS_SELECTED400Add at least one validation id in validations[], or name a validationSetId from GET /api/validation-sets.
ONE_VALIDATION_SET_PER_JOB400Send a single validationSetId string. A subject that needs a different set is a separate job.
PLAN_EXCLUDES_SELECTED_CHECKS400After plan gating, no selected validation remains runnable — a job that would do nothing is refused rather than accepted at a zero quote. details.gatedValidationIds lists what was excluded and details.missingFeatures the plan features that would unlock them. Nothing was charged and no job was created.
PLAYGROUND_INVALID_URL400Provide a public http(s) URL. Private, loopback, link-local, and internal addresses are rejected. See details.reason.
SIGNUP_INVALID_EMAIL400Provide a syntactically valid email address in the `email` field. It becomes the account owner, so a human must be able to sign in with it later.
UNKNOWN_EMAIL_CLIENT400A client id in clients does not exist in the rbtr render catalog at that form factor. details.validationId names the validation and details.unknownClients lists the (formFactor, clientId) pairs. GET /api/email-clients returns the current catalog. Refused before pricing, so nothing is charged and no job is created.
UNKNOWN_FIELD400Remove or correct the fields in details.unknownFields. The accepted top-level fields are listed in the error message; the most common cause is a typo on a field that matters (e.g. "validation" for "validations").
VALIDATION_NOT_RUNNABLE400Use an id from GET /api/validations where runnable is true. An id we run but do not list is refused too, so that it can never run unpriced.
VALIDATION_SET_CONFLICT400Send validationSetId, or an explicit validations[] list, not both with an internal validationSets array. Extra checks belong in validations[].
VALIDATION_SET_NOT_FOUND400Call GET /api/validation-sets (or GET /api/validation-sets/:setId) for published ids, or name checks in validations[].
VALIDATION_SET_SUBJECT_MISMATCH400validationSetId must match the job type: url and static_html jobs take url sets; email jobs take email sets. details.setSubjectType and details.jobSubjectType name the mismatch.
VALIDATION_SUBJECT_MISMATCH400Every named validation must match the job type: url/static_html jobs take url-subject checks, email jobs take email-subject checks. details.mismatches names each offender with its subject type. Pick ids from GET /api/validations whose subjectType matches your job.
VALIDATION_TYPE_UNSUPPORTED400Use a supported validation type, such as url or static_html.
AUTH_REQUIRED401Sign in and retry the request with a valid session.
INSUFFICIENT_CREDITS402The organization does not hold enough credits for this job. Nothing was charged and no job was created. See details.balance, details.required and details.shortfall; quote first with POST /api/jobs/estimate.
ADMIN_REQUIRED403Authenticate as an admin user before calling this endpoint.
CUSTOMER_SCOPE_MISMATCH403Use the authenticated customer ID, or sign in as the correct customer.
INSUFFICIENT_SCOPE403The API key lacks one or more required scopes. Mint a new key with the needed scopes (scopes are fixed at creation) and retry.
PLAN_EXCLUDES_EMAIL_CHECKS403The organization's plan does not include email checks. details.feature names the plan feature that unlocks them; upgrade the plan in the billing portal and retry. Nothing was charged and no job was created.
PLAN_EXCLUDES_REQUESTED_CLIENTS403An explicitly named (formFactor, clientId) pair in clients is outside the organization's plan grants. Explicit requests are refused, never silently narrowed. details.validationId names the validation, details.refusedClients the pairs, and details.missingFeatures the plan feature(s) that unlock them. Nothing was charged and no job was created.
PLAN_EXCLUDES_WEBSITE_CHECKS403The organization's plan does not include website checks. details.feature names the plan feature that unlocks them; upgrade the plan in the billing portal and retry. Nothing was charged and no job was created.
UNAUTHORIZED_DOMAIN403Sign in with an email address on an invited domain. There is no self-serve allowlist to change: domain restriction is enforced by the identity provider for this deployment, so an unexpected refusal here is a support question.
CUSTOMER_NOT_FOUND404No organization matches that customerId. Omit it to act as the organization your credential belongs to — that is the default, and naming a different tenant is refused as CUSTOMER_SCOPE_MISMATCH rather than answered.
FEEDBACK_CHECK_NOT_IN_JOB404No result row exists for this validationId on this jobId (with the given capture identity, when supplied). Feedback anchors to a check that actually ran — take jobId and validationId from the job's own result rows.
FEEDBACK_NOT_FOUND404Record feedback first, or treat this result as pending review.
FEEDBACK_SET_UNKNOWN404No active feedback set has this id (for looks-wrong feedback, no active set serves this verdict for this audience). GET /api/feedback/questions returns the set currently being asked, with its id.
JOB_NOT_FOUND404Verify the jobId and customer scope.
PLAYGROUND_RUN_NOT_FOUND404Unknown or expired playground run id. Runs are kept in memory for a limited time (PLAYGROUND_RUN_TTL_MINUTES).
STORAGE_OBJECT_NOT_FOUND404Check customerId, jobId, and storage key. Legacy jobs may use _screenshot.
VALIDATION_NOT_FOUND404Use GET /api/validations for offered ids, or a published content slug from /validations/:slug/standard.
ACCOUNT_EXISTS409A tenant already exists for this email address. POST /api/signup only provisions NEW accounts and never returns a credential for an existing one. Sign in at /auth/login and use POST /api/keys, or `npx arbiterqa login` for a CLI key.
FEEDBACK_ALREADY_GIVEN409This user already completed this set (or already filed this looks-wrong set for this job and check). Feedback is append-only: the original submission stands and was paid at most once. There is nothing to retry.
FEEDBACK_NOT_DUE409The set is not currently due for this credential. Job-scope sets are served one at a time by GET /api/feedback/questions — submit the set that endpoint returns. For validation scope, the set must match the result row's verdict (details.expectedSetId when known).
JOB_CHARGE_CONFLICT409A credit_ledger charge already exists for this job id under a different tenant or a different amount. Job ids are minted server-side and are not accepted in the request body, so this should be unreachable — treat it as an invariant violation and check the logs for billing.charge_conflict. Nothing was charged and no job was created.
PLAYGROUND_LIMIT_REACHED429The anonymous session used all its free playground runs. Sign up for a free account, or authenticate with an API key and use POST /api/jobs.
PLAYGROUND_RATE_LIMITED429Too many playground runs from this IP in the current window. Wait for the window to pass, or sign up and use the authenticated API.
INTERNAL_ERROR500Retry the request. If it persists, contact support with the requestId.
JOB_EXECUTION_FAILED500The job failed after it was accepted and charged. Settlement refunds undelivered captures and any errored result rows, so a partial run is not billed in full. Retry if retryable is true; otherwise contact support with the requestId and jobId.
BILLING_NOT_PROVISIONED503The customer row has no Kinde billing customer, or its billing customer holds no live agreement. Signup provisions both, so this org predates billing or was provisioned outside it. Repair by reprovisioning the org (scripts/reprovision-kinde-orgs.ts); no default plan is substituted.
BILLING_PROVIDER_UNREACHABLE503The Kinde Management API or the synced plan catalog could not be read. No plan data is assumed during the outage — every request needing the plan fails until Kinde answers. Check Kinde status, credentials, and network egress; a cached resolution (≤5 min) is still served where one exists.
EMAIL_CLIENT_CATALOG_UNAVAILABLE503The rbtr client catalog is unreachable and nothing is cached, so client ids in clients cannot be validated. Refusing beats skipping: an unvetted id would fail at capture time, after the charge. Retry, or resubmit without per-validation clients. Nothing was charged and no job was created.
PLAN_NOT_PUBLISHED503The customer's Kinde agreement is missing a usable credits entitlement (absent, ≤0, or the 2147483647 unlimited sentinel on a paid plan) or the plan code is not one we grant for. Fix the plan's credits meter in the Kinde dashboard; Free ignores that field and uses FREE_MONTHLY_CREDITS. Requests needing the plan fail until the entitlement is set.
SERVICE_UNAVAILABLE503A dependency this endpoint requires is unconfigured or unreachable, so the request was refused before any work or billing. This is our deployment, not your request: the body is unchanged and retrying is safe. Call sites override this message with the specific dependency; the request log carries it either way.