Skip to main content

Overview

In the sandbox environment, customer KYC is mocked: no upstream KYC provider is called, and no end-user KYC data is collected. Customer onboarding still flows through the full review pipeline (sourcing, document validation, extraction, verification, compliance checks) — but the compliance-checks stage is satisfied by a mock that defers the verdict for one hour by default. Org-level KYB is not mocked. Your organization completes real KYB against real providers in sandbox; sandbox KYB approval is the prerequisite for graduating to production.

Customer-onboarding lifecycle

When you submit a customer onboarding application in sandbox:
  1. The application enters "processing".
  2. The mock records PENDING field verifications for the same paths a real KYC transaction would (business website, classification report, adverse-media report, associated persons, TIN — depending on the submitted data and country).
  3. A 1-hour timer starts.
  4. Either you call one of the simulate endpoints below to drive a specific outcome, or the timer fires and auto-approves the application.
First call wins: subsequent simulate calls return 409 Conflict.

Simulate endpoints

POST /v2/sandbox/applications/:id/simulate/decision forces a sandbox application to a terminal status on demand, regardless of the application’s current review stage. The body’s outcome field selects approved or rejected.

Works for any application type

This endpoint is not limited to customer onboarding. It accepts every application type your sandbox API key can create:
Application typeOn outcome: "approved"
CUSTOMER_ONBOARDINGCustomer transitions to ACTIVE and customer.activated fires.
VIRTUAL_ACCOUNTThe virtual account and its underlying account details are provisioned in one shot (see Virtual accounts for the end-to-end shape).
CRYPTO_WALLETThe customer’s crypto provider account is provisioned. Use POST /v2/customers/:id/wallets {chain} to create each chain’s wallet.
WALLET_CUSTODY_CONVERSIONThe customer’s wallets flip to custodyModel: "non_custodial" and the cosign gate becomes live for future payouts (see Non-Custodial Wallets).
CUSTOMER_UPDATEThe pending customer update is applied.
outcome: "rejected" publishes the matching *.rejected event for the application’s type. Rejected applications never reach a provisioning branch.

Request shape

The endpoint requires your sandbox API key and a JSON body with an outcome field. The simplest approval call:
curl -X POST https://api.sandbox.conduit.financial/v2/sandbox/applications/app_01H.../simulate/decision \
  -H "x-api-key: ck_sandbox_..." \
  -H "Content-Type: application/json" \
  -d '{ "outcome": "approved" }'
Returns 200 OK with the application at its current state. Replays against an already-terminal application return 200 with the current resource (idempotent).

Rejection options

The same endpoint with outcome: "rejected" accepts optional fields to shape the rejection:
BodyBehavior
{ "outcome": "rejected" }Generic rejection. The persisted reason falls back to a sentinel string.
{ "outcome": "rejected", "reason": "<text>" }Free-text rejection. The string is persisted verbatim as the rejection reason.
{ "outcome": "rejected", "category": "...", "field": "..." }Structured rejection that drives the rejection down a specific sub-path so the persisted reason matches what the live KYB pipeline would produce.
Structured category is only meaningful for KYB-pipeline application types (CUSTOMER_ONBOARDING, ORGANIZATION_ONBOARDING, CUSTOMER_UPDATE, SANCTIONS_REVIEW). Sending category on a non-KYB type (e.g. a VIRTUAL_ACCOUNT application) returns 422 REJECTION_CATEGORY_NOT_APPLICABLE. Sending category with outcome: "approved" returns 400 VALIDATION_ERROR. Within category: GENERIC an optional reason is accepted (this is how integrators simulate a manual operator rejection); for all other categories reason cannot be combined with category. Categories: DOCUMENT_MISMATCH (optional field: TAX_ID, DATE_OF_INCORPORATION, BUSINESS_NAME, BUSINESS_ENTITY_ID), DATA_SOURCING_MISMATCH (optional field: BUSINESS_ENTITY_ID, UBO_FIRST_NAME, UBO_LAST_NAME, UBO_PHONE, UBO_EMAIL, OWNERSHIP_LIST), COMPLIANCE (no field), GENERIC (no field; accepts an optional reason).

Errors

StatusReason
400Missing outcome, non-object body, or reason combined with a category other than GENERIC. Also returned when category is sent with outcome: "approved".
401Missing or invalid x-api-key.
404The application does not exist or does not belong to your organization.
422category was sent against a non-KYB application type (VIRTUAL_ACCOUNT, CRYPTO_WALLET, WALLET_CUSTODY_CONVERSION).

Non-custodial wallet provisioning

A wallet is created custodial by default. To flip a wallet to non-custodial, create a WALLET_CUSTODY_CONVERSION feature application after the CRYPTO_WALLET feature is approved. See the Custodial vs non-custodial guide for the full copy-paste curl flow with both custody models walked side by side.

Notes

  • Customer KYC data submitted to sandbox is not transferred to production. Sandbox and production are isolated environments — end-user customers re-onboard in production.
  • The mock matches the real KYC adapter’s field-path set so your client code sees the same shape on application_field_verifications whether it’s running against sandbox or live.
  • The 1-hour fallback applies to customer-onboarding applications only and makes it safe to ignore simulation entirely for happy-path tests — submit, wait, observe an approved outcome. Other application types do not auto-resolve; drive them with simulate/decision { outcome: "approved" | "rejected" }.

See also