Skip to content
Seismic Orchestration

POST /customers

Create an end-user customer. The body is polymorphic: a type discriminator selects an individual (KYC flow) or a business (KYB flow). The rest of the body carries the matching profile fields. On success the response carries the customer's UUID. Every subsequent call uses it. The customer is scoped to the org bound to your API key.

The body accepts partial drafts: every leaf is optional on the wire, the wizard can save what it has so far and the server stores whatever's there. Submit-time validation runs on POST /customers/:id/kyc/submit, required-field gaps surface there as missing[], not here.

Request body

FieldTypeRequiredNotes
type"individual" | "business"yesDiscriminator. Picks the shape of profile.
profileKycProfile | KybProfileyesEmpty object is fine: every leaf is optional at create time. Submit-time validation runs separately on POST /customers/:id/kyc/submit.
sandbox_auto_approve_kycbooleannoSandbox only. Controls what happens when you later call POST /customers/:id/kyc/submit. Omit it or set true for immediate approval in the submit response and a corresponding status webhook. Set false for asynchronous status progression. Creating the customer never submits it. On production, explicit true is rejected with 400; explicit false is accepted as a no-op.

type: "individual", embed a KycProfile

{
  "type": "individual",
  "profile": {
    "identity": {
      "first_name": "Alice",
      "last_name": "Liddell",
      "date_of_birth": "1990-04-12"
    },
    "contact": {
      "email": "alice@example.com"
    },
    "addresses": {
      "residential": {
        "line1": "123 Main St",
        "city": "Brooklyn",
        "state": "NY",
        "postal_code": "11201",
        "country": "US"
      }
    }
  }
}

See KycProfile for the full sub-section list (identity, addresses, contact, employment, risk, volumes, jurisdictions, attestations, documents, supplemental).

type: "business", embed a KybProfile

{
  "type": "business",
  "profile": {
    "business": {
      "legal_name": "Acme Inc.",
      "entity_type": "c_corporation",
      "country_of_registration": "US"
    },
    "contact": {
      "email": "ops@acme.com"
    }
  }
}

See KybProfile for the full sub-section list (business, addresses, contact, operations, volumes, counterparties, attestations, associated_persons, supplemental, documents).

Document references

Document bytes are uploaded separately via POST /customers/:id/kyc/documents, but the upload endpoint takes a customer_id in the URL, so you need a created customer first. The common flow is:

  1. POST /customers with profile fields only, no document refs.
  2. POST /customers/:id/kyc/documents for each document. Capture the returned document_id; supporting documents attach automatically and appear in the read-only profile.documents.supporting_documents list.
  3. PUT /customers/:id/kyc/profile to write the document_id into the profile (profile.documents.government_issued_identification.front_image_document_id or profile.associated_persons[].gov_id_front_image_document_id).
  4. POST /customers/:id/kyc/submit once required fields are in.

Request headers

HeaderNotes
Idempotency-KeyOptional. Any unique string up to 256 characters, generally a UUID. Re-sending the same key returns the originally created customer instead of creating a duplicate. Re-sending it with a different type than the original create is rejected with 409 — a key never answers for the other kind of customer. Deleting the customer frees its key for reuse. Recommended for any caller doing automatic retries.

Response 201

The id of the new customer, the type discriminator (so clients don't have to remember which kind they posted), and an auto-minted HostedKycLink: share this url with the end-customer to drive them through verification, no separate provisioning call needed.

Individual (type: "individual"): url carries the ind segment, fully qualified against the environment's APP_BASE_URL:

{
  "type": "individual",
  "id": "2f10…",
  "kyc_link": {
    "token": "klink_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w",
    "url": "https://orchestration.seismic.systems/kyc-link/ind/klink_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w"
  }
}

Business (type: "business"): same field name kyc_link, url carries the biz segment:

{
  "type": "business",
  "id": "8e3b…",
  "kyc_link": {
    "token": "klink_b9c8d7e6f5g4h3i2j1k0l9m8n7o6p5q4r3s2t1u0v9w8x",
    "url": "https://orchestration.seismic.systems/kyc-link/biz/klink_b9c8d7e6f5g4h3i2j1k0l9m8n7o6p5q4r3s2t1u0v9w8x"
  }
}

On sandbox, url resolves against https://orchestration.seismictest.net instead (same shape, different origin).

Sandbox approval timing

Creating a sandbox customer always stores a draft. You must still call POST /customers/:id/kyc/submit after the profile is complete.

By default, that submit returns the final approved state immediately and emits the corresponding status webhook. Set "sandbox_auto_approve_kyc": false when creating the customer to exercise the asynchronous lifecycle instead: submit returns an in-flight state, then signed status webhooks advance the customer through pending, under_review, and finally approved after randomized delays. If the first update lands quickly, the submit response may already show under_review.

Sandbox submission still enforces profile validation and jurisdiction restrictions. A restricted jurisdiction leaves the customer under_review instead of approving. The production API rejects explicit true with 400; explicit false is accepted as a no-op, so an opted-out payload can be shared across environments.

{
  "type": "individual",
  "profile": {
    "identity": {
      "first_name": "Alice",
      "last_name": "Liddell",
      "date_of_birth": "1990-04-12"
    },
    "contact": {
      "email": "alice@example.com"
    }
  },
  "sandbox_auto_approve_kyc": false
}

Errors

  • 400: body schema invalid (unknown enum value, malformed date, etc.). The message names the bad path.
  • 400: sandbox_auto_approve_kyc: true set against the production API. Immediate sandbox approval is sandbox-only. Explicit false is accepted as a no-op.
  • 409 (IdempotencyKeyConflict): the Idempotency-Key was previously used to create a customer of a different type. Send a fresh key for the new customer.

Example: individual

curl -X POST "$SEISMIC_ORCHESTRATION_URL/v0/customers" \
  -H "Api-Key: $SEISMIC_ORCHESTRATION_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "type": "individual",
    "profile": {
      "identity": {
        "first_name": "Alice",
        "last_name": "Liddell",
        "date_of_birth": "1990-04-12"
      },
      "contact": {
        "email": "alice@example.com"
      },
      "addresses": {
        "residential": {
          "line1": "123 Main St",
          "city": "Brooklyn",
          "state": "NY",
          "postal_code": "11201",
          "country": "US"
        }
      }
    }
  }'

Example: business

curl -X POST "$SEISMIC_ORCHESTRATION_URL/v0/customers" \
  -H "Api-Key: $SEISMIC_ORCHESTRATION_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "type": "business",
    "profile": {
      "business": {
        "legal_name": "Acme Inc.",
        "entity_type": "c_corporation",
        "country_of_registration": "US"
      },
      "contact": {
        "email": "ops@acme.com"
      }
    }
  }'