Ingramdomains.ingram.tech

Guides

.md

Errors & idempotency

The envelope

Every /api/v1 error is:

{ "error": "<machine_code>", "details": { "message": "human explanation", … } }

Branch on error — it is stable API contract. details.message is for humans and may change; other details fields are documented per code below.

Error catalog

CodeStatusMeaningRecovery
missing_token401No Authorization: Bearer headerSend the token
invalid_token401Token not recognizedCheck the value; mint a new token if lost
revoked_token401Token was revokedMint a new token
insufficient_scope403Token scope too narrow (e.g. read on a mutation)Use a write token
not_found404Domain not in your workspace's portfolio, or unknown routeCheck the domain and workspace
bad_request400Body/params failed validationFix per details
already_in_portfolio409The domain is already in your workspaceNothing to order — manage it under /domains/{name}
price_changed409confirm_price ≠ live priceRe-quote; retry with details.current_price if acceptable
renewal_in_progress409That renewal term is already being bought — by the auto-renew run or another renew call (details.state)Wait for it to complete; a later renew stacks a further term on top
insufficient_balance402Wallet can't cover the priceTop up, or pass payment_method / shared_payment_token; details.required and details.available are in cents
payment_failed402/422Pay-as-you-go charge declined402 = card declined (details.kind); fix the instrument and retry
email_taken409Signup email already registeredSign in / use the existing account
pending_at_registry409GET /domains/{name} on a domain you just registered or transferred in that the registry doesn't report yet (details.addedVia)Retry later — minutes for a registration, up to days for a transfer
no_hosted_zone409Zone operation on a domain with external nameserversManage DNS at the delegated provider, or switch to ns1/ns2.ingram.tech
delete_failed422Registry rejected the deleteSee details.message (often TLD policy)
registration_failed / renew_failed / transfer_failed422Registry rejected the operationdetails.message carries the registry's reason — often user-actionable (e.g. contact field policy)
internal500Unexpected registry/server errorRetry with the same Idempotency-Key

Registry mutations are asynchronous: a success response with registry status Pending means the registry accepted the operation and is processing it. The change (new expiry date, transferred status, …) becomes visible when the registry completes — usually seconds to minutes, up to days for transfers.

Idempotency

Every mutating route accepts an Idempotency-Key header (any unique string; a UUID is conventional):

-H 'idempotency-key: 0e8f3b7c-renew-example-com-2027'

Semantics:

  • The first request with a key executes; if it succeeds, its response is stored for about 24 hours.
  • A later request with the same key and body returns the stored response (with Idempotent-Replay: true) without re-executing — so retrying after a timeout, once the original has completed, can't double-charge or double-register. The same key with a different body is 409 idempotency_key_reused.
  • Errors are not stored, whatever their status: a retry after a 4xx or 5xx executes again. Failed operations don't commit side effects, so this is safe and a transient failure doesn't need a new key.
  • Two requests with the same key in flight at the same time are not serialized: both execute. A client timeout doesn't mean the original stopped, so before retrying a spending call after a timeout, check its effect (GET /domains/{name}) rather than resending blind.

The CLI generates a fresh key per mutation automatically. For direct API use, derive the key from your own operation identity (order id, task id) so your retries collapse naturally.