Hosts and auth
| Sandbox host | https://api.sandbox.conduit.financial |
| Production host | https://api.conduit.financial |
| Auth header | x-api-key: ck_sandbox_... or ck_live_... |
| Idempotency header | idempotency-key: <uuid> (required on every money-moving POST; 300s cache TTL) |
Address format
- EVM: all-lowercase OR a correct EIP-55 checksum.
- Tron / Solana: Base58 verbatim.
Scenario suffixes
| Suffix | Chain | Forces | Observable on |
|---|---|---|---|
12517C00 | EVM, Tron, Solana | Wallet screening returns elevated risk score (below rejection threshold) | Withdrawal proceeds; status: completed (elevated-risk classification recorded for audit) |
503CA110 | EVM, Tron, Solana | Travel Rule provider temporarily unavailable | Withdrawal parks in status: processing until the Travel Rule provider recovers. |
5A4070ED | EVM, Tron, Solana | Wallet screening matches sanctions list | Withdrawal fails; status: failed, failureCode: compliance_review_rejected (sanctions classification recorded for audit) |
5A4D4E51 | EVM, Tron, Solana | compliance check returns elevated risk (routes as approved at medium threshold) | Withdrawal proceeds; status: completed |
5A4D4E5A | EVM, Tron, Solana | compliance check flags sanctions match | Withdrawal fails; status: failed, failureCode: compliance_review_rejected (sanctions classification recorded for audit) |
5A4D4E9E | EVM, Tron, Solana | compliance check passes but holds for manual review | Withdrawal pauses for compliance review; status: pending. Resolve via POST /v2/sandbox/transactions/:id/simulate/compliance-decision (approve → proceeds; reject → rejected). |
5A4D4EAA | EVM, Tron, Solana | compliance check passes (approved) | Withdrawal proceeds; status: completed |
5A4D4EE5 | EVM, Tron, Solana | compliance check fails (high-risk rejection) | Withdrawal fails; status: failed, failureCode: compliance_review_rejected |
5A4ED0DD | EVM, Tron, Solana | Travel Rule record created in a non-sendable state | Withdrawal fails pre-broadcast; status: failed, failureCode: travel_rule_rejected |
5A50AB1E | EVM, Tron, Solana | Wallet screening identifies destination as a known exchange | Travel Rule flow triggered; withdrawal proceeds if TR passes |
5E1F0577 | EVM, Tron, Solana | Wallet screening identifies destination as self-hosted | Travel Rule skipped; withdrawal proceeds normally |
94000000 | Fiat | Fiat withdrawal completes successfully | Withdrawal completed; status: completed |
94009001 | Fiat | Rail policy rejects (amount limit, frequency cap, or recipient restriction) | Withdrawal fails; status: failed, failureCode: rail_policy_rejected |
94009002 | Fiat | Insufficient funds at settlement (funds available at reservation) | Withdrawal fails; status: failed, failureCode: insufficient_funds_at_settle |
94009003 | Fiat | No viable rail available for the corridor | Withdrawal fails; status: failed, failureCode: rail_unavailable |
94009004 | Fiat | Rail provider timeout | Withdrawal fails; status: failed, failureCode: rail_unavailable |
95000000 | Fiat | compliance check passes for inbound fiat deposit | Deposit credited; status: completed |
95009001 | Fiat | compliance check fails for inbound fiat deposit | Deposit holds for a compliance decision; status: pending. Reject it via POST /v2/sandbox/transactions/:id/simulate/compliance-decision to freeze (failureCode: compliance_hold); approve is not available on an already-rejected review. |
95009002 | Fiat | compliance check flags sanctions match on inbound fiat deposit | Deposit holds for a compliance decision; status: pending. Same reject-only exit as 95009001. |
95009003 | Fiat | compliance check returns elevated risk on inbound fiat deposit (routes as approved) | Deposit credited; status: completed |
AC6BC0DE | EVM, Tron, Solana | Receiving institution acknowledges transfer (informational only) | Withdrawal proceeds; status: completed |
ACCEEDED | EVM, Tron, Solana | Receiving institution accepts transfer (informational under FATF Rec. 16) | Withdrawal proceeds; status: completed |
BAD6A1A4 | EVM, Tron, Solana | Receiving institution rejects transfer | Pre-broadcast: status: failed, failureCode: travel_rule_rejected. Post-broadcast: withdrawal completes (on-chain transfer cannot be reversed). |
BAD7E517 | EVM, Tron, Solana | Travel Rule pre-send validation rejects the transfer | Withdrawal fails pre-broadcast; status: failed, failureCode: travel_rule_rejected |
BAD8CA57 | EVM, Tron, Solana | Broadcast rejected by the chain provider | Withdrawal fails pre-broadcast; status: failed, failureCode: provider_rejected |
D15CCAD0 | EVM, Tron, Solana | Wallet attestation conflict: customer attests the destination is their own wallet but screening identifies a VASP | Withdrawal fails; status: failed, failureCode: travel_rule_rejected |
DA171465 | EVM, Tron, Solana | Receiving institution requests additional information; auto-resolves to accepted | Withdrawal proceeds; Travel Rule row transitions to accepted post-broadcast. status: completed |
DE5E11F1 | EVM, Tron, Solana | Deposit parked at sender-info gate; auto-pilot fires deadline | Deposit paused; customer clears via POST /v2/sandbox/customers/:customerId/deposits/:depositId/simulate/sender-info |
DEAA49F1 | EVM, Tron, Solana | compliance check passes for inbound crypto deposit | Deposit credited; status: completed |
DEAA5A4D | EVM, Tron, Solana | compliance check flags sanctions match on inbound crypto deposit | Deposit holds for a compliance decision; status: pending. Same reject-only exit as DEAA8E51. |
DEAA8157 | EVM, Tron, Solana | compliance check returns elevated risk on inbound crypto deposit (routes as approved) | Deposit credited; status: completed |
DEAA8E51 | EVM, Tron, Solana | compliance check fails for inbound crypto deposit | Deposit holds for a compliance decision; status: pending. Reject it via POST /v2/sandbox/transactions/:id/simulate/compliance-decision to freeze (failureCode: compliance_hold); approve is not available on an already-rejected review. |
DEAA9E9E | EVM, Tron, Solana | compliance check passes but holds for manual review on inbound crypto deposit | Deposit pauses for compliance review; status: pending. Resolve via POST /v2/sandbox/transactions/:id/simulate/compliance-decision (approve → credited; reject → frozen). |
DEC11A1D | EVM, Tron, Solana | Receiving institution declines transfer | Pre-broadcast: status: failed, failureCode: travel_rule_rejected. Post-broadcast: withdrawal completes. |
Simulate endpoints
| Endpoint | Drives | Body | Response | Errors |
|---|---|---|---|---|
POST /v2/sandbox/applications/:id/simulate/decision | Application -> APPROVED or REJECTED | { outcome: "approved" | "rejected", category?, field?, reason? } | 200 | APPLICATION_NOT_FOUND, REJECTION_CATEGORY_NOT_APPLICABLE |
POST /v2/sandbox/customers/:customerId/virtual-accounts/:virtualAccountId/deposits/simulate | Accepts a new fiat deposit for ingestion; outcome steers compliance branch. Returns — poll GET /v2/transactions?externalReference= | { outcome: "completed" | "frozen" | "returned" (default completed), assetAmount, senderInfo?, detectedAt?, externalReference?, reason? } | 202 | VALIDATION_ERROR, NOT_FOUND |
POST /v2/sandbox/customers/:customerId/wallets/:walletId/deposits/simulate | Accepts a new crypto deposit for ingestion; outcome steers compliance branch. Returns — poll GET /v2/transactions?txHash= | { outcome: "completed" | "frozen" | "returned" (default completed), assetAmount, originator?, sourceAddress, detectedAt?, externalReference?, reason? } | 202 | VALIDATION_ERROR, NOT_FOUND |
POST /v2/sandbox/customers/:customerId/deposits/:depositId/simulate/sender-info | Deposit’s sender-info gate -> RESOLVED | { senderInfo } | 200 | SANDBOX_SENDER_INFO_NOT_REQUIRED, NOT_FOUND |
POST /v2/sandbox/transactions/:id/simulate/terminal | Transaction -> COMPLETED or FAILED | { outcome: "completed" | "failed", utr?, reason? } | 200 | SANDBOX_TRANSACTION_NOT_FORCE_TERMINAL_READY, RESOURCE_TERMINAL, NOT_FOUND |
POST /v2/sandbox/transactions/:id/simulate/compliance-decision | Transaction parked for compliance review -> resumes (approve) or terminalizes at the type default (reject: deposit -> frozen, withdrawal -> rejected). Once rejected, approve is no longer available — a rejected case can only be terminalized via reject. | { outcome: "approve" | "reject" } | 202 | TRANSACTION_NOT_FOUND, VALIDATION_ERROR, CONFLICT |
POST /v2/sandbox/wallets/registered-addresses/:id/simulate/compliance-decision | Registered address parked pending_screening (0x999 review magic value) -> registered + deposits from the address that were awaiting registration resume (approve) or suspended as sanctions-blocked (reject); same decision replays 200, a conflicting one is 409 | { outcome: "approve" | "reject" } | 200 | REGISTERED_ADDRESS_NOT_FOUND, REGISTERED_ADDRESS_INVALID_TRANSITION, VALIDATION_ERROR |
POST /v2/sandbox/payouts/:id/simulate/confirm | Crypto payout -> chain-confirm | { outcome: "completed" | "failed", txHash?, reason? } (txHash required when outcome="completed") | 200 | PAYOUT_NOT_FOUND, SANDBOX_TRANSACTION_NOT_FORCE_TERMINAL_READY, RESOURCE_TERMINAL |
POST /v2/sandbox/payouts/:id/simulate/settled | Fiat payout -> rail settlement | { outcome: "completed" | "failed", utr?, reason? } | 200 | PAYOUT_NOT_FOUND, SANDBOX_TRANSACTION_NOT_FORCE_TERMINAL_READY, RESOURCE_TERMINAL |
POST /v2/sandbox/payouts/:id/simulate/counterparty-webhook | Travel-Rule counterparty resolution | { outcome: "acknowledged" | "approved" | "rejected" | "declined", reason? } | 200 | RESOURCE_TERMINAL, NOT_FOUND |
POST /v2/sandbox/payouts/:id/simulate/cosign | Non-custodial payout cosign gate | { outcome: "approved" | "declined", reason? } | 200 | PAYOUT_NOT_FOUND, RESOURCE_TERMINAL, NOT_FOUND |
POST /v2/sandbox/payouts/:id/simulate/broadcast-fail | Crypto payout -> broadcast failure terminal | { reason? } | 200 | RESOURCE_TERMINAL, NOT_FOUND |
POST /v2/sandbox/payouts/:id/simulate-review-approve | Payout document review -> APPROVED; payout resumes | {} | 200 | PAYOUT_NOT_FOUND |
POST /v2/sandbox/payouts/:id/simulate-review-reject | Payout document review -> REJECTED; funds returned | {} | 200 | PAYOUT_NOT_FOUND |
POST /v2/sandbox/payouts/:id/simulate-stamp | Records one signer’s cosign stamp on a non-custodial payout’s quorum; repeats walk the quorum, then the compliance auto-stamp lands once the customer threshold is met | { walletSignerId, outcome: "approved" | "declined" (default approved) } | 200 | PAYOUT_NOT_FOUND, NOT_IN_AWAITING_SIGNATURE, SIGNER_NOT_FOUND |
POST /v2/sandbox/wallet-signers/:signerId/mark-enrolled | Drives a signer from pendingActivation to active without going through the real passkey/OIDC flow; once every roster member is active, the provider account auto-activates and crypto_wallet.completed fires | {} | 200 | SIGNER_NOT_FOUND |
POST /v2/sandbox/verifications/:token/simulate/ceremony-stamp | Records a synthetic admin stamp (approved by default, or declined) on the parked CEREMONY_APPROVAL verification identified by :token, advancing a roster-change ceremony (add/remove/promote/demote) without the real passkey flow; the first ACTIVE admin is auto-selected as the stamper. Replays on a terminal token return the verification at its current state | { outcome?: "approved" | "declined" (default approved) } | 200 | VERIFICATION_NOT_FOUND, VERIFICATION_TOKEN_INVALID |
POST /v2/sandbox/customers/:customerId/simulate-reset-claim | Single-shot reset of a customer’s wallet claim (non-custodial or custodial). Removes every signer, every wallet, the crypto-wallet feature flag, and, for a custodial account, its server-side signing credentials, so the next claim-non-custodial starts fresh. Refuses with 409 CLAIM_RESET_BLOCKED if open transactions, deposits, or pending signature approvals still reference the customer. | {} | 200 | PROVIDER_ACCOUNT_NOT_FOUND, CLAIM_RESET_BLOCKED |
POST /v2/sandbox/orders/:id/simulate/rate-lock-expired | Order -> rate-lock-expired cancellation (about 1s) | {} | 200 | NOT_FOUND |
POST /v2/sandbox/orders/:id/simulate/conversion-failed | Order -> conversion-failed terminal | { reason? } | 200 | NOT_FOUND |
POST /v2/sandbox/orders/:orderId/deposits/simulate | Sends crypto to a deposit-funded order’s funding address (an order created with no source); outcome steers the compliance branch. sourceAddress must be an address the customer registered, or the funds are sent straight back. Accepts the order in any status, so funding an expired order reaches the unclaimed-transfer return. Returns the order — the transfer is not a resource | { outcome: "completed" | "frozen" | "returned" (default completed), assetAmount, txHash?, sourceAddress?, detectedAt?, externalReference?, providerFinalityState? } — sourceAddress: nullandoriginator are both rejected | 200 | ORDER_NOT_FOUND, SANDBOX_ORDER_NOT_DEPOSIT_FUNDED, VALIDATION_ERROR |
POST /v2/sandbox/orders/:orderId/deposits/simulate/compliance-decision | The compliance review holding a transfer into the order’s funding address -> resolved. approve releases it to fund the order; reject holds the funds permanently and the order goes unfunded | { outcome: "approve" | "reject" } | 202 | ORDER_NOT_FOUND, SANDBOX_ORDER_NOT_DEPOSIT_FUNDED, SANDBOX_ORDER_NO_PARKED_FUNDING, CONFLICT |
POST /v2/sandbox/orders/:id/simulate/cosign | Non-custodial conversion/offramp source-leg cosign gate | { outcome: "approved" | "declined", signerId? } | 200 | ORDER_NOT_FOUND, NOT_IN_AWAITING_SIGNATURE, SIGNER_NOT_FOUND |
POST /v2/sandbox/whitelist-recipients/:id/simulate-approve | Whitelist recipient PENDING_REVIEW -> REGISTERED | {} | 200 | WHITELIST_RECIPIENT_NOT_FOUND, WHITELIST_INVALID_TRANSITION |
POST /v2/sandbox/whitelist-recipients/:id/simulate-reject | Whitelist recipient PENDING_REVIEW -> REJECTED | {} | 200 | WHITELIST_RECIPIENT_NOT_FOUND, WHITELIST_INVALID_TRANSITION |
Index by entity
| If you have a… | You can call… |
|---|---|
app_... (application) | POST /v2/sandbox/applications/:id/simulate/decision |
cus_... (customer) | none directly; act on the customer’s applications and features |
vac_... (virtual account) | POST /v2/sandbox/customers/:customerId/virtual-accounts/:virtualAccountId/deposits/simulate |
wlt_... (wallet) | POST /v2/sandbox/customers/:customerId/wallets/:walletId/deposits/simulate |
wra_... (registered address) | POST /v2/sandbox/wallets/registered-addresses/:id/simulate/compliance-decision (when parked pending_screening via the 0x999 review magic value) |
dep_... (deposit) | POST /v2/sandbox/customers/:customerId/deposits/:depositId/simulate/sender-info |
txn_... (transaction / payout) | POST /v2/sandbox/transactions/:id/simulate/terminal; payouts also: .../payouts/:id/simulate/confirm, .../simulate/settled, .../simulate/counterparty-webhook, .../simulate/cosign, .../simulate/broadcast-fail, .../simulate-review-approve, .../simulate-review-reject |
ord_... (order) | POST /v2/sandbox/orders/:id/simulate/rate-lock-expired, .../simulate/conversion-failed, .../simulate/cosign (non-custodial conversion/offramp source leg), .../deposits/simulate and .../deposits/simulate/compliance-decision (deposit-funded orders — an order created with no source) |
Index by lifecycle state
Payouts| Payout state | What you can call |
|---|---|
pending (custodial) | nothing yet; the cosign auto-resolves and broadcast begins |
pending_cosign (non-custodial) | simulate/cosign {outcome: "approved" | "declined"}, or simulate/counterparty-webhook to drive a Travel Rule pre-broadcast reject |
parked at document review (payout created with documents) | simulate-review-approve to resume the payout, or simulate-review-reject to terminate it as failed with failureCode: compliance_rejected (reserved funds returned). The payout reads as status: "processing" while parked. |
broadcasting | simulate/confirm {outcome: "completed" | "failed", txHash?} to drive chain finality (custodial) or simulate/broadcast-fail to force-fail |
completed / failed | nothing; document-review simulation returns 404 PAYOUT_NOT_FOUND, while terminal-aware simulation levers return 409 RESOURCE_TERMINAL. See RESOURCE_TERMINAL playbook. |
| Deposit state | What you can call |
|---|---|
| (parked at sender-info gate) | simulate/sender-info |
terminal (completed / frozen / returned) | nothing; outcome was set at ingestion time |
| Order state | What you can call |
|---|---|
pending (rate-lock window open) | simulate/rate-lock-expired (cancels within ~1s) |
| any pre-terminal state with a conversion leg | simulate/conversion-failed |
| any state, deposit-funded orders only | deposits/simulate — sends crypto to the order’s published address, from an address the customer registered. Answers 200 with the order; the transfer itself is still a readable deposit transaction on GET /v2/transactions. Funding an already-cancelled (expired) order is how an unclaimed transfer arises; see Deposit-Funded Orders. |
| deposit-funded orders, transfer held for review | deposits/simulate/compliance-decision {outcome: "approve" | "reject"} — the funding transfer has no client-visible id, so the order carries the lever. 404 SANDBOX_ORDER_NO_PARKED_FUNDING when nothing is held. |
| Application state | What you can call |
|---|---|
pending | simulate/decision {outcome: "approved" | "rejected"} |
Index by error code
| If you got… | The call that produced it… |
|---|---|
RESOURCE_TERMINAL (409) | A terminal-aware simulate/* call against a terminal entity. Document-review simulation instead returns 404 PAYOUT_NOT_FOUND when no actionable review remains. See playbook. |
SANDBOX_TRANSACTION_NOT_FORCE_TERMINAL_READY (422) | simulate/settled or simulate/confirm against a payout parked at the document-review gate (call simulate-review-approve first); also simulate/terminal with outcome: "completed" before a fiat route is selected. See playbook. |
APPLICATION_NOT_FOUND (404) | simulate/decision when the application ID does not exist or does not belong to the org. |
REJECTION_CATEGORY_NOT_APPLICABLE (422) | simulate/decision with a category field on a non-KYB application. |
SANDBOX_SENDER_INFO_NOT_REQUIRED (409) | simulate/sender-info against a deposit not parked at the sender-info gate. |
Public failure codes
| Code | Terminal state | Channel | Description | Playbook |
|---|---|---|---|---|
chain_broadcast_failed | failed | webhook + polled | The signed payout could not be broadcast to the chain before reaching finality; no funds left the wallet | Playbook |
compliance_hold | failed | webhook + polled | Compliance review held the funds; manual remediation required | Playbook |
compliance_rejected | failed | webhook + polled | A compliance reviewer rejected the payout’s supporting documentation; reserved funds were returned (fires transaction.rejected, not transaction.failed) | Playbook |
compliance_review_rejected | failed | webhook + polled | Transaction rejected in regulatory compliance review; non-retryable | Playbook |
crypto_wallet_misconfigured | failed | webhook + polled | The source wallet is missing required custody configuration; the payout could not be signed | Playbook |
insufficient_funds | failed | webhook + polled | Source balance was insufficient to reserve the payout before broadcast; no funds moved | Playbook |
insufficient_funds_at_settle | failed | webhook + polled | Source funds were insufficient at the settlement attempt | Playbook |
provider_rejected | failed | webhook + polled | The crypto outbound provider declined the broadcast request before the transaction was submitted to the chain | Playbook |
rail_policy_rejected | failed | webhook + polled | The receiving rail rejected the payment per its policy | Playbook |
rail_unavailable | failed | webhook + polled | The chosen rail was temporarily unavailable | Playbook |
returned_by_sender | failed | webhook + polled | The inbound transfer was returned by the originating institution before it could be credited | Playbook |
roster_changed | failed | webhook + polled | A signer on the customer’s roster was removed (or demoted out of the signing pool) while the payout was awaiting signatures; the half-collected stamps were voided so the fintech can re-initiate | Playbook |
sender_info_timeout | failed | webhook + polled | Sender-information gate expired before resolution | Playbook |
travel_rule_rejected | failed | webhook + polled | Counterparty rejected the Travel Rule request, or Travel Rule validation failed pre-broadcast | Playbook |
user_signature_declined | failed | webhook + polled | End user explicitly declined to sign | Playbook |
user_signature_expired | failed | webhook + polled | End user did not sign across the allowed signing windows; the request expired | Playbook |
user_signature_rejected_by_provider | failed | webhook + polled | Custody provider rejected the signature payload | Playbook |
user_signature_timeout | failed | webhook + polled | Payout timed out waiting in the wallet’s signing queue before it could start collecting signatures | Playbook |
Sandbox simulate endpoint errors
These codes are returned as HTTP errors by sandbox simulate endpoints. They are notfailureCode values on transactions.
| Code | HTTP status | Description | Playbook |
|---|---|---|---|
INVALID_ADDRESS_FORMAT | 400 | EVM address must be all-lowercase or a correct EIP-55 checksum | Playbook |
RESOURCE_TERMINAL | 409 | Tried to drive a simulate against an already-terminal entity | Playbook |
SANDBOX_TRANSACTION_NOT_FORCE_TERMINAL_READY | 422 | Called a sandbox simulate endpoint whose requested outcome is not viable in the transaction’s current phase (e.g. simulate/settled or simulate/confirm against a payout parked at the document-review gate, or simulate/terminal with outcome:completed before a fiat route is selected) | Playbook |
HTTP status code legend
| Code | Meaning |
|---|---|
| 200 | Mutation succeeded; resource returned |
| 202 | Accepted; work is in flight — see the route’s row for what comes back |
| 400 | Validation error; check the pointer in the response |
| 401 | Missing or invalid API key |
| 403 | API key lacks permission |
| 404 | Resource not found |
| 409 | Conflict; inspect type for the precise error |
| 429 | Rate limited |
| 500 | Server error |
Webhook event topics (most common)
application.approved/application.rejectedtransaction.created/transaction.completed/transaction.failed/transaction.cancelledtransaction.awaiting_sender_informationorder.failedcustomer.activatedvirtual_account.activated