Every owner intent and every agent action gets a correlation_id. Check its result with GET /v1/status, and read the error document when a call fails.
At a glance
  • Poll until final: SUCCESS, REFUNDED or FAILED. wait_ms (up to 30 s) holds the answer until something changes.
  • Retry a write with the same Idempotency-Key and the same body. A new key is new work.
  • Outcome not yet proven (UNCERTAIN)? Keep polling the same correlation_id. Never send the action again under a new key.
  • Errors: branch on errors[0].code, never on the text. layer says whose rule refused. Unknown codes fall back to the HTTP status.

Check a status

GET /v1/status reads the status of any correlation_id (intent_… for owner intents, op_… for executions and deposits). It needs your partner API key.
wait_ms returns as soon as the status is final, needs a signature (PENDING_SIGNATURE) or needs review (NEEDS_REVIEW). Otherwise it returns the current status when the wait ends. A non-final answer is normal: call again with the same correlation_id. Each check also reconciles the status with chain and provider state, so you never need a separate refresh to keep it current. At most 10 wait_ms waits run at the same time per API key. Further ones answer at once, as plain reads. Poll in a loop:

Status values

SUCCESS appears only with proof: an intent hash on NEAR Intents, the transaction on the destination chain for a withdrawal to another chain, or the settled transaction of a native NEAR transfer or call. A sent transaction alone does not count as success. Owner intents move through PENDING_SIGNATURE, PROCESSING, SUCCESS and FAILED.

Status fields

Execution details (swap, withdraw, transfer, shield, unshield, deposit, agent_delete) can hold these fields. Only the ones that apply are present. Owner intent details: agent_create, policy_update, agent_freeze and agent_unfreeze carry revision, policy_hash and transaction_hash (agent_create also agent_id). grant_issue carries grant, grant_revoke carries grant_id, committed_correlation_ids and committed_truncated, execution_cancel carries cancelled_correlation_id, agent_archive and agent_restore carry archived, and approval_vote carries approval_id and verdict. native details say what was asked of the agent’s own key: method, chain, account_id, target, amount, method_name, primary_type, domain, message_json, request_sha256, signature, tx_hash, provider_request_id, provider_status and failure_reason. See Native Accounts.

List an agent’s history

GET /v1/agents/{agent_id}/history lists everything an agent has done, newest first, one status object per entry. Every deposit lookup issues a new address, so history leaves out deposit addresses nothing was paid into (still open, expired, or closed by a deletion). Add include_unpaid=true to list them too. Each one stays readable by its correlation_id through GET /v1/status, and an address paid in part is always listed.
The response is { "data": [...], "next_cursor": "..." }. next_cursor is null on the last page. History entries are shown as stored. They are not reconciled with chain and provider state, so an entry can lag behind the real outcome. Track one item with GET /v1/status instead.

Idempotency

An Idempotency-Key is a name you give one write, so a retry cannot run it twice. Every execution, deposit and /recover call needs one, and a missing key returns idempotency_key_required (400). generate-intent accepts an optional one. A key is 8 to 128 characters of A-Za-z0-9._:-. A UUID works well.
  • Retry with the same key and the same body. You get the first result back, with the same correlation_id.
  • The same key with a different body fails with idempotency_conflict (409). Reuse the original body, or use a new key for new work.
  • Keys are scoped to the grant. The same key under two grants is two separate actions.
  • Recorded refusals are remembered. When the API refused an action it had already admitted, for example with wallet_busy, the same key returns the same FAILED result. Such an error never carries meta.retryable. After the cause is gone, send the work again under a new key.
  • Budget and limit refusals are not recorded. A new execution that would run now and exceeds the USD budget or a per-asset limit (spend_budget_exceeded, spend_price_unavailable, policy_limit_exceeded, policy_action_limit_exceeded, policy_usage_unavailable) is a plain refusal: no operation, no FAILED status, no charge. The same key is judged again each time and refuses again while the cause lasts. A FAILED row is recorded only when a request loses a concurrent race at the dispatch commitment, or a timelocked request is released over budget or a limit; its key then replays that outcome.
  • A new key is new work. Never use one to retry an action that is UNCERTAIN, PROCESSING or already dispatched.
The SDK creates a key when you omit one, for generate-intent as well, and returns it as idempotencyKey on the result. It never retries a write by itself. A transport failure surfaces as an error that carries the key, so you can resend with it. For crash safety, create the key with createIdempotencyKey(), save it, then send the request.

When the result is unknown

Never send an UNCERTAIN action again with a new Idempotency-Key. The first attempt may have worked, and a new key would run the action a second time.

UNCERTAIN

UNCERTAIN means the outcome is not yet proven: the API sent, or may have sent, the action and has not yet seen the result. Most of these settle on their own into SUCCESS, FAILED or REFUNDED. What to do:
  • A refusal never hides an UNCERTAIN action. When a request may have reached the agent wallet, the response is 202 with status UNCERTAIN and the action’s correlation_id, even if the provider’s answer looked like a refusal. If the API itself fails, you get a server error with meta.retryable: send the same request again with the same key, which returns the action without sending it twice.
  • Keep polling the same correlation_id. Every check reconciles the status with the provider.
  • failure_code: settlement_overdue means the provider still reports the request in progress after the settlement deadline. A provider success whose settlement is still pending reads PROCESSING until then. Keep polling: observation continues, and a late settlement still completes the action.
  • Lost the response to your request? Retry with the same key, or read the status of the correlation_id you saved. Do not start anything new until you know the result.
  • Stuck before it was sent? POST /v1/agents/{agent_id}/recover dispatches an UNCERTAIN execution once, but only when the API can prove it never reached the agent wallet: it has no provider reference and no dispatch commitment. It cannot force through an action whose result is unknown.

NEEDS_REVIEW

NEEDS_REVIEW means the provider reported a state that automatic settlement cannot resolve. failure_code is provider_review_required, and details.provider_status and details.failure_reason hold what the provider said. details.reason says why settlement stopped. A swap the provider reports as successful but below its min_amount_out reads NEEDS_REVIEW with amount_out_below_minimum, never SUCCESS. A native NEAR transfer or call whose provider answer was lost reads NEEDS_REVIEW with native_request_unconfirmed once the API stops resending it, 10 minutes after dispatch. See Native Accounts.
  • Stop polling. Routine status reads do not observe the provider again while the status is NEEDS_REVIEW.
  • Never retry it or send it again. Funds may have moved.
  • After the review is resolved, read the status with refresh=true. This is the one explicit observation of the original request, and it can move the status to a final value.
refresh=true can also repair the USD budget charge of a historical failed execution when the provider proves no funds moved.

Budget charges and failures

An execution’s USD charge is decided at the dispatch commitment. The API releases the charge only when it can prove the action never spent: the failure carries details.never_executed or never_submitted. Those flags come from the provider itself; a failure reason such as a balance that ran short (failure_code: insufficient_balance) is not proof, so that failure keeps its charges. An UNCERTAIN or NEEDS_REVIEW action keeps its charge until the result is known. See USD budget.

Owner intents

Owner intents (create, policy, freeze, grants) finish on their own after the signature is submitted. Keep checking the status. An unsigned intent expires, and a new one needs a new generate call.

Error format

Every error is a JSON:API document. Branch on errors[0].code, never on title or detail.
A response with meta.retryable: true also carries a Retry-After header with the wait in whole seconds. So does any response with meta.available_at, even a recorded refusal without meta.retryable: there the header says when new work can succeed, not that the same key can. Validation errors (validation_failed, 400) return one entry per invalid field, up to 20. source.pointer is a JSON pointer: /amount for a body field, /query/wait_ms, /path/agent_id or /header/… for the others. Unknown codes. The list grows over time. Handle a code you do not know by its layer and its HTTP status: 4xx means fix the request, 429 and 503 mean retry only if meta.retryable is set, as described below, and 5xx otherwise means try again later or ask for support with the request_id.
Decide from code, layer, meta.retryable and the status, never from detail:
  • meta.retryable: true: send the identical request again, with the same Idempotency-Key, after Retry-After. Server errors (internal_error, database_busy, transaction_unconfirmed) carry it because the request may have taken effect; only the same key can safely find out.
  • No meta.retryable: sending the same request again will not help now. It does not prove that an earlier attempt sent nothing.
  • A new key only after a proven refusal: a code whose “What to do” column says nothing was sent, such as policy_action_denied or a wallet_busy recorded as the action’s outcome (its key replays the refusal). Send the new work only once the cause is gone. A FAILED or REFUNDED status is not such proof, since a partial failure is also FAILED: check its details and the balances before you decide to send new work, and never retry it automatically.
  • Earlier work under the key: after a server error or a lost response, on intent_generation_recovery_required, operation_not_recoverable or operation_not_pending, or on any error from /recover, the first outcome may still be unknown. Keep the original key and correlation_id and read the status.
  • No response at all (timeout or network error): the result is unknown. Send the same request with the same key.
  • 202 with UNCERTAIN: not an error. Poll that correlation_id and never send the work under a new key.

Error layers

Every error carries a layer that names whose rule refused. Only a change to that layer lifts the refusal. Branch on code for the exact refusal and read layer to know who acts next. meta.retryable keeps its meaning in every layer: true says the same Idempotency-Key can succeed, and its absence proves nothing about an earlier attempt.

Error codes

The Layer column is the layer of the error. The Retryable column says whether the response carries meta.retryable: true. “At available_at” means it does, with a time. A refusal recorded as an action’s outcome never carries it, whatever the column says.

Request and authentication

Idempotency

Rate limits and capacity

Agents and lifecycle

Grants

Refused by the account policy

The API checks these before anything is sent. Nothing was spent. See Policies & Limits. The action, destination, asset, schedule, USD budget and per-asset limit codes below refuse the live request. A swap quote or withdrawal preview (dry: true) is not refused by them: it returns 200 and lists each rule the live request would fail in warnings, under the same code: policy_action_denied, policy_destination_denied, policy_asset_denied, policy_schedule_denied (with available_at), spend_budget_exceeded, spend_price_unavailable, policy_limit_exceeded, policy_action_limit_exceeded or policy_usage_unavailable. insufficient_balance (with available_amount) is a warning too. unsupported_token and invalid_address refuse the quote itself with 400, before any provider call, and use no preview attempt. A withdrawal preview uses one of the 20 daily preview attempts every time it reaches the provider, rule warning or not. A provider refusal, an outage and an uncertain attempt stay counted; only an answer the provider gives before running anything (its rate limiter’s 429, wallet_busy, provider_limit_reached) gives the attempt back. A new execution that runs now and exceeds the USD budget or a per-asset limit is a plain refusal: no operation, no FAILED status, no charge, and the same key refuses again while the cause lasts. A FAILED row is recorded only when a request loses a concurrent race at the dispatch commitment, or a timelocked request is released over budget or a limit; its key then replays that outcome. An archived, deleted or frozen account gets no quote: a swap quote and a withdrawal preview are refused with agent_archived, agent_deleted or wallet_frozen, reach no provider and use no attempt. See Account rules in a quote.

Refused by the wallet provider

The wallet provider checks the rest of the policy, and the request itself. These answer HTTP 409 unless the row says otherwise. Each one is decided before anything is sent, so nothing moved. Fix the cause, then send the work under a new Idempotency-Key. A refusal recorded as the action’s outcome replays under its own key and never carries meta.retryable. One returned before the action was admitted does carry it, and then the same key works after Retry-After. On the deposit address route, amount_too_low, route_unavailable, invalid_address and unsupported_token come from the quote and answer HTTP 400. amount_too_low carries meta.min_amount there too when the provider states the minimum. A quote the service refuses in words the API does not map to one of those is provider_refused (400) with its reason in detail. No address was created. See Funding an Agent.

Provider and broadcast failures

These are not refusals of the request. The provider or the network failed, and the request may or may not have taken effect. Never send the work again under a new key unless a status check proves nothing was sent.

Executions, recover and approvals

Owner intents and signatures

Policy updates

Agent wallet state

These come from creating, restoring or deleting an agent wallet. Most mean the wallet’s on-chain state differs from what the API expects, and you cannot fix them yourself. Contact support with the request_id.

Deleting an agent

A delete is one owner signature, so submitting it can also fail with agent_not_bound, policy_blocks_delete, policy_owner_mismatch, policy_delegate_expired, wallet_request_mismatch and wallet_request_type_mismatch. They are listed under Agents and lifecycle, Policy updates and Agent wallet state. The submission is refused before anything is retired. After the deletion completes, the API relays the signed policy cleanup. It cannot undo the deletion, and its outcome is details.policy_record in the status.

Native accounts

Transparency


Next steps

Executions

Run actions, and recover an uncertain one.

Policies & Limits

Understand the rules and the USD budget behind the policy errors.

Network & Quotas

Read limits, cooldowns and the health of the services behind the API.

Funding an Agent

Create deposit addresses and handle refunds.