Core ConceptsVerification & Endorsements

Verification & Endorsements

Each endorsement is one currency's review of the customer: an explicit first submit, documents read at submit, and every ask a bare documented code.

The model

The endorsement is the verification. You request an endorsement by name (endo_usd for USD services), and the review of the customer for that currency is the whole verification. There is no separate identity check in front of it. Everything, from progress to asks to approval to adverse decisions, surfaces on that endorsement's status and requirements.

You share everything over the API. Profile fields, identity documents, and supporting documents ride the customer object on create and PATCH. We hold them, and nothing is reviewed until you submit. See Customers.

Each submission opens one verification round (ver_...) per endorsement it sends to review. Rounds are history. Build your UI on the endorsement.

Documents we ask for

We ask up front for every document the review needs. Once you request an endorsement, its missing bucket lists each document still owed, so you can collect them all before you submit. The submit returns 422 verification_incomplete while any is owed. Each code below is also the purpose you send the document under (see Customers), except identity, which rides identifying_information, and the controller_ codes, which ride each controller's own identifying_information and documents.

Individuals

DocumentOwed
identityAlways
selfieAlways
proof_of_addressWhen the customer lives outside the US, or where they live is not known yet. We go by address.country, else residence_country. A US resident may still send one, and we read it

Businesses, by legal form (business_kind):

business_kindDocuments owed
corporationregistration_document, articles_of_association, ownership_information, proof_of_signatory_authority, proof_of_address
llcregistration_document, articles_of_association, operating_agreement, ownership_information, proof_of_signatory_authority, proof_of_address
partnershipregistration_document, operating_agreement, ownership_information, proof_of_signatory_authority, proof_of_address
sole_propregistration_document, proof_of_tax_identification, proof_of_address
trustregistration_document, ownership_information, proof_of_signatory_authority, proof_of_address
cooperativeregistration_document, articles_of_association, proof_of_signatory_authority, proof_of_address
Not known yetregistration_document. We read the legal form from it, and the rest of the row follows

Every business also owes, for each controller, an identity document (controller_identity) and a proof of address (controller_proof_of_address).

To see a business's whole list from the start, send business_kind when you create it. Without it, missing names registration_document first, and the rest of the list appears once the submit reads the legal form from that document.

What each business document is:

CodeSend
registration_documentThe certificate of incorporation (corporation, cooperative), the certificate of formation or registration (LLC, partnership), the business license (sole proprietorship), or the trust agreement (trust)
articles_of_associationThe articles of incorporation or organization, the bylaws, or the memorandum of association
operating_agreementThe LLC operating agreement, or the partnership or limited-partnership agreement
ownership_informationA document showing who owns the business, such as a shareholder register or an ownership chart. For a trust, its ownership and formation documents
proof_of_signatory_authorityThe list of the people authorized to represent or sign for the business
proof_of_addressA recent proof of the business's operating address
proof_of_tax_identificationA document showing the business's taxpayer identification number, such as a tax filing

These are the documents every customer of a kind or legal form needs. The review can ask one customer for more, for example under enhanced due diligence. Those asks arrive in the requirement buckets like any other, and each has its own purpose:

CodeSend
proof_of_source_of_fundsEvidence of where the funds come from, such as a bank statement, payslip, or sale agreement. Either kind
evidence_of_good_standingA certificate of good standing, or the registry's equivalent
directors_registryAn official record of the directors and officers, such as a certificate of incumbency
regulatory_license_registrationThe license or registration for a regulated activity
aml_policyThe business's anti-money-laundering policy
annual_reportThe most recent annual report or statement filed with the registry
proof_of_tax_exemptionA document showing the business is exempt from tax
trade_name_registrationThe registration of the business's trading (doing-business-as) name

Submitting for review

The first submission is explicit. Its body takes an optional intake_method, which says how the customer's details and documents reach us:

intake_methodMeaning
apiThe default. We submit everything you sent through POST and PATCH /api/customers
hostedOne standing link per customer, valid for 90 days, that collects everything still owed, terms included. Not available yet: it returns 400 hosted_intake_unavailable

An empty body is the same as {"intake_method": "api"}:

curl -s $BASE/api/customers/cus_.../verifications \
  -H "Api-Key: $KEY" -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" -d '{}'

redirect_url belongs to hosted collection only. Sent without "intake_method": "hosted", it returns 422 invalid_parameter. Any other field, requested_info included, also returns 422 invalid_parameter. The response is a list with one round for each endorsement the call sent to review:

{
  "data": [
    {
      "id": "ver_033y1FhTOmoW9W2jAuSvQx",
      "object": "verification",
      "customer_id": "cus_033y1FhTOmoW9W2jAuSvQx",
      "endorsement": "endo_usd",
      "purpose": "full",
      "requested_info": null,
      "intake_method": "api",
      "status": "under_review",
      "url": null,
      "expires_at": null,
      "rejection_reasons": null,
      "created_at": "2026-08-26T09:15:32+00:00"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

A submit sends every requested endorsement that is incomplete or in issues. Endorsements already under review or decided are left alone.

RefusalRetry classMeaning
400 customer_tos_requiredactionableThe customer has no terms acceptance on record
400 endorsement_requiredactionableNo endorsement is requested. Request one on create or PATCH ("endorsements": ["endo_usd"]), then submit
400 hosted_intake_unavailableactionableHosted collection is not available yet. Returned ahead of every other refusal in this table. Omit intake_method, or send api, and share the customer's details and documents through the API
409 verification_not_neededterminalEvery requested endorsement is already under review or decided
422 verification_incompleteactionableSomething is still owed, or a document was not accepted. The message names each document we could not accept and why, then everything else owed, and param names the first
503 document_reading_unavailableretryableWe could not read the documents right now. Nothing was submitted. Retry

Documents are read at submit

Send the images, and we read the rest. At submit we read the newest document of each kind below, for the customer and for each controller of a business, then fill the fields you left empty. Selfies are not read, and neither are the business documents that carry no profile field (articles_of_association, operating_agreement, ownership_information, and proof_of_signatory_authority).

DocumentFills
Identity document (identifying_information)first_name, middle_name, last_name, date_of_birth, nationality, the document's kind, number, and issuing country, and address when no proof of address gives one
proof_of_addressaddress. For a business, its own proof of address fills physical_address, the operating address
registration_documentlegal_name, business_kind, registration_number, incorporation_date, and the registered address
proof_of_tax_identificationtax_id and tax_residence_country

A controller's identity document, proof of address, and proof of tax identification fill that person's fields the same way. A value you sent is never overwritten. It changes only through your own PATCH. Only what the documents could not supply is asked, in missing and in the submit's 422 verification_incomplete.

Each document is read once, and a newer document's reading replaces a value an older one filled. An address always comes from one document, so lines from two documents never mix. We read identity and registration documents first, so a proof of address or a tax document is checked against the name they carry, or the name you sent.

Contact data appears on no document, so requesting endo_usd needs it already on the customer or in the same request: email and phone for an individual, or email for a business. Missing ones return 422 endorsement_profile_required.

Tax data is not needed to request endo_usd, but the review needs it. In live, until you send tax_id, and tax_residence_country for an individual, or a proof_of_tax_identification that carries them, the endorsement lists them in missing and the submit's 422 verification_incomplete names them. Send them on create so the first submit is not held up.

A document we cannot accept is asked for again

Reading also checks the document. When the newest document for a requirement has one of these problems, that requirement moves to the issues bucket and the submit returns 422 verification_incomplete before anything is sent to review:

ProblemFix
It could not be readSend a clear, complete image or file
It has expiredSend a valid document
It is older than we accept, for a proof of addressSend a recent one
It carries a different name than the one we holdSend a document in the customer's or controller's name
It failed its authenticity checks, or is not the document requiredSend a genuine document of the kind asked for

The message names the document and the problem:

{
  "error": {
    "kind": "invalid_request_error",
    "code": "verification_incomplete",
    "message": "The proof of address was not accepted: it is older than we accept. Supply them via POST /api/customers or PATCH /api/customers/{id}, then submit.",
    "param": "proof_of_address",
    "request_id": "req_033xhsr1AZZxC8AOkK7y3W"
  }
}

Supply a new document with a PATCH, then submit. After the first submit, the same check runs on every PATCH that resubmits: a document we cannot accept is listed in issues, and nothing is sent to review until it is replaced.

After the first submit

You do not submit again to send a change. A PATCH that changes something resubmits by itself: a new document, a corrected value, or a newly requested endorsement. Every endorsement that is incomplete or in issues and owes nothing goes back to review in a new resubmission round, and the PATCH response shows the outcome.

  • A PATCH that changes nothing does nothing. The same file again or the same value again opens no round. Files are deduplicated by content hash, so send a new image when a document was refused.
  • A document added after approval is held, not sent. It shows in uploaded, and the approval stands.
  • Until the first submit succeeds, submit explicitly. A PATCH before then only updates what we hold.

Decisions arrive on the endorsement

EventMeaning
endorsement.approvedThe review passed. The currency's features open
endorsement.request_for_informationRetryable. The asks are in the buckets. Also fires when the endorsement returns to incomplete because something only you can supply is owed
endorsement.rejectedFinal adverse decision

Each event carries the endorsement object GET /api/customers/{customer_id} embeds, plus object: "endorsement" and the customer_id. There are no verification.* webhooks.

The review's word stands in both directions. It can ask for more on an endorsement it already approved. The endorsement then moves from approved to issues and endorsement.request_for_information fires. Anything that needs the endorsement approved, such as creating a virtual account in its currency, is refused until it is approved again.

When we cannot reach the review, the endorsement shows issues with the single code unavailable. No webhook fires, the round is marked expired because it never reached the review, and we retry automatically with a new round. You need not act.

The one channel: requirement buckets

Every ask is a bare snake_case code in exactly one of five buckets.

BucketMeaning
completeDone. Nothing owed
uploadedHeld by us until the next submission to review. Do not re-send
pendingSubmitted and under review
missingNever supplied. You owe it
issuesReceived but not usable, or a verdict blocking the endorsement

Before the first submit, missing holds compliance_review: the submit itself is owed. A requested endorsement with everything held reads:

{
  "name": "endo_usd",
  "status": "incomplete",
  "requirements": {
    "complete": ["terms_of_service"],
    "uploaded": ["identity", "proof_of_address", "selfie"],
    "pending": [],
    "missing": ["compliance_review"],
    "issues": []
  }
}

missing lists everything the review needs that you have not supplied, with three rules for documents:

  • A missing document is listed itself, such as identity, never the fields it would carry.
  • A field a held document will supply is not listed while that document waits to be read. Whatever the reading cannot yield is listed after the submit, and the submit's 422 verification_incomplete names it. A document we cannot accept moves to issues.
  • Optional documents are not owed. proof_of_tax_identification appears in a bucket only once you send one, except for a sole proprietorship, which owes it. Until then, the tax fields are asked as fields. Which documents are owed is in Documents we ask for.

For the endorsement's own statuses and transitions, see Statuses & Lifecycles.

Requirement codes

Buckets carry only the codes below: never prose, a field path, or a message from anyone else. A field code is satisfied by the customer field of the same name unless the table says otherwise.

Agreements and the review

CodeMeaning
terms_of_serviceThe customer has accepted our terms of service
compliance_reviewThe review of the customer for this currency: owed (missing) until submitted
verificationThe identity verification of the customer. Appears as one entry, in place of the document codes, on customers onboarded before documents were tracked one by one

Documents

CodeMeaning
identityA government-issued identity document, sent in identifying_information
selfieA selfie that matches the identity document, sent as {"purpose": "selfie"}
proof_of_addressA recent proof of the customer's address, or a business's operating address, sent as {"purpose": "proof_of_address"}
registration_documentThe business's registration document, sent as {"purpose": "registration_document"}
articles_of_associationThe business's articles of incorporation or organization, bylaws, or memorandum of association, sent as {"purpose": "articles_of_association"}
operating_agreementThe LLC operating agreement, or the partnership or limited-partnership agreement, sent as {"purpose": "operating_agreement"}
ownership_informationA document showing who owns the business, sent as {"purpose": "ownership_information"}
proof_of_signatory_authorityThe list of the people authorized to represent or sign for the business, sent as {"purpose": "proof_of_signatory_authority"}
controller_identityA government-issued identity document for every controller, in that controller's identifying_information
controller_proof_of_addressA recent proof of address for every controller, in that controller's documents
proof_of_tax_identificationA document showing the customer's taxpayer identification number, sent as {"purpose": "proof_of_tax_identification"}. Owed by a sole proprietorship. Otherwise optional: it appears once sent
controller_proof_of_tax_identificationA document showing every controller's taxpayer identification number, in that controller's documents. Optional: it appears once sent
proof_of_source_of_fundsEvidence of where the customer's funds come from, such as a bank statement, payslip, or sale agreement, sent as {"purpose": "proof_of_source_of_funds"}. Asked per risk or on request
evidence_of_good_standingA certificate of good standing, or the registry's equivalent, showing the business is active, sent as {"purpose": "evidence_of_good_standing"}
directors_registryAn official record of the business's directors and officers, such as a certificate of incumbency, sent as {"purpose": "directors_registry"}
regulatory_license_registrationThe license or registration under which the business carries on a regulated activity, sent as {"purpose": "regulatory_license_registration"}
aml_policyThe business's anti-money-laundering policy, sent as {"purpose": "aml_policy"}
annual_reportThe business's most recent annual report or statement filed with its registry, sent as {"purpose": "annual_report"}
proof_of_tax_exemptionA document showing the business is exempt from tax, such as a tax-exemption determination letter, sent as {"purpose": "proof_of_tax_exemption"}
trade_name_registrationThe registration of the business's trading (doing-business-as) name, sent as {"purpose": "trade_name_registration"}

Customer profile. In missing the field is absent. In issues it was supplied and refused. address is the residential address, or a business's registered one.

CodeMeaning
first_nameThe customer's first name
last_nameThe customer's last name
date_of_birthThe customer's date of birth
nationalityThe customer's nationality
emailThe customer's email address
phoneThe customer's phone number, in E.164
address_street_line_1First street line of the customer's address
address_street_line_2Second street line of the customer's address
address_cityCity of the customer's address
address_stateState or province of the customer's address
address_postal_codePostal code of the customer's address
address_countryCountry of the customer's address
id_document_numberThe identity document's number: id_document.number, or number on the identifying_information entry
id_document_kindThe identity document's kind (passport, id_card, drivers_license, or residence_permit), when it could not be read from the document: id_document.kind, or kind on the identifying_information entry
issuing_countryThe country that issued the identity document, on the identifying_information entry
tax_idThe customer's taxpayer identification number
tax_residence_countryThe customer's country of tax residence

Business profile

CodeMeaning
legal_nameThe business's registered legal name
trade_nameThe business's trading (doing-business-as) name
websiteThe business's website, as an absolute URL
business_kindThe business's legal form
business_industryThe business's industry, as NAICS codes
incorporation_dateThe date the business was incorporated
physical_addressThe business's principal operating address
account_purpose_otherWhat the account is for, when account_purpose is other
source_of_wealthWhere the business's wealth comes from
estimated_annual_revenue_usdThe business's estimated annual revenue, in USD
expected_monthly_payments_usdThe business's expected monthly payments, in USD
ownership_structureHow the business is owned
controllersThe business's owners and control persons, at least one with control (has_control)
controller_nameEvery controller's first and last name
controller_emailEvery controller's email address
controller_date_of_birthEvery controller's date of birth
controller_nationalityEvery controller's nationality
controller_addressEvery controller's complete residential address
controller_tax_idEvery controller's taxpayer identification number
controller_tax_residence_countryEvery controller's country of tax residence
controller_id_document_numberThe number on every controller's identity document
controller_id_document_kindThe kind of a controller's identity document, when it could not be read from the document: kind on that controller's identifying_information entry
controller_issuing_countryThe country that issued a controller's identity document, when it could not be read: issuing_country on that controller's identifying_information entry
source_of_wealth_descriptionWhere the business's wealth comes from, described in words

Enhanced due diligence. The review asks for it per customer, based on its own risk assessment.

CodeMeaning
enhanced_due_diligenceThe review requires enhanced due diligence for this customer: employment_status, occupation, source_of_funds, account_purpose, and expected_monthly_volume_usd. The codes beside it name the fields still absent
occupationThe customer's occupation
employment_statusThe customer's employment status
source_of_fundsWhere the customer's funds come from
account_purposeWhat the customer uses the account for
expected_monthly_volume_usdThe customer's expected monthly volume, in USD
source_of_funds_descriptionWhere the customer's funds come from, described in words

Asks the review could not pin to one field

CodeMeaning
profile_incompleteThe customer profile is missing information the review needs
profile_invalidA value on the customer profile was not accepted
passport_requiredThis customer's residence onboards with a passport, and the identity document supplied is not accepted for it. Send a passport in identifying_information

Verdicts. Nothing you send fixes these.

CodeMeaning
region_unsupportedWe do not serve this customer's region for this currency
declinedThe customer cannot be approved for this currency
contact_supportThe review is blocked for a reason we cannot name. Contact support
unavailableThe review could not be completed. It is retried automatically; you need not act

Verification rounds

Each submission opens one round per endorsement it sends. GET /api/customers/{customer_id}/verifications lists them, newest first. Their statuses are in Statuses & Lifecycles.

{
  "id": "ver_033y1FhTOmoW9W2jAuSvQx",
  "object": "verification",
  "customer_id": "cus_033y1FhTOmoW9W2jAuSvQx",
  "endorsement": "endo_usd",
  "purpose": "full",
  "requested_info": null,
  "intake_method": "api",
  "status": "request_for_information",
  "url": null,
  "expires_at": null,
  "rejection_reasons": [{"requirement": "proof_of_address", "retryable": true}],
  "created_at": "2026-08-26T10:02:11+00:00"
}
FieldMeaning
endorsementThe endorsement this round was submitted to, such as endo_usd. null only on a partner's own KYB
purposefull for the first submission to that endorsement's review, resubmission for every later one. Older rounds can carry rfi
requested_infoThe asks this round answers, as requirement codes. null on a first submission
intake_methodHow the round's material reached us: api or hosted. Every round you can create today is api
statussubmitted, under_review, request_for_information, approved, rejected, or expired. expired marks a round that will never be decided: an old one, or one that never reached the review. awaiting_input is reserved for hosted collection
urlReserved for hosted collection: the customer's standing link. Always null today
expires_atReserved for hosted collection: when that link stops accepting input. Always null today
rejection_reasonsOn an adverse decision, what to fix: requirement is a code from this page, or null for a verdict, and retryable says whether supplying something can fix it. Otherwise null

What to ask your user for:

requirementTypical fix
identityA clear, uncropped photo of the valid physical document, both sides, no screenshots
selfieA new selfie taken live, matching the submitted document
proof_of_addressA recent utility bill or bank statement showing the customer's address
registration_documentA current official company registration extract
null, with retryable: falseA verdict such as declined. Route the customer to your support path

Whether an outcome is retryable is decided per decision, on each reason plus the round's status. Never assume it from the requirement alone.

Rounds carry no extracted identity data. The endorsement carries readiness, and your own records carry the dossier.

Fixing an ask

The fix is a PATCH, and the PATCH is the resubmission. There is no special endpoint and no second call.

curl -s -X PATCH $BASE/api/customers/cus_... \
  -H "Api-Key: $KEY" -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"documents": [{"purpose": "proof_of_address", "file": "<base64>"}]}'

The endorsement in the response reads pending again, and a new round opens with purpose: "resubmission" and requested_info naming the asks it answers. If the endorsement still owes something, it stays in issues and the buckets say what.

What to render, per state

Endorsement statusBucketsRender
incompletemissing or issues non-emptyYour collection flow for the listed codes. When compliance_review is the only code left, submit
pendinganyA verification-in-progress waiting state
issuesonly unavailableThe waiting state. We retry automatically
issuesany other codeA fix-it screen for the listed codes. Your PATCH resubmits
approvedanythingDone. Open the currency's features. Codes in uploaded are held documents, not asks
rejectedanyA terminal state with your support path

Launch scope. Document sharing over the API (intake_method: "api") is the only intake today. Hosted collection, one standing link per customer that collects everything still owed, is coming. Reusable verification through token sharing returns after launch.

In sandbox you make the decisions, including "decision": "request_for_information" to rehearse the fix-it loop. See Sandbox Testing.