Customers
Individuals and businesses, and how the customer object doubles as the verification intake.
One object, two kinds
A customer is your end user. kind is required, and there is no
default: a silently defaulted individual for a forgotten field is the wrong
record to fail into.
{
"kind": "individual",
"first_name": "Ana",
"last_name": "Silva",
"email": "ana@example.com",
"phone": "+14155550123",
"date_of_birth": "1993-04-12",
"nationality": "US",
"residence_country": "US",
"address": {
"street_line_1": "300 Post St",
"city": "San Francisco",
"state": "CA",
"postal_code": "94108",
"country": "US"
},
"id_document": {"kind": "passport", "number": "P123456"},
"economic_profile": {"tin": "078-05-1120", "tax_residence_country": "US"},
"tos_acceptance": {"ip_address": "203.0.113.7"},
"partner_reference_id": "user-8213"
}
Only a terms path is strictly required. Names are not, so that a partially known person can exist as a legal record before they can transact. In practice, send everything you have: date of birth, nationality, and address anchor the identity cross-checks, and anything you omit surfaces later in a requirement bucket.
{
"kind": "business",
"legal_name": "Kunde Inc",
"business_kind": "corporation",
"registration_number": "DE-12345",
"incorporation_date": "2019-03-01",
"vat_number": "US812345678",
"website": "https://kunde.example",
"controllers": [
{
"first_name": "Max",
"last_name": "Muster",
"date_of_birth": "1981-07-02",
"has_ownership": true,
"ownership_percentage": 60,
"has_control": true,
"is_signer": true,
"role": "ceo",
"id_document": {"kind": "passport", "number": "C9876"}
},
{
"first_name": "Erika",
"last_name": "Muster",
"is_director": true
}
],
"tos_acceptance": {"ip_address": "203.0.113.9"}
}
legal_name and a terms path are required.
controllers is one array with role flags, not parallel lists of
owners, directors, and signers. The same person is very often all three,
and parallel arrays force you to send them three times and keep three
copies in sync. Flags are has_ownership (with a required
ownership_percentage), has_control, is_signer, and is_director.
Anything downstream that wants separate lists gets them projected from
this one.
Responses omit the other kind's fields entirely. An individual payload has
no legal_name key at all, rather than a null.
The customer object is the verification intake
Two intake-only arrays ride customer create and PATCH. They are archived and staged for the customer's single applicant, never stored as customer fields.
{
"identifying_information": [
{
"kind": "passport",
"issuing_country": "USA",
"number": "P123456",
"image_front": "<base64>",
"image_back": "<base64, optional>"
}
],
"documents": [
{"purpose": "selfie", "file": "<base64>"},
{"purpose": "proof_of_address", "file": "<base64>"}
]
}
identifying_information[].kind:passport,id_card,drivers_license, orresidence_permit.issuing_countryis required on every entry, because a country is required per document, and failing here names the field instead of failing opaquely at submit.documents[].purpose:selfie,proof_of_address,registration_document, orother.- Images are base64, and
data:URIs are accepted. Up to ten of each per request.
Nothing here enters review on its own. Documents are staged. The only
call that submits them is
POST /api/customers/{id}/verifications/submit. See
Verification & Endorsements.
Requesting endorsements
Enrollment is explicit. Naming an endorsement is what starts that currency's compliance review at all:
{"endorsements": ["endo_usd"]}
Requesting endo_usd requires email, phone,
economic_profile.tin, and economic_profile.tax_residence_country in the
same request. Missing ones return 422 endorsement_profile_required
naming them, in sandbox as well as live, so integrations learn the contract
before go-live. You can request endorsements later with a PATCH.
Reading a customer
{
"id": "cus_033y1FhTOmoW9W2jAuSvQx",
"object": "customer",
"kind": "individual",
"first_name": "Ana",
"last_name": "Silva",
"email": "ana@example.com",
"address": {"state": "CA", "country": "US"},
"status": "active",
"tos": {
"status": "accepted",
"tos_version": "2026-08-01",
"accepted_at": "2026-08-26T09:14:40+00:00"
},
"endorsements": [
{
"name": "endo_usd",
"status": "approved",
"requirements": {
"complete": ["terms_of_service", "identity", "selfie", "proof_of_address", "aml"],
"uploaded": [],
"pending": [],
"missing": [],
"issues": []
}
}
],
"partner_reference_id": "user-8213",
"created_at": "2026-08-26T09:14:40+00:00"
}
endorsementsis the KYC reading. One entry per endorsement you requested. Itsstatusis the gate, and itsrequirementsbuckets show each item's exact state. Every entry is a bare documented code, never prose.statusis the account itself:pending,active,rejected, orfrozen. A frozen customer's calls are refused.
The payload is deliberately lean. Identity inputs you send (date of birth,
phone, documents, economic profile) are relayed for verification and never
echoed back, and the address trims to state and country. What you
submitted is yours to keep: the API reflects readiness, not a copy of the
dossier.
Updating
PATCH /api/customers/{id} updates identity fields and accepts the same
two document arrays. Supplying what a missing or issues bucket asks for
is an ordinary PATCH, followed by another submit.
Two PATCH semantics worth knowing:
- Nested objects merge.
{"address": {"state": "NY"}}fixes the state alone. Sending the whole object replaces every line it names, and"address": nullclears it. - Approved verification data is never overwritten by a later review. Your PATCH is always the correction path.
Set partner_reference_id to your own user id at creation. It is unique
per environment within your account, filterable on list endpoints, and the
cleanest way to join Rasto records to yours. A collision returns
409 partner_reference_id_exists.