curl --request POST \
--url https://api.platform.dakota.xyz/sandbox/simulate/onboarding \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--header 'x-idempotency-key: <x-idempotency-key>' \
--data '
{
"type": "kyb_approve",
"applicant_id": "01HABCDEFG1234567890XYZ",
"simulation_id": "sim_kyb_approve_001"
}
'const options = {
method: 'POST',
headers: {
'x-idempotency-key': '<x-idempotency-key>',
'x-api-key': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'kyb_approve',
applicant_id: '01HABCDEFG1234567890XYZ',
simulation_id: 'sim_kyb_approve_001'
})
};
fetch('https://api.platform.dakota.xyz/sandbox/simulate/onboarding', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.platform.dakota.xyz/sandbox/simulate/onboarding"
payload = {
"type": "kyb_approve",
"applicant_id": "01HABCDEFG1234567890XYZ",
"simulation_id": "sim_kyb_approve_001"
}
headers = {
"x-idempotency-key": "<x-idempotency-key>",
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.platform.dakota.xyz/sandbox/simulate/onboarding"
payload := strings.NewReader("{\n \"type\": \"kyb_approve\",\n \"applicant_id\": \"01HABCDEFG1234567890XYZ\",\n \"simulation_id\": \"sim_kyb_approve_001\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-idempotency-key", "<x-idempotency-key>")
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"simulation_id": "sim_kyb_approve_001",
"applicant_id": "01HABCDEFG1234567890XYZ",
"previous_state": "not_started",
"new_state": "approved"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#invalid-request",
"title": "Invalid request",
"status": 400,
"detail": "Invalid request",
"instance": "https://api.platform.dakota.xyz/sandbox/simulate/onboarding",
"request_id": "req_01hzy6y7v8w9x0y1z2a3b4c5d6"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#forbidden",
"title": "Not available in production",
"status": 403,
"detail": "Not available in production",
"instance": "https://api.platform.dakota.xyz/sandbox/simulate/onboarding",
"request_id": "req_01hzy6y7v8w9x0y1z2a3b4c5d6"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Applicant not found",
"status": 404,
"detail": "Applicant not found",
"instance": "https://api.platform.dakota.xyz/sandbox/simulate/onboarding",
"request_id": "req_01hzy6y7v8w9x0y1z2a3b4c5d6"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#internal-error",
"title": "Failed to apply transition",
"status": 500,
"detail": "Failed to apply transition",
"instance": "https://api.platform.dakota.xyz/sandbox/simulate/onboarding",
"request_id": "req_01hzy6y7v8w9x0y1z2a3b4c5d6"
}Simulate an onboarding state transition
Drives KYB, KYC, or applicant account status through a sandbox transition without waiting for real compliance review. Available in sandbox mode only.
Type → effect mapping:
| type | KYB status set | Application status set | webhooks emitted |
|---|---|---|---|
kyb_approve | approved (via provisioning) | approved, with decision approved (see below) | 2× customer.kyb_status.created (one per provider, kyb_status: approved) + recipient.created |
kyb_reject | — | declined | (none — application status only) |
kyb_info_request | — | request_for_information | (none — application status only) |
kyc_approve | — | approved | (none — application status only) |
kyc_reject | — | declined | (none — application status only) |
kyc_info_request | — | request_for_information | (none — application status only) |
applicant_activate | approved (via provisioning) | approved, with decision approved (see below) | 2× customer.kyb_status.created (one per provider, kyb_status: approved) + recipient.created |
applicant_suspend | — | declined | (none — application status only) |
poa_reject | — (kept as it is) | — (kept as it is) | customer.kyb_status.updated with reason_code: proof_of_address_rejected, in compliance_review only (see below) |
Approving customers (individual or business): Use kyb_approve to fully approve
any customer type. This triggers the complete onboarding flow including endorsement
and recipient creation. The kyc_* types only update the individual applicant’s
KYC application status without triggering the full onboarding flow.
Placeholder application data: kyb_approve and applicant_activate write
placeholder application data, so a customer approved without completing the
onboarding form can still be provisioned.
| application type | placeholder business | placeholder person |
|---|---|---|
individual | none | one person with role individual, no title and no ownership, added only when the application has no person; a person already on the application is never overwritten |
business | placeholder business details | one person with roles ubo and control_person, title ceo and 100% ownership, when the application has no person |
The placeholder person is named after the customer and has a placeholder email address.
Note: applicant_activate does NOT auto-create payment accounts, wallets, or
account numbers. Create those separately via the account creation API after activation.
What previous_state and new_state report. Both are the customer’s provisioning
status at Dakota: the status of the customer’s dakota-provider entry in
provider_statuses, using the ProviderKybStatus.status values (not_started, in_review,
approved, rejected, requires_info, auto_declined, frozen). They are not
Customer.kyb_status, and not the application status the type → effect table above sets.
previous_stateisnot_starteduntil the customer is first provisioned.- Only
kyb_approveandapplicant_activatecan change the status, and only once provisioning has finished. Provisioning runs inline, but if it has not finished when the response is built,new_stateequalsprevious_stateand the change arrives later through thecustomer.kyb_status.*webhooks. - Every other type leaves the status unchanged, so
new_stateequalsprevious_state. Readapplication_statusonGET /customers/{customer_id}to see the status the transition set.
Approval decision. kyb_approve and applicant_activate also record the application
decision approved, as a compliance approval does, with decision_by
sandbox-onboarding-simulator and a decision_reason naming the simulation type, such as
Approved by the sandbox onboarding simulator (kyb_approve). kyc_status and the
customer status then read approved. An application whose decision is already
approved keeps that decision.
Proof of address. For an individual, kyb_approve and applicant_activate also
approve a submitted proof of address, as a compliance approval does, including when the
application is already approved. They add no document: with no proof of address
submitted, nothing changes.
poa_reject rejects the proof of address of an individual, as a compliance rejection does:
- In
compliance_review(a proof of address uploaded after the volume limit froze the customer), the KYB status stays as it is and acustomer.kyb_status.updatedwebhook fires withreason_code: proof_of_address_rejected. - During the onboarding review (
submitted,under_revieworrequest_for_information, with no decision yet), only a submitted proof of address is rejected, and no webhook fires.
In both cases the proof of address volume limit stays in force
(GET /customers/{customer_id}/limits). poa_reject returns 400 where a compliance
reviewer cannot reject a proof of address either:
- a business application;
- an application under onboarding review with no submitted proof of address;
- an application that is neither under onboarding review nor in
compliance_review.
kyb_reject, kyb_info_request, kyc_reject, kyc_info_request and applicant_suspend
first withdraw a decision the simulator recorded, so status and kyc_status follow the
application status they set. A decision recorded by a reviewer is left unchanged.
kyc_approve sets the application status only and records no decision, so it does not
change kyc_status.
curl --request POST \
--url https://api.platform.dakota.xyz/sandbox/simulate/onboarding \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--header 'x-idempotency-key: <x-idempotency-key>' \
--data '
{
"type": "kyb_approve",
"applicant_id": "01HABCDEFG1234567890XYZ",
"simulation_id": "sim_kyb_approve_001"
}
'const options = {
method: 'POST',
headers: {
'x-idempotency-key': '<x-idempotency-key>',
'x-api-key': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'kyb_approve',
applicant_id: '01HABCDEFG1234567890XYZ',
simulation_id: 'sim_kyb_approve_001'
})
};
fetch('https://api.platform.dakota.xyz/sandbox/simulate/onboarding', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.platform.dakota.xyz/sandbox/simulate/onboarding"
payload = {
"type": "kyb_approve",
"applicant_id": "01HABCDEFG1234567890XYZ",
"simulation_id": "sim_kyb_approve_001"
}
headers = {
"x-idempotency-key": "<x-idempotency-key>",
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.platform.dakota.xyz/sandbox/simulate/onboarding"
payload := strings.NewReader("{\n \"type\": \"kyb_approve\",\n \"applicant_id\": \"01HABCDEFG1234567890XYZ\",\n \"simulation_id\": \"sim_kyb_approve_001\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-idempotency-key", "<x-idempotency-key>")
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"simulation_id": "sim_kyb_approve_001",
"applicant_id": "01HABCDEFG1234567890XYZ",
"previous_state": "not_started",
"new_state": "approved"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#invalid-request",
"title": "Invalid request",
"status": 400,
"detail": "Invalid request",
"instance": "https://api.platform.dakota.xyz/sandbox/simulate/onboarding",
"request_id": "req_01hzy6y7v8w9x0y1z2a3b4c5d6"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#forbidden",
"title": "Not available in production",
"status": 403,
"detail": "Not available in production",
"instance": "https://api.platform.dakota.xyz/sandbox/simulate/onboarding",
"request_id": "req_01hzy6y7v8w9x0y1z2a3b4c5d6"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Applicant not found",
"status": 404,
"detail": "Applicant not found",
"instance": "https://api.platform.dakota.xyz/sandbox/simulate/onboarding",
"request_id": "req_01hzy6y7v8w9x0y1z2a3b4c5d6"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#internal-error",
"title": "Failed to apply transition",
"status": 500,
"detail": "Failed to apply transition",
"instance": "https://api.platform.dakota.xyz/sandbox/simulate/onboarding",
"request_id": "req_01hzy6y7v8w9x0y1z2a3b4c5d6"
}Authorizations
Headers
Unique key (UUID) to ensure request idempotency on POST, PUT and PATCH requests.
A key is scoped to your client, the HTTP method and the path, and is remembered for 24 hours after a
successful (below 400) response. Repeating the same request (same JSON body, whatever the key order or
whitespace) with the same key within that window returns the original response with
X-Idempotent-Replayed: true instead of executing the request again. Using the same key on the same
endpoint with a different body returns 422 with problem type idempotency-key-reused; the request is
not executed. Error responses (4xx and 5xx) are not remembered, so a request refused for a fixable reason
can be corrected and retried under the same key. Request bodies that are not JSON (for example file
uploads) are not compared.
Sandbox-only. Applies a preset failure or behavior mode for the request, selecting a coherent combination of error step, status, and message. The full set of scenarios is also exposed dynamically via GET /sandbox/scenarios along with descriptions and per-rail applicability.
Effective only on https://api.platform.sandbox.dakota.xyz. Ignored in production.
happy_path, delayed_settlement, insufficient_funds, compliance_block, invalid_account, provider_maintenance, network_congestion, kyb_manual_review, kyb_rejected, kyb_expired, network_timeout, intermittent_errors, account_frozen, document_expired, invalid_swift, unauthorized "insufficient_funds"
Sandbox-only. Names the pipeline step at which the injected error fires. Pair with X-Sandbox-Error-Status and (optionally) X-Sandbox-Error-Message to drive a deterministic failure mode at a known point in the request lifecycle. Values longer than 100 characters are ignored.
Effective only on https://api.platform.sandbox.dakota.xyz. Ignored in production.
transaction_processing, compliance_check, account_validation, provider_call, kyb_submission, kyb_approval, network_call 100"provider_call"
Sandbox-only. Sets the HTTP status code returned when the sandbox injects an error at the configured step (see X-Sandbox-Error-Step). Must be a valid HTTP status code in the range 100-599; values outside that range are ignored. Status codes >= 400 cause the request to short-circuit immediately, before authentication, with the RFC 9457 application/problem+json response production sends for that status: for example 429 is #rate-limited with Retry-After and X-RateLimit-Remaining: 0, and 503 is #service-unavailable. With X-Sandbox-Scenario: insufficient_funds a 400 is #insufficient-balance. A status with no single production problem type, such as 422, which production answers with several types, has type: about:blank and the status phrase as its title.
Effective only on https://api.platform.sandbox.dakota.xyz. Ignored in production.
100 <= x <= 599503
Sandbox-only. Sets the detail of the injected sandbox problem response. Values longer than 500 bytes are ignored.
Effective only on https://api.platform.sandbox.dakota.xyz. Ignored in production.
500"Provider temporarily unavailable for maintenance."
Sandbox-only. When true, asynchronous simulation flows complete immediately rather than progressing through their normal timed states. Useful for fast end-to-end test runs that do not need to exercise intermediate webhook events. Accepts true/false (also 1/0, yes/no); other values are treated as false.
Effective only on https://api.platform.sandbox.dakota.xyz. Ignored in production.
true
Sandbox-only. When true, prevents the default 5-second KYB auto-approval that the sandbox applies to newly created customers. Use this header (typically with X-Sandbox-Scenario: kyb_manual_review or kyb_rejected) to keep an application in pending so integrators can exercise manual-review and rejection flows. Accepts true/false (also 1/0, yes/no); other values are treated as false.
Effective only on https://api.platform.sandbox.dakota.xyz. Ignored in production.
true
Body
The onboarding transition to simulate
kyb_approve, kyb_reject, kyb_info_request, kyc_approve, kyc_reject, kyc_info_request, applicant_activate, applicant_suspend, poa_reject The onboarding application ID, despite the field name: the application_id
returned by POST /customers, also available as application_id on
GET /customers/{customer_id}.
"01H..."
Unique ID for this simulation (for idempotency and tracing)
1 - 128"sim_03H..."
Organization ID (for context; optional, used for logging)
"org_01H..."
Optional reason code for reject/info_request transitions
"MISSING_EIN"
Fields to request (for *_info_request types only)
["ssn", "address"]
Response
Onboarding transition applied
The customer's provisioning status at Dakota before the transition, using the
ProviderKybStatus.status values. not_started until the customer is first
provisioned. Not Customer.kyb_status; see the endpoint description.
The customer's provisioning status at Dakota after the transition. Equal to
previous_state for every type except kyb_approve and applicant_activate,
and for those too when provisioning has not finished yet.
Was this page helpful?

