Skip to content
Hightop docs header art
Hightop
API and Integrations

Going to Production#

Production write endpoints move real funds on Base. Treat every mutation as a real money movement. This guide collects the patterns that keep an integration correct under retries, timeouts, and partial failures, regardless of which surface you use.

Idempotency by transport#

Every mutating endpoint requires an idempotency key. Use one key per logical operation, and reuse the same key only when retrying the same request with the same body. Reusing a key with a different body returns idempotency_key_reuse_mismatch.

How each surface supplies the key:

SurfaceHow the key is provided
Raw HTTPIdempotency-Key header on the request — you generate it
SDKidempotencyKey option; generate with createIdempotencyKey(prefix?)
CLIAuto-generated and printed in the response, or pass --idempotency-key
MCPRequired idempotency_key argument on mutating tools — the client must supply it; Hightop does not synthesize one

When a request did create a record, a retry with the same key and body replays that stored response (within 24h, with X-Idempotency-Replayed: true) instead of creating a second operation — see Conventions. When the outcome is unknown (a timeout) or the request was rejected before it ran (a 429, which creates no record), the same key and body simply guarantees at most one operation: it replays if the first attempt landed, or runs once if it didn't. Either way, retrying with the same key is safe.

typescript
example
const key = createIdempotencyKey('payout')
// First attempt times out (no response received)
// Retry with the SAME key and SAME body — at most one payment is created
await client.request('POST /v1/agent/one-off-payments', body, { idempotencyKey: key })

Finalize every operation#

A successful write response does not mean the money has moved — it means the operation was accepted. Operation-backed (money-movement) writes return an operation you must poll to a terminal status; quote/simulate POSTs and webhook-management writes return their object directly. Check the generated endpoint metadata for a given endpoint's behavior.

Operation statuses:

text
example
accepted        non-terminal — queued
submitted       non-terminal — broadcast onchain
executed        terminal — success
execution_failed   terminal — failed onchain
policy_rejected    terminal — blocked by your rules
cancelled       terminal — cancelled

Poll until terminal, then branch on the result. The SDK's operations.wait() throws HightopAgentOperationWaitTimeoutError if the deadline passes before a terminal status, so wrap it:

typescript
example
import { HightopAgentOperationWaitTimeoutError } from '@hightop/sdk'
 
try {
  const final = await client.operations.wait(op.operation_id, { timeoutMs: 60_000 })
  if (final.operation.status !== 'executed') {
    // execution_failed / policy_rejected / cancelled — inspect and surface, do NOT blindly retry
  }
} catch (error) {
  if (error instanceof HightopAgentOperationWaitTimeoutError) {
    // Not a failure: the operation may still settle. Re-poll by id later rather than re-submitting.
  } else {
    throw error
  }
}

The CLI equivalent is --wait; over MCP use agent_operations_wait_for_status. See Operations and Lifecycle.

Retry strategy#

Retry only what is safe to retry, and always with the original idempotency key:

  • Network error / client timeout (request_timeout) — retry the same key and body. The first attempt may or may not have landed; idempotency makes a duplicate impossible.
  • 429 rate_limited — wait at least Retry-After, add jitter, then retry. See Rate Limits.
  • 503 agent_api_cutover_in_progress / agent_api_chain_unavailable — transient; back off and retry.
  • 400 validation_failed — do not retry unchanged; fix the request.
  • 403 policy errors — do not retry unchanged; the request is blocked by configuration (see below).

Use capped exponential backoff with jitter, and a maximum attempt budget. Never retry a different body under the same key.

Troubleshooting by status#

401 — authentication

CodeMeaningFix
authentication_failedBad or missing credentialsCheck x-agent-id/x-api-key or bearer token and base URL
agent_disabledAgent turned offRe-enable the agent in the app
agent_not_yet_activeBefore the agent's active windowWait for active_window.starts_at
agent_expiredAfter the agent's active windowIssue a new agent or extend the window

403 — authorization and policy

CodeMeaning
insufficient_scopeOAuth token lacks the scope this route needs — request the scope (OAuth)
permission_not_grantedThe agent's permissions don't allow this action
asset_not_allowed / recipient_not_allowed / protocol_not_allowedThe target is outside the agent's allowlists
limit_exceededA spending or rate limit on the agent was hit
ltv_too_highBorrow action would exceed the allowed loan-to-value
owner_only_action / identity_gated_actionAction is reserved for the app/owner, not an agent

403 policy errors are working as designed — your rules blocked the action. Surface them; do not retry unchanged. See Permissions and Limits and If an Agent Goes Off-Script.

429 — rate limited. Back off per Retry-After. See Rate Limits.

400 — validation_failed. Fix the request shape; do not retry unchanged.

400 — signature_expired / signature_invalid_*. x402/signature-specific: re-sign the authorization and retry.

409 — quote_expired / quote_already_consumed. Conversion quotes are short-lived and single-use; fetch a fresh quote and submit again.

503 — transient. Back off and retry.

The full list is in Errors.

Pre-launch checklist#

  • One idempotency key per logical write, reused only for retries of the same body.
  • Every operation-backed write polled to a terminal operation status before you report success.
  • Backoff with jitter on 429 and 503; no retry-unchanged on 400/403.
  • Credentials in a secret store, never in source; one auth mode per client.
  • Webhooks registered and HMAC-verified for async settlement instead of tight polling.
  • Errors logged with code and request_id for support.
  • Validate request shapes with --simulate / client.simulate before going live.

Next#

Previous

Emerging Payment Protocols

Next

API Reference