Runtime, errors and security

This page defines the integration control plane shared by every capability. Exact operation-specific headers, schemas, status codes and error examples remain in OpenAPI.

Base URL and authentication

Keep API keys in a server-side secret store. Never place them in a browser bundle, mobile-app binary, redirect URL, query parameter, log, analytics event, screenshot, support ticket or documentation console.

Use a separate Client and key when customer, application, quota, usage or billing isolation is required. A Tenant in the customer system is not an API field.

Minimal client policy

Every production adapter should have one shared response wrapper that:

  1. records the operation ID, HTTP method and path template without recording secrets;
  2. captures HTTP status and the documented operational headers;
  3. parses JSON only when the response media type and operation declare it;
  4. on non-2xx, branches on the stable top-level error code and never on message;
  5. on a job-status 200, branches again on the job's domain status;
  6. rejects a payload that does not satisfy the pinned OpenAPI schema;
  7. returns a typed domain result to the calling ERP, DMS, shop or worker.

Do not build each endpoint as an unrelated HTTP call. Authentication, retry, idempotency, async state, tenant isolation and observability must be consistent across the adapter.

Response decision matrix

HTTP outcome Meaning Client action
2xx other than 202 Transport-level success Validate the operation-specific success schema. For status resources, still inspect the embedded domain state.
202 Work accepted, result not final Validate and persist the workflow-specific full statusUrl, jobId, Retry-After, input correlation and next-poll time. Capture Location only for comparison; quarantine a mismatch. Poll statusUrl; do not resubmit the order.
400 Request is invalid or operation-specific processing rejected it as documented Branch on error. Correct the request or route it to review; retry only when the global error catalogue explicitly marks that code retryable.
401 API key missing Fix secret injection. Do not retry unchanged in a loop.
402 Billing or sponsorship cannot cover the call Stop the business operation and resolve funding/terms.
403 Key invalid, endpoint not enabled, rights missing, or sandbox operation unavailable Stop and resolve access; do not convert it to “not found”.
404 Selected resource or documented business match not found Handle as an operation-specific negative outcome. Do not try a fabricated alternate path.
409 Request-state or idempotency conflict Branch on error; only the explicitly retryable in-progress condition may be repeated unchanged after Retry-After.
413 / 415 Upload too large or media type unsupported where documented Reject or transform input before a new request.
422 Semantically unprocessable or ambiguous input for operations that declare it Route to review or request better input; do not guess a candidate.
429 Rate limit or one-active-request rule Read error and Retry-After; serialize per Client and delay retry.
5xx Temporary API failure Consult the global error catalogue's retryable flag. Preserve the same idempotency key for an ambiguous write.

The same error code can require evaluation together with HTTP status. Apply only the retry behaviour documented for the selected operation and response status.

In a successful base-part response, part.name is a string or null when available. Preserve the returned value, and do not reconstruct a missing name from another field. fitment contains the available type-level assignments and can be empty.

Error object

The common non-2xx body is:

{
  "error": "stable_machine_code",
  "message": "Human-readable explanation"
}

Only error is a program key. message can be localized or reworded. The OpenAPI contract documents retry behaviour and operation-specific schemas and examples determine which codes are evidenced on each status; extra response fields remain governed by that response schema.

Do not assume that the global catalogue alone proves that every error applies to every operation. Use only errors documented for the selected operation and status.

Asynchronous jobs

9 public operations can return 202; they form 6 documented job families. The two Intelligence status operations themselves return 202 while work remains pending and therefore count as operations with a 202, not as additional job families.

The two Intelligence job families are conditional pilot contracts. Adaptive part refresh and VIN Economic Intelligence are currently disabled in production; their initiating routes return the documented error branches when work would have to be started. Apply the polling rows below only after the corresponding feature has been enabled or to a job that was already accepted.

Order operation Status operation Important distinction
GET /vin/{vin}/parts GET /vin/parts/jobs/{jobId} The order is a GET but may still return 202; do not create a second job by changing lookup context.
POST /vin/cart-check GET /vin/cart-check/jobs/{jobId} Use the returned jobId and statusUrl only with this status operation.
GET /vin/{vin}/economic-evaluation GET /vin/economic-evaluation/jobs/{jobId} Preserve the input assumptions used for the evaluation.
POST /vision/part/generate GET /vision/part/generation-jobs/{jobId} Preserve synthetic/disclosure metadata on the resulting assets.
GET /parts/intelligence or GET /parts/clusters/{clusterId}/intelligence GET /parts/intelligence/jobs/{jobId} Both orders share the part-refresh status family. Preserve the resolved cluster, version, market scope and original idempotency key; never poll the history route.
POST /vin/economic-intelligence GET /vin/economic-intelligence/jobs/{jobId} Preserve the VIN request dimensions. In the current pilot, accepted requestId and jobId are the same UUID and the final result repeats it as requestId and evaluationId.

GET /parts/clusters/{clusterId}/history has no asynchronous branch and never creates missing history.

Generic polling flow:

send order once
  -> final 2xx: validate and complete
  -> 202:
       validate the required absolute statusUrl against the documented status operation
       compare Location with statusUrl; quarantine a mismatch
       persist workflow family + jobId + nextPollAt
       wait at least Retry-After seconds
       GET statusUrl
         -> HTTP 202 queued/running where declared: update nextPollAt and wait
         -> HTTP 200 status resource: inspect its domain status
         -> succeeded/final result: require the workflow's usable result, or quarantine
         -> failed: require/map the workflow's embedded error, or quarantine

Polling the six documented status families belongs to the original order and does not create a second order. The current Intelligence pilot remains closed until approved positive pricing and atomic billing are enabled together; no real result is released before then. Test every documented HTTP and domain-status branch against its schema.

Do not infer lifecycle behaviour that the selected operation does not document.

Idempotency

The support signal is the operation's declared Idempotency-Key header parameter. Do not infer support merely because the method is POST, PUT, PATCH or DELETE.

For an operation that declares the header:

  1. Generate one opaque key for one logical business action.
  2. Persist it before sending the request.
  3. Reuse the same key after a timeout or other ambiguous transport failure.
  4. Never reuse it for a different request.
  5. Keep the key scoped to the Client and business action in local storage.

The Intelligence resolver, current-cluster and history GET operations are explicit exceptions to the usual read convention: each requires Idempotency-Key. Their two status-polling GET operations accept only jobId and do not declare that header. Follow the selected operation rather than deriving idempotency from its HTTP method.

The central replay details below apply only where the selected operation declares the corresponding response headers and error codes. The four initiating Intelligence pilot operations document X-Tapinoma-Idempotent-Replay and 409 idempotency_key_conflict, but not X-Tapinoma-Idempotency-Stored, idempotency_request_in_progress, idempotency_result_unavailable or a 24-hour retention promise. Do not infer those central behaviours for an Intelligence request.

Documented behaviour:

The renamed scanner paths use compatibility aliases rather than HTTP redirects. Follow the documented request, response and idempotency behaviour for each alias pair:

For these three pairs, an identical body and key may be retried through either path and returns the same stored result without re-execution or a second charge. Calculation extraction is deliberately not an identical response pair: /scanner/document/calculation returns 12 top-level fields including equipment, while deprecated /scanner/document/extract retains the 11-field response without it. Reusing a key across those two response contracts returns HTTP 409; repeat an ambiguous request only on the same path contract.

The three key-issuing operations deliberately do not support idempotency:

Their success payload contains a new plaintext API key and cannot be repeated safely after an uncertain outcome. Enter an operation-specific reconciliation_required state and never repeat the write blindly. POST /client/users can use GET /client/users to determine whether a Client was created, but that list cannot recover the lost plaintext key. The public contract exposes no raw-key recovery for POST /client/users/{clientId}/keys and no partner-workspace lookup by externalReference; those two outcomes require an operator-approved recovery procedure.

Retry policy

Use the global error catalogue, HTTP status and operation contract together.

if final success:
  do not retry
else if 202:
  poll the validated full statusUrl; do not resend order
else if response.error is not marked retryable:
  stop or request corrected state/input
else if Retry-After exists:
  wait at least that duration
else:
  use bounded exponential backoff with jitter

for an ambiguous supported write:
  retry with the same Idempotency-Key
for an unsupported key-issuing write:
  enter operation-specific reconciliation_required
  use published list state only where it exists
  require operator-approved recovery when the public contract is insufficient
  never repeat blindly

The exact maximum attempt count and time budget are customer-system policies; the API contract does not publish universal values. Keep the total below the calling system's business deadline and surface exhaustion as an operational state.

Rate limits and concurrency

Rate limits attach to a Client and endpoint_key. Client-specific overrides can target one endpoint key or *. When no override exists, technical defaults apply, but the numeric defaults are not published.

Independent of numeric rate limits, only one API request may be active per Client. Another concurrent request is rejected with 429 client_request_in_progress. This is not a contract-partner-wide cap: separate Clients can run in parallel.

Recommended queue shape:

queue key = clientId (or local Client mapping)
concurrency per queue = 1
cross-Client workers = bounded by the partner's own resources and agreed limits
429 -> read error + Retry-After -> reschedule, do not busy-loop

When effective numeric defaults are not published, do not hard-code guessed requests-per-minute values. Use only limits supplied through an approved contract or documented mechanism.

Pagination

The contract's long-list pattern uses:

API 1.71.0 exposes this pattern on the OE aftermarket-reference list and the VDI catalogue. Without limit, the contract says the complete result is returned. Every page is a separate call and is billed as a call, so a complete unpaginated read remains cheapest when its size is operationally acceptable.

Use page.hasMore, not items.length === limit, as the continuation signal. Keep the same query semantics across pages and validate the selected operation's response schema.

Operational response headers

Read only headers declared by the selected operation. The global vocabulary includes:

Header Use
Location Status URL for accepted asynchronous work.
Retry-After Minimum seconds before retry or the next poll.
X-Tapinoma-Billing-Source Technical settlement decision: plan, balance, bundle, sandbox or idempotent_replay; this header alone never proves a debit or commercial entitlement.
X-Tapinoma-Usage-Warning Human text at high or exhausted plan usage; do not parse it as a code. Multiple warnings can be joined with `
X-Tapinoma-Billing-Bundle Monthly bundle associated with the request.
X-Tapinoma-Sponsorship-Grant Associated sponsorship reference; it does not prove this call caused a sponsor debit.
X-Tapinoma-Live-Available-At Earliest documented time for another live attempt.
X-Tapinoma-Idempotent-Replay Response came from a prior completed request.
X-Tapinoma-Idempotency-Stored First-success replay-storage result.

Header names are case-insensitive. Their values are not substitutes for validating the response body.

Billing-aware behaviour

Do not calculate customer invoices from a single response header. Reconcile usage and balance with GET /client/usage or the Master client view GET /client/users/{clientId}/usage.

Timeouts

Only an operation carrying x-client-timeout-seconds publishes a client timeout recommendation through the contract. API 1.71.0 declares 300 seconds for the part-composite, part-damage-transfer and identifier-redaction Vision operations. Do not apply this number globally.

For asynchronous operations, the synchronous request timeout and the job's total processing lifetime are different concepts. Follow the returned job URL and Retry-After until a documented terminal state is reached.

Response handling

File, image and document security

Redirect security

For POST /vin/redirect-sessions:

The contract says an empty state is treated as unset and omitted from the callback. It does not turn state into an authentication credential; the customer application remains responsible for securing its callback session.

Contract pinning and upgrades

Pin generated client types and contract tests to the published info.version. Validate responses against the selected schema: reject or quarantine additive fields when that schema is closed with additionalProperties: false, and accept them only where the schema explicitly permits additions. A transport layer may capture a redacted response for controlled diagnostics when the approved data policy permits it, but the business projection contains only documented fields. Fail validation when a required field, enum or response shape changes unexpectedly.

Do not infer semantic-version guarantees from the version string alone; review contract changes before automating upgrades.