Skip to content
Logo

Documents

Supporting documents (government IDs, bank statements, proof of address, ownership charts, etc.) attach to a customer's profile or to one of its associated persons (UBOs on a business). The bytes live in object storage. The API surface is a metadata table the dashboard renders and the submit step consumes when running the customer's verification checks.

Polymorphic: the same three endpoints serve individuals and businesses. For individuals the document attaches to the customer directly. For businesses, a document either attaches to the business itself (owner=customer) or to one of its UBOs (owner=person with a person_id).

Documents accumulate across saves of the profile. They do not reset when PUT /customers/:id/kyc/profile re-saves a draft. Re-uploading the same (owner, kind) slot creates a new version with a new document_id and soft-deletes the prior one. Only the latest live version appears in GET /customers/:id/kyc/documents and feeds into submit. The prior row is retained in the database (deleted_at set) for compliance audit, but it isn't exposed back through the API.

All endpoints accept either Authorization: Bearer <jwt> or Api-Key: <key>.

Endpoints

POST /customers/:id/kyc/documents

Upload a supporting document. The body is multipart/form-data with the following fields.

FieldRequiredNotes
kindyesOne of the DocumentKind variants below.
ownerconditionalcustomer (the default) or person. person is only valid on a business customer. Picks whether the document attaches to the business itself or to a specific UBO row.
person_idconditionalUUID of the associated person. Required when owner=person. Rejected otherwise.
descriptionconditionalFree-text label, up to 256 characters. Required when kind=other unless purpose=rfi_answer: the free-form catch-all needs it so reviewers can tell attachments apart. Optional (and typically omitted) for every other kind. A blank or whitespace-only value counts as absent.
purposenorfi_answer when the file answers an open request for information. The request names the file, so an other upload sent with it needs no description. Omit for a plain profile upload.
proof_of_address_typeconditionalRequired on a proof_of_address from 2026-11-01. What a proof_of_address is: utility_bill, bank_statement, rental_agreement, tax_document, or government_correspondence. Verification reviews file the document under this type. Only accepted with kind=proof_of_address. To set the type on a proof of address already on file, upload the same file again with the type. From 2026-11-01, submitting a profile whose proof of address has no type returns 422 naming it, e.g. documents.proof_of_address.proof_of_address_type or associated_persons[0].proof_of_address_type.
fileyesThe file bytes. Up to 5 MB per file (the request as a whole is capped at 12 MB), and PDFs at most 200 pages. application/pdf, image/jpeg, or image/png: both the content type and the filename extension are checked, and anything else (spreadsheets, Word documents, …) is rejected with 400 UnsupportedDocumentType. A PDF longer than 20 pages is split at upload — the first 20 pages are stored under the kind you sent and returned here, and each further span of 20 pages is stored as an extra other document named FILENAME_2.pdf, FILENAME_3.pdf, … A PDF under 10 KB gets a filler page appended so it clears the 10 KB verification floor. See Preparing documents before upload.

Idempotent against the latest live row in the slot: re-uploading byte-identical content for an existing (owner, kind, person_id?) returns that document unchanged. Re-uploading different content supersedes the prior live version, the new document gets a new document_id and the prior row is soft-deleted in the same call. After a separate DELETE on a slot, re-uploading the same bytes creates a fresh document with a new document_id. The soft-deleted row stays soft-deleted.

other is the exception. The other kind isn't a single slot: it's a free-form catch-all that accepts any number of documents. Uploads under other never supersede or dedupe: each one creates a new document_id, so you can attach as many as you like. Because they'd otherwise be indistinguishable, every other upload must include a non-empty description (a missing one is rejected with 400). The one exception is an upload sent with purpose=rfi_answer: the request for information it answers already names the file.

After upload, store the returned document_id on the corresponding profile slot via PUT /customers/:id/kyc/profile:

  • For individuals: profile.documents.government_issued_identification.front_image_document_id (or back_image_document_id). Supporting uploads automatically appear in the read-only profile.documents.supporting_documents[] list; do not write that list through the profile endpoint.
  • For businesses: profile.associated_persons[].gov_id_front_image_document_id (or gov_id_back_image_document_id).

Response 200

A polymorphic envelope: { type, document }. Both kinds carry the full metadata. The field set differs slightly per kind.

For individuals (type === "individual"), document is a KycDocumentRef:

{
  "type": "individual",
  "document": {
    "kind": "passport",
    "document_id": "8c9d…",
    "filename": "alice-passport.jpg",
    "content_type": "image/jpeg",
    "size_bytes": 154777,
    "uploaded_at": "2026-05-10T20:14:21.045Z"
  }
}

For businesses (type === "business"), document is a KybDocumentRef:

{
  "type": "business",
  "document": {
    "document_id": "8c9d…",
    "kind": "articles_of_association",
    "filename": "acme-articles.pdf",
    "content_type": "application/pdf",
    "size_bytes": 184213,
    "uploaded_at": "2026-05-10T20:14:21.045Z"
  }
}

Errors

  • 400: bad multipart body, unknown kind, unknown purpose, missing required field, person_id absent when owner=person, person_id set when owner=customer, owner=person on an individual customer, description absent when kind=other (without purpose=rfi_answer), a description longer than 256 characters, a proof_of_address_type that isn't one of the listed values or is sent with a kind other than proof_of_address, an unsupported file type (UnsupportedDocumentType: documents must be PDF, JPEG, or PNG), or a PDF longer than 200 pages.
  • 403: caller does not own the customer.
  • 404: unknown customer id, or person_id does not belong to this customer's associated_persons.
  • 413: file exceeds 5 MB, or the multipart request exceeds 12 MB.
  • 503: document storage is not configured on this server.

Example: individual passport

curl -X POST "$SEISMIC_ORCHESTRATION_URL/v0/customers/$CUSTOMER_ID/kyc/documents" \
  -H "Api-Key: $SEISMIC_ORCHESTRATION_API_KEY" \
  -F "kind=passport" \
  -F "file=@./alice-passport.jpg"

Example: business UBO driver's license front

curl -X POST "$SEISMIC_ORCHESTRATION_URL/v0/customers/$CUSTOMER_ID/kyc/documents" \
  -H "Api-Key: $SEISMIC_ORCHESTRATION_API_KEY" \
  -F "kind=drivers_licence_front" \
  -F "owner=person" \
  -F "person_id=2455…" \
  -F "file=@./jane-license-front.jpg"

Example: business articles of incorporation

curl -X POST "$SEISMIC_ORCHESTRATION_URL/v0/customers/$CUSTOMER_ID/kyc/documents" \
  -H "Api-Key: $SEISMIC_ORCHESTRATION_API_KEY" \
  -F "kind=articles_of_association" \
  -F "file=@./acme-articles.pdf"

Example: an other document (requires a description)

curl -X POST "$SEISMIC_ORCHESTRATION_URL/v0/customers/$CUSTOMER_ID/kyc/documents" \
  -H "Api-Key: $SEISMIC_ORCHESTRATION_API_KEY" \
  -F "kind=other" \
  -F "description=Prior verification approval" \
  -F "file=@./approval-letter.pdf"

Example: a proof of address with its type

curl -X POST "$SEISMIC_ORCHESTRATION_URL/v0/customers/$CUSTOMER_ID/kyc/documents" \
  -H "Api-Key: $SEISMIC_ORCHESTRATION_API_KEY" \
  -F "kind=proof_of_address" \
  -F "proof_of_address_type=utility_bill" \
  -F "file=@./electricity-bill.pdf"

GET /customers/:id/kyc/documents

List every non-deleted document attached to the customer or to any of its associated persons.

Response 200

A { type, documents } envelope with the array nested under documents.

For individuals, each entry is a KycDocumentRef:

{
  "type": "individual",
  "documents": [
    {
      "kind": "passport",
      "document_id": "8c9d…",
      "filename": "alice-passport.jpg",
      "content_type": "image/jpeg",
      "size_bytes": 154777,
      "uploaded_at": "2026-05-10T20:14:21.045Z"
    }
  ]
}

For businesses, each entry is a KybDocumentRef:

{
  "type": "business",
  "documents": [
    {
      "document_id": "8c9d…",
      "kind": "articles_of_association",
      "filename": "acme-articles.pdf",
      "content_type": "application/pdf",
      "size_bytes": 184213,
      "uploaded_at": "2026-05-10T20:14:21.045Z"
    },
    {
      "document_id": "a1f2…",
      "kind": "drivers_licence_front",
      "filename": "jane-license-front.jpg",
      "content_type": "image/jpeg",
      "size_bytes": 154777,
      "uploaded_at": "2026-05-10T20:39:26.495Z"
    }
  ]
}

Entries carry the exact byte count under size_bytes and the uploader-supplied content_type (not re-sniffed).

Errors

  • 403: caller does not own the customer.
  • 404: unknown customer id.

Example

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

GET /customers/:id/kyc/documents/:doc_id

Download a document's bytes, for example to let a user re-check which file they attached to a slot.

Response 200

The raw file bytes. Content-Type is the stored MIME type and Content-Disposition carries the original upload filename:

Content-Type: image/jpeg
Content-Disposition: attachment; filename="alice-passport.jpg"

Errors

  • 403: caller does not own the customer.
  • 404: no document with that id belongs to this customer. Same response for "never existed", "belongs to a different customer", and "soft-deleted" so existence isn't leaked.
  • 503: document storage is not configured on this server.

Example

-OJ saves the file under its original name:

curl -OJ "$SEISMIC_ORCHESTRATION_URL/v0/customers/$CUSTOMER_ID/kyc/documents/8c9d…" \
  -H "Api-Key: $SEISMIC_ORCHESTRATION_API_KEY"

DELETE /customers/:id/kyc/documents/:doc_id

Soft-delete a document. The DB row is marked deleted_at and disappears from GET /customers/:id/kyc/documents. The underlying object in storage is retained for audit / compliance.

Response 204

Empty body on success.

Errors

  • 403: caller does not own the customer.
  • 404: no document with that id belongs to this customer. Same response for "never existed" and "exists but belongs to a different customer" so cross-customer existence isn't leaked.
  • 503: document storage is not configured on this server.

Example

curl -X DELETE "$SEISMIC_ORCHESTRATION_URL/v0/customers/$CUSTOMER_ID/kyc/documents/8c9d…" \
  -H "Api-Key: $SEISMIC_ORCHESTRATION_API_KEY"

To replace a slot, upload a new file with the same (owner, kind, person_id?), the prior version is soft-deleted in the same call and the new file becomes the live row for that slot. You don't need to call DELETE first.

DocumentKind

One of:

  • business_formation: the filed formation document — for example Articles of Incorporation, Articles of Organization, or a Certificate of Incorporation. Must be the original document from the time of formation; a later document such as a Certificate Confirming Registration is not accepted.
  • articles_of_association: a constitutional document that outlines how the entity is governed, which includes details such as how many directors it should have and who can exert control. The title for this differs per region, but it is typically referred to as Articles of Association, Bylaws, or Memorandum of Association.
  • dba_registration: a government-issued DBA certificate or registration for the trade name in business.trade_name. Required at submit whenever business.trade_name is set; not relevant otherwise.
  • memorandum_of_association: memorandum (and articles) of association, for jurisdictions that use them instead of US-style incorporation documents.
  • bank_statement: the business's own statements from the last three full months. The last statement in the sequence should cover a period within 30 days of today. It should reasonably demonstrate the business' current operating activity and financial capacity; if it does not, also upload a ubo_bank_statement. The statement should also include both the entity name and the submitted operating address. If it does not, also upload a proof_of_address.
  • ubo_bank_statement: an ultimate beneficial owner's personal statements for the last three full months, evidencing the business' source of funds when it does not keep a business account of its own. The last statement in the sequence should cover a period within 30 days of today. They should reasonably demonstrate the business' current financial capacity as funded by that owner. Together with a proof_of_address, satisfies the financing-and-operations requirement when no bank_statement is uploaded.
  • ownership_information: a table listing all shareholders and their ownership percentages. Shareholders holding 25% or more must be listed by full legal name. Shareholders holding less than 25% may be grouped into named buckets (e.g. "Team Members", "Seed Investors", "Unallocated Reserves"). Percentages must sum to 100%. Must include a date and signature from an authorized signatory, such as a control person, lawyer, or CPA. If the entity is partially owned by other entities holding 25% or more, please a) include ownership information for each one, recursing until you reach the natural beneficial owners, and b) add business formation documents for each of the other entities under the Supplemental section.
  • shareholder_register: the entity's official shareholder / member register showing current holders and their stakes.
  • certificate_of_incumbency: a document confirming the entity's current officers, directors, managers, or authorized signatories. Unlike ownership_information, it names who is currently authorized to act for the entity, not only who owns it. Include full legal names, titles, and a current date, issued or certified by an authorized party. Must be dated and signed by a control person.
  • flow_of_funds: diagram showing your complete transaction flow. Must include payers and payees, existing financial service providers, and a highlighted zone where our platform comes into play. Must clearly document what party owns or controls each account or wallet through which funds move. Required at submit when the business KYB profile's operations.registered_msb is true or its operations.business_classifications includes marketplace or financial_services_provider.
  • proof_of_nature_of_business: evidence showing the business generating income from the type of business activities claimed by the entity (e.g. invoices, executed agreements). The document should reflect the business as the service provider, not the client. If the business is pre-revenue and does not yet have invoices or customer agreements, provide any alternative documentation demonstrating the business' preparations to operate.
  • compliance_screening: the written AML/CTF program — the controls operated, who is accountable for them, and how they are monitored. Output of screening run internally is accepted where there is no written policy.
  • Government ID kinds (see the rule below):
    • passport: photo page of a passport. Passports are single-sided. There is no passport_back.
    • drivers_licence_front / drivers_licence_back: each side of a driver's license.
    • identity_card_front / identity_card_back: each side of a national identity card.
    • government_issued_id: legacy catch-all for any government ID. Still accepted, but new integrations should use the specific kinds above.
  • selfie: a photo of the customer's face, taken the same day on their own device. Required on every individual submission.
  • proof_of_address: Must be a bank statement, utility bill, rent payment, or government letter issued within 60 days. If this is not available, we can also accept a lease agreement. Must clearly state the entity's name and submitted operating address. For a per-UBO upload: Must be a bank statement, utility bill, rent payment, or government letter issued within 60 days. If this is not available, we can also accept a residential lease agreement. Must clearly state the individual's full name and submitted address.
  • proof_of_source_of_funds: retired on 2026-10-07 in favour of ubo_bank_statement. New uploads are rejected with 400 and a document already on file no longer counts towards the financing-and-operations requirement; it is still returned by reads.
  • ubo_source_of_funds: documents showing where a beneficial owner's own funds came from, such as paystubs, W-2s, brokerage statements, asset sale agreements, or dividend vouchers. For a business whose KYB profile sets business.pre_revenue, which is funded by its owners rather than by revenue. If a ubo_bank_statement is also uploaded, both must belong to the same beneficial owner. Required at submit when business.pre_revenue is true.
  • tax_document: tax certificate, tax-residency letter.
  • licensed_vendors_attestation: the Provider Attestation, a list of the licensed or regulated vendors that play core roles in the business — no need to enumerate every vendor in the stack. Sign and date it with a control person. Required at submit when the business KYB profile's operations.licensed_vendors is true or its operations.business_classifications includes marketplace or financial_services_provider.
  • proof_of_licensure: official license certificate or authorization letter issued by the regulator, demonstrating the business is registered to provide its stated activities. Included in the verification review. Not required for business KYB (it is required only on the integrator's own org KYB).
  • supplemental: a document backing an entry in the profile's supplemental section (on both KycProfile and KybProfile). Accepts multiple documents — a customer may hold many — and no description is required at upload; the profile entry carries the title and description, and needs at least one of them at submit.
  • other: anything else the verification flow asks for during review. Unlike the slot kinds, other accepts multiple documents, and each upload must include a non-empty description (see POST /customers/:id/kyc/documents).

Government ID kinds

Two-sided IDs (driver's license, national identity card, residence permit, voter's card) upload each side as its own document: the front under the *_front kind and the back under the matching *_back kind. Passports are single-sided. Upload the photo page as passport. These apply both to individual customers and to per-UBO uploads on business customers, and every gov-ID upload must be a photo of the original document with at least an inch of space around all four sides. Scans and photocopies are not accepted.

government_issued_id predates the per-side kinds and remains accepted as a catch-all, but because each (owner, kind) slot holds a single live document, it can only hold one image. A second upload supersedes the first. Use the specific kinds so a two-sided ID's front and back each get their own slot.

Which kinds are required depends on the verification path the customer takes (jurisdiction, regulated status, ownership complexity). The submit-time validator surfaces specific gaps as missing[] paths under profile.documents (individuals) or under the relevant business / per-UBO slot (businesses).

KycDocumentRef

The reference returned on the KYC (individual) side, both for upload responses and for the documents list.

FieldTypeNotes
kindstringOne of the DocumentKind slugs.
document_idstring (UUID)Stable id. Use as the foreign key on profile slots that reference a document (e.g. profile.documents.government_issued_identification.front_image_document_id). Supporting documents are attached automatically and appear in the read-only profile list.
filenamestringOriginal upload filename. Trimmed at 512 chars.
content_typestringMIME type the uploader supplied. Echoed back, not re-sniffed.
size_bytesintegerExact byte count of the stored object.
descriptionstring | nullFree-text label supplied at upload. Present only for other documents. Omitted for every other kind.
proof_of_address_typestring | nullThe type declared on a proof_of_address at upload. Omitted for every other kind, and for a proof of address uploaded without one.
uploaded_atstring (ISO-8601)Server clock at upload, millisecond precision.

The read-only entries in profile.documents.supporting_documents[] are derived from uploads and ignored on profile writes. They stay lean: kind + document_id only. The broader metadata lives on this documents surface.

KybDocumentRef

The reference returned on the KYB (business) side, both for upload responses and for the documents list.

FieldTypeNotes
document_idstring (UUID)Stable id. Use as the foreign key on profile.associated_persons[].gov_id_front_image_document_id etc.
kindDocumentKindWhat the document represents.
filenamestringOriginal upload filename. Trimmed at 512 chars.
content_typestringMIME type the uploader supplied. Echoed back, not re-sniffed.
size_bytesintegerExact byte count of the stored object.
descriptionstring | nullFree-text label supplied at upload. Present only for other documents. Omitted for every other kind.
proof_of_address_typestring | nullThe type declared on a proof_of_address at upload. Omitted for every other kind, and for a proof of address uploaded without one.
uploaded_atstring (ISO-8601)Server clock at upload, millisecond precision.

The submit pipeline picks the latest live document per (owner, kind, person_id?) automatically when forwarding for verification, so the slot's content is always current, you don't need to chase a fresh document_id after replacing a file. References to soft-deleted document_id values stored on the profile won't break submit. The latest live row for that slot is still picked up.