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 on message.
  • 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 the x-request-id response 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

codestatusretryablemeaning
invalid_request400nothe request is malformed
invalid_field422noa field failed validation (param names it)
invalid_api_key401nokey missing, malformed, or revoked
api_client_disabled403nothe API client is disabled
permission_denied403novalid key, action not allowed
not_found404noresource absent or owned by another client
partner_data_purge_in_progress409noa data purge is running for this partner
analysis_outdated409nothe evaluation was superseded
revision_mismatch409yes (re-read)the template changed since you read it
evaluation_in_progress409yes (later)an evaluation is queued or running
submission_not_cancelable409nothe submission is already completed
rate_limited429yes (backoff)rate limit exceeded
authz_not_applied_yet503yes (same key)authorization is still propagating
internal_error500yes (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}");
    }
}

On this page