Errors
The single error envelope, the full ApiErrorCode table, and what each stable code means and how to handle it.
Every non-2xx response uses one envelope:
{
"error": {
"code": "invalid_field",
"message": "external_submission_id must not be empty",
"param": "external_submission_id",
"request_id": "req_9b2cf1f4-6f6e-4a7e-8f8e-2d5c7a1b0c3d"
}
}
code— a stable, machine-readable identifier. Branch on this, never onmessage.message— human-readable, not stable; for logs and humans, not for control flow.param— present for field-level errors; names the offending request field.request_id— identical to thex-request-idresponse header; quote it in support requests.
Caution
New codes may be added at any time. Treat an unknown code by its HTTP status class (e.g. any
4xx you don't recognize is a client error you shouldn't blindly retry; any 5xx is a server
error that may be retryable). Never hard-fail on seeing a code you don't know.
Error code table
| code | status | retryable | meaning |
|---|---|---|---|
invalid_request | 400 | no | the request is malformed |
invalid_field | 422 | no | a field failed validation (param names it) |
invalid_api_key | 401 | no | key missing, malformed, or revoked |
api_client_disabled | 403 | no | the API client is disabled |
permission_denied | 403 | no | valid key, action not allowed |
not_found | 404 | no | resource absent or owned by another client |
partner_data_purge_in_progress | 409 | no | a data purge is running for this partner |
analysis_outdated | 409 | no | the evaluation was superseded |
revision_mismatch | 409 | yes (re-read) | the template changed since you read it |
evaluation_in_progress | 409 | yes (later) | an evaluation is queued or running |
submission_not_cancelable | 409 | no | the submission is already completed |
rate_limited | 429 | yes (backoff) | rate limit exceeded |
authz_not_applied_yet | 503 | yes (same key) | authorization is still propagating |
internal_error | 500 | yes (idempotent) | unexpected server error |
Code reference
invalid_request
400. The request is structurally malformed — bad JSON, a missing required body, a wrong content type. Fix the request shape; do not retry unchanged.
invalid_field
422. A specific field failed validation. error.param names the field (e.g.
external_submission_id, url, limit). Correct that field and resubmit.
invalid_api_key
401. The API key is missing, malformed, or revoked. This is about the credential, not the action. Fix or rotate the key; retrying the same request with the same key fails identically. See Authentication.
api_client_disabled
403. The API client itself has been disabled. Retrying will not help — contact AirCredit support.
permission_denied
403. The key is valid but the client is not permitted to perform this action. Check the client's configured permissions in the console.
not_found
404. The resource does not exist, or it belongs to a different API client (clients are fully isolated — you cannot see another client's resources). Also returned when deleting an already-deleted webhook endpoint.
partner_data_purge_in_progress
409. A data-deletion job is currently running for this partner, so the resource is temporarily in a conflicting state. Wait for the purge to finish before retrying.
analysis_outdated
409. The evaluation you referenced has been superseded by a newer run. Re-read the submission to
get the current latest_evaluation and use that.
revision_mismatch
409. A template write carried an expected_revision that no longer matches — someone else
changed the template since you read it. Retryable after reconciling: re-read the template, apply
your change to the fresh state, and retry with the new revision. See
Templates & customization.
evaluation_in_progress
409. The action conflicts with an evaluation that is currently queued or running — starting a second evaluation, or deleting/reassigning an upload mid-run. Wait for the evaluation to finish (or cancel the submission), then retry.
submission_not_cancelable
409. The submission is already completed and can no longer be canceled. Completed submissions
can still be deleted.
rate_limited
429. The API client exceeded its rate limit. Retryable — honor the retry-after response
header (seconds), then retry with backoff and jitter. Spread bulk work out rather than bursting.
authz_not_applied_yet
503. Authorization for a just-created resource is still propagating. Retryable — wait for
the retry-after header, then retry the request with the same external_submission_id /
external_file_id. Because writes are idempotent, the retry either completes the create or returns
the already-created resource. See the consistency note on
submission creation.
internal_error
500. An unexpected server error. Retryable for idempotent operations (all writes here are
idempotent on your external ids) — retry with backoff and the same external id. If it persists,
quote the request_id to support.
Handling errors in code
# The status line and the body's error.code both carry the classification:
curl -i -X POST https://api.aircredit.de/partner/v1/submissions \
-H "Authorization: Bearer $AIRCREDIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "external_submission_id": "" }'
# HTTP/1.1 422 Unprocessable Entity
# { "error": { "code": "invalid_field", "param": "external_submission_id", ... } }import { ApiError } from "@aircredit/partner-sdk";
try {
await client.createSubmission({ templateId, externalSubmissionId });
} catch (err) {
if (err instanceof ApiError) {
if (err.code === "rate_limited" || err.code === "authz_not_applied_yet") {
// retryable — back off and retry with the same external id
}
console.error(err.code, err.param, err.requestId);
}
}from aircredit_partner import ApiError
try:
client.create_submission(template_id=template_id, external_submission_id=external_id)
except ApiError as err:
if err.code in ("rate_limited", "authz_not_applied_yet"):
... # retryable — back off and retry with the same external id
print(err.code, err.param, err.request_id)use aircredit_partner::ApiError;
match client.create_submission().body(body).send().await {
Ok(resp) => { /* … */ }
Err(ApiError { code, param, request_id, .. }) => {
if matches!(code.as_str(), "rate_limited" | "authz_not_applied_yet") {
// retryable — back off and retry with the same external id
}
eprintln!("{code} {param:?} {request_id}");
}
}