Skip to content
Logo

POST /customers/:id/kyc/submit

Submit the saved profile for verification. The endpoint dispatches the right flow based on the customer's type: KYC for individuals, KYB for businesses. Idempotent, calling twice on an already-submitted customer returns the existing state row rather than re-submitting.

In production, every submission is reviewed by Seismic before verification begins. The status reads submitted while that review is open; it moves to under_review (and any hosted link appears on the state) once verification starts. If the review needs more information, the request arrives by email while the status stays submitted — update the profile and submit again. If the review rejects the submission, the status reads rejected with a rejection_reason saying what to fix: correct the profile and submit again, and a fresh review opens. If the review declines the submission, the status reads terminally_rejected with a rejection_reason: the decision is final and further submissions for this customer are refused with 409 VerificationDeclined.

In sandbox, the customer creation setting sandbox_auto_approve_kyc controls timing. The default (true) returns the final approved state in this submit response and emits the corresponding status webhook. When the customer was created with false, signed status webhooks move the state through pending, under_review, and approved after randomized delays. The submit response contains the latest in-flight state, which may already be under_review if the first update has landed. Customer creation never submits automatically in either mode.

Resubmission works from a settled verification too. If a verification came back rejected, correct the saved profile and call submit again — the same customer is re-verified with your changes rather than duplicated. An approved customer can also be resubmitted after a profile edit, but only through the explicit resubmit_approved opt-in below — the status may move back into review until the re-check completes.

Query parameters

NameDefaultMeaning
resubmit_approved01 / true / yes / on → also re-verify already-approved parts of the verification after a profile edit. Without it a submit never disturbs an approved verification; with it but no profile change, the call is a no-op returning the current state.

The server:

  1. Reads the saved profile sub-object (KycProfile for individuals, KybProfile for businesses).
  2. Validates required fields. Returns 422 KycProfileIncomplete or 422 KybProfileIncomplete if any are missing or malformed, with absent-field dotted paths in missing[] and any format failures (e.g. postal code) in violations[].
  3. Queues the submission for review in production. In sandbox, it either approves immediately or begins asynchronous progression according to the setting stored at customer creation.
  4. Returns the resulting CustomerState.

Business narrative length

From 2026-11-01, business narratives must meet these lengths on every submit; until then only the 350-character floor on business.description applies. Out-of-range values are reported in violations[] with reason: "too_short" or "too_long":

  • business.description: 350 to 1,000 characters.
  • operations.primary_source_of_funds_description: 200 to 1,000 characters.
  • operations.account_purpose_explanation: 200 to 1,000 characters when given.
  • operations.msb_compliance_screening_description: at least 200 characters when operations.registered_msb or operations.in_flow_of_funds is true.
  • The screening narrative assembled from operations.msb_compliance_screening_description and operations.high_risk_activities_description (or operations.compliance_screening_explanation supplied directly): at most 1,000 characters, section headers included.

From 2026-11-01, a business with operations.in_flow_of_funds set to true must send operations.registered_msb_description and operations.msb_compliance_screening_description; a profile without them is reported in missing[]. Profiles already verified do not need to be resubmitted.

From 2026-11-14, operations.account_purpose_explanation is required whatever operations.account_purpose is, not only for other; a profile without one is reported in missing[]. Profiles already verified do not need to be resubmitted.

Request body

Optional. Send it to record where the customer was when they accepted the terms of service — the acceptance is stored against the verification, with the version of the terms in force at the time.

{
  "end_user": {
    "ip_address": "203.0.113.7",
    "device_fingerprint": "8f14e45fceea167a5a36dedd4bea2543",
    "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
  }
}
FieldMeaning
end_user.ip_addressThe address the customer accepted from, v4 or v6. Send this from a server-to-server call authenticated with an API key: your own server's address is never substituted for it, so omitting it records no address at all.
end_user.device_fingerprintAn opaque identifier for the device they accepted on. Any printable encoding up to 128 characters — it is recorded and shown, never parsed, so whatever your client library produces is fine.
end_user.user_agentThe user agent of the browser they accepted in: the User-Agent header your own servers received from it, or navigator.userAgent. Printable ASCII up to 512 characters. Like the address, send it from a server-to-server call: your own server's user agent is never substituted for it.

All three fields are optional today, and an empty body behaves exactly as before. Starting November 1, 2026, a submit made with an API key will require end_user.ip_address, end_user.device_fingerprint and end_user.user_agent.

When the customer fills in a hosted verification link instead, the request comes from their own browser: the address and user agent are taken from the request itself, and sending end_user.ip_address or end_user.user_agent on that path returns 400 InvalidRequest rather than being silently ignored.

See Device fingerprinting for how to collect these values in your own frontend, including a client function that computes the fingerprint the same way the hosted link does.

Response 200

The current CustomerState, wrapped in the { type, state } envelope. For individuals:

{
  "type": "individual",
  "state": {
    "kyc_status": "submitted",
    "kyc_link": null,
    "missing_fields": [],
    "last_synced_at": "2026-05-04T11:40:00.000Z",
    "rejection_reason": null,
    "pending_rfi": false,
    "last_submitted_at": "2026-05-04T11:40:00.000Z",
    "resubmit_available": false
  }
}

For businesses the shape is identical except state.kyc_status and state.kyc_link are replaced by state.kyb_status and state.kyb_link:

{
  "type": "business",
  "state": {
    "kyb_status": "submitted",
    "kyb_link": null,
    "missing_fields": [],
    "last_synced_at": "2026-05-04T11:40:00.000Z",
    "rejection_reason": null,
    "pending_rfi": false,
    "last_submitted_at": "2026-05-04T11:40:00.000Z",
    "resubmit_available": false
  }
}

kyc_link / kyb_link is null while the submission is under review, and stays null for flows that collect verification inline or over email rather than through a hosted form. Once verification starts, re-read GET /customers/:id/kyc/state for the link.

Errors

  • 403 KybNotApproved: the org's own KYB hasn't been approved yet. Customers can only be registered once the org has passed KYB — complete it and resolve any checks still pending or requiring action.
  • 404: unknown customer id, or no profile saved yet (call PUT /customers/:id/kyc/profile first).
  • 409 ResubmitUnchanged: the profile has not changed since the submission that was returned or rejected. Edit the saved profile or replace a document before resubmitting.
  • 422 KycProfileIncomplete (individuals) or 422 KybProfileIncomplete (businesses): required fields are missing or malformed. The error body's missing array names the dotted paths of absent fields. violations[] names any present-but-malformed fields (e.g. postal code) with a reason slug and a human description.

Example

curl -X POST "$SEISMIC_ORCHESTRATION_URL/v0/customers/$CUSTOMER_ID/kyc/submit" \
  -H "Api-Key: $SEISMIC_ORCHESTRATION_API_KEY"