Partner API
AI index
Launch contract: sandbox is synthetic and cannot charge or create a patient. Use only signed-onboarding origins and credentials.

Errors, retries, and idempotency

Every non-edge API error uses one closed envelope and every application-level response carries Chia-Request-Id:

{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "Too many requests.",
    "param": null,
    "request_id": "req_01K0ABCDEFGH",
    "retryable": true
  }
}

With the SDK, catch ChiaPartnerError. Record only status, code, retryable, param, and requestId. Its message is redacted, but treat it as operational text—not user copy.

try {
  await runtime.listCatalog({ ifNoneMatch: currentEtag });
} catch (error) {
  if (error instanceof ChiaPartnerError) {
    safeMetric("chia_partner_api_error", {
      status: error.status,
      code: error.code,
      retryable: error.retryable,
      requestId: error.requestId,
    });
  }
  throw error;
}

Never log request headers, option objects, response bodies, webhook bodies, credentials, authenticated asset URLs, or handoff URLs/fragments.

Retry decision table#

Status/code Retry? Action
400 cursor_expired restart traversal Discard starting_after, fetch the first settlement page, insert immutable release IDs idempotently, and reapply supersedes_id links
other 400, 415 no Fix request/content type; contract fields are closed
401 invalid_api_key no Check origin/key pairing and revocation/configuration; rotate if exposure is possible
403 insufficient_scope no Move the operation to the right isolated role; never substitute a key
404 catalog_asset_unavailable no Abort/purge candidate bytes and refetch catalog
409 idempotency_key_reused no Same key was used with a different test body; create a new logical operation
409 webhook_endpoint_unavailable no Fix registered endpoint readiness before a new test
429 rate_limit_exceeded yes Obey Retry-After, add jitter, reduce polling
500 internal_error, any 503 with retryable: true yes Bounded exponential backoff
opaque edge 502, 504 yes Treat as a transient transport failure; use the same bounded budget
network_error, request_timeout yes Retry within the same operation budget
asset_integrity_error no immediate byte reuse Abort/purge, alert, then refetch the catalog
unsupported_api_version, invalid_response no Stop publication and investigate signed release/config drift

The SDK defaults to at most three attempts and a ten-second timeout per attempt. It applies jitter and honors a valid integer Retry-After up to maxRetryDelayMs (30 seconds by default). If Chia asks for a longer wait, the SDK returns the error with retryAfterMs; move the operation to a durable queue instead of holding an application request open. Every operation accepts an AbortSignal. Keep the complete retry budget bounded.

The SDK rejects a sandbox/live key-prefix mismatch locally before sending a request. The API deliberately does not reveal whether an otherwise invalid key belongs to another environment: a direct integration receives 401 invalid_api_key, not an environment-enumerating error.

Catalog refreshes are operational, not interactive. During a retryable failure, continue serving only the last valid unexpired snapshot. A production finance connectivity retry can wait for the next isolated finance schedule. Mobile handoff should not wait on either API.

Idempotency#

GET catalog, asset, and settlement operations are read-only. Retry them normally; do not send Idempotency-Key.

POST /v2/handoff-sessions requires a caller-persisted random UUIDv4. Reuse it only for the byte-identical closed body after an uncertain transport outcome. A new affirmative action is a new logical operation and key. Never derive the key from, or add to the body, any user/account/device/contact/patient/order value. A definitive 201 returns a 15-minute URL; do not post again, persist the URL, or attempt a status read.

POST /v2/webhook_tests also requires a cryptographically random UUIDv4 Idempotency-Key. The SDK requires exactly one of these two caller-retained inputs:

  • idempotencyKey: generate and persist the UUIDv4 in your operation record before the first call; or
  • operationId: persist the UUIDv4 before the first call and configure the runtime client with a durable idempotencyStore that maps that operation and event type to one stable key.

Passing both inputs, neither input, or operationId without a configured durable store fails locally before any request. The SDK never creates an ephemeral fallback key that disappears when a request fails.

For either mode:

  • create one key for one logical event_type request;
  • retain the caller-owned key or operation ID before the first attempt;
  • reuse it for transport/time-out retries of the byte-identical body;
  • never reuse it for another event type or environment;
  • after a definitive 202, store the returned acceptance for that operation instead of posting again.
import { randomUUID } from "node:crypto";

const idempotencyKey = randomUUID();
await webhookTestOperations.insert({
  environment: "sandbox",
  eventType: "endpoint.test",
  idempotencyKey,
}); // durable commit before network I/O

const accepted = await runtime.createWebhookTest({
  eventType: "endpoint.test",
  idempotencyKey,
});
await webhookTestOperations.markAccepted(idempotencyKey);

The alternative operationId mode is useful when the runtime client already has a transactional or otherwise process-safe idempotencyStore. The store reuses one key for the tuple of environment, operation ID, and event type. An in-memory store is test-only and does not satisfy restart-safe delivery.

Idempotency of the API request does not replace webhook deduplication. Delivery remains at least once, so the receiver must commit a unique Chia-Webhook-Id boundary.

Rate limits#

Every application-level API response exposes:

  • RateLimit-Limit: request budget for the credential/partner window;
  • RateLimit-Remaining: budget remaining;
  • RateLimit-Reset: seconds until reset;
  • Retry-After: seconds to wait on 429.

An intermediary-generated 502 or 504 may omit all Chia headers and the JSON envelope. Preserve your own correlation ID and UTC timestamp; do not turn an unvalidated header into a trusted Chia request ID.

No fixed numeric allowance is part of this contract. Design around conditional catalog fetches, explicitly requested synthetic webhook tests, scheduled isolated finance connectivity checks, and coalesced refreshes rather than consuming the budget per user.

See Troubleshooting for symptom-level diagnosis and the safe escalation packet.