Skip to main content
A “user” is one of your end clients — the person or company that owns a virtual account. Creating one with POST /v1/users kicks off identity verification (KYC for individuals, KYB for businesses). The field set you must send depends on two values in the payload: type (individual or business) and address_country (ISO alpha-3, e.g. USA, MEX). Together they put every user into one of four categories.
Two things matter and they are not the same. A bare payload (just the required-to-create fields) returns 201 — but the user will not be product-eligible: verification does not trigger and the user stays CREATED until the field set is complete. A complete user triggers verification automatically — but approval is a manual step, not instant; per Kira it can take up to 24 hours, and the sandbox can route the user to manual review (status: REVIEW, verification_status: in_review) at any point in that window, which a Kira operator resolves. Don’t build against a “verifies in seconds” assumption — see Known limitations. The authoritative, per-user list of what is still outstanding is the missing_fields map on GET /v1/users/{id} (product_code → [field tokens]) and on the user.created webhook. Treat that map as the source of truth; the tables below tell you what to send up front so you never hit it. See Known limitations and For AI agents.
Checking KYB status via the API. A client can check where a business stands without contacting Kira: GET /v1/users/{id} returns identifying_information[] listing the documents actually submitted — each entry’s documents[] carries the real file_name and uploaded_at timestamp Kira received — plus the missing_fields map (what’s still outstanding per product) and status / verification_status (the review state). This does not mean everything you submitted round-trips: scalar questionnaire fields (account_purpose, source_of_funds, expected_monthly_volume, expected_transaction_count, employment_status) are accepted on write but not returned by GET — persist those on your own side. See Known limitations.

The four user categories

type and address_country select the required field set: International categories drop the US-only fields (ssn, ein) and add a few of their own (immigration_status, international_entity_type, additional_info). The per-category fields are below.
Country codes are ISO-3 on users (USA, MEX) — not ISO-2. Recipients use ISO-2; do not mix them up. address_country drives category selection, so an ISO-2 value here misroutes the whole payload.

Minimum to create

POST /v1/users only enforces a handful of fields. Everything else is enforced later, by verification and by per-product eligibility.
Always send source_of_funds. It’s a required KYC/KYB questionnaire field, and it behaves differently by user type. For an individual, live-tested: omitting it does not by itself block verification from triggering, and does not cause an auto-reject — a fully complete individual missing only source_of_funds still triggered verification and landed in manual REVIEW. For a business, live-tested: it’s a blocking KYB field — a fully complete business missing only source_of_funds did not trigger verification and stayed CREATED with source_of_funds in missing_fields. Send it on every user regardless of type; see Known limitations.

Required fields by category

To get a user verified and product-eligible, send the full set for its category. Fill scalar gaps later with PUT /v1/users/{id} and re-submit uploads for document gaps; the product flips eligible: true once missing_fields is empty.

Shared identity fields

Sent by every user, individual or business:
expected_monthly_volume and expected_transaction_count are required for a virtual account — a sponsor-bank rule. They are not auto-filled. If either is absent when you create the user, it shows up in the missing_fields map on GET /v1/users/{id}, and POST /v1/virtual-accounts returns 422 with code: "missing_required_fields" and a top-level missing_fields array naming exactly which are missing — the account is not created in a silently-ineligible state. Send both on POST /v1/users (the enum sets differ for individuals vs businesses — see the tables below) or backfill with PUT /v1/users/{id} before creating the VA.
Identity
Some banking partners additionally require country_of_birth and an identity-document expiry date (identifying_information[].expiration). These two behave differently from the required fields described above, so send them whenever you have them:
  • They are reported in missing_fields, under the entry for the product that needs them — so you can see the gap before you create anything. They do not appear under products that don’t require them, and they do not block verification: a user missing only these still reaches VERIFIED.
  • They do not produce a 422. If you create the account anyway, POST /v1/virtual-accounts returns 201, the account stays in activating, and its status reason names the unmet condition — read it from GET /v1/virtual-accounts/{id}. Backfill with PUT /v1/users/{id} and create the virtual account again.
  • A user already onboarded at the partner is unaffected — an existing banking relationship is reused rather than re-checked.
Questionnaire
All USA individual fields apply, with these differences:
Entity identityCompany documents — all four groups must be covered. Verification requires a company document for each of the four groups below; a business with only the formation document stalls with the other three groups in missing_fields. One document can cover several groups at once — articles of incorporation / bylaws (file_bylaws) count for legal presence, company details, and control structure. The tokens below are the representative doc types missing_fields reports for each group; the listed alternatives satisfy the same group.Additional compliance documents. Some banking partners require supplementary documentation beyond the four groups above. Submit these as top-level documents[] entries ({ "type": "…", "file": … }) — they are not covered by missing_fields, so provide them proactively when known to be required:QuestionnaireAssociated persons (UBOs / directors)associated_persons is an array; each object carries the person’s own identity:
All USA business fields apply, with these differences:
The USA virtual-account products require different fields. They do not share an identical KYC set. Rather than hard-coding any one list, read the per-product missing_fields map on GET /v1/users/{id} and fill what it reports for the product you are creating. See For AI agents and the missing_fields example in Webhooks.

A product can require more than verification does

missing_fields is keyed by product, and a product may ask for fields that are not required to verify the user. This is deliberate: some sponsor banks require data the identity check itself does not. Two consequences worth designing for:
  • A user can reach VERIFIED while a product still reports missing fields. Verification completing is not a promise that every product is satisfied. Check missing_fields for the product you intend to open, not just the user’s status.
  • A gap on one product never blocks another. Fields required only by product A are invisible to product B’s missing_fields, to its eligible flag, and to whether verification can start at all.
So the reliable sequence is: create the user → let verification complete → re-read GET /v1/users/{id} and check missing_fields[<your product>] → fill what it reports → create the virtual account. Creating the account while that product still lists gaps is the common cause of an account that is accepted and then fails during activation at the bank.
Some products require documents that Kira does not generate for you — notably file_fatca, the signed W-9 or W-8BEN tax self-certification. Its federal tax classification cannot be derived from the data you send us, so it must come from the account holder. If a product needs it, missing_fields will say so.

Conditional field rules

missing_fields tokens encode these conditions; send the field only when the condition holds.

Biometric / liveness verification report

Some banking partners require a biometric verification report — a verification-result document proving a face match against the government ID — on a user’s record. Kira produces this itself from your identity provider; you do not upload a finished report. Two ways it is produced:
  • Send a selfie (face match). Include a selfie in identifying_information[].documents[] alongside the ID (the same document shape as front/back). Kira submits the selfie and the ID to the identity provider, which face-matches the selfie against the ID portrait; on approval the verification report is attached to the user automatically. No interactive session is required.
  • Have the user complete a liveness session. For a higher-assurance check that includes liveness (not possible from an uploaded image), call POST /v1/users/{user_id}/liveness-link to get hosted link(s), then relay each to the right end user. The request body is optional — omit it, or supply a redirect (see below) to send the user back to your own page afterward. Calling it again for a subject whose link is still active returns that same link rather than issuing a new one. The response is { "links": [...] } with one entry for an individual user and one per beneficial owner for a business; each entry carries a subject ("user" or "ubo"), a liveness_link, an expires_at (the link is valid for 7 days), and — for ubo subjects — a person_reference_id and name so you can route the link to the right person:
    When a user finishes, a user.liveness_completed webhook fires with data.result of "approved" or "rejected" (and, for a beneficial owner, the matching data.person_reference_id), and on approval Kira attaches the generated report to the user automatically. A liveness result never changes the user’s main KYC status. You can request the links during onboarding — a business’s UBO liveness links are issuable once its KYB has been submitted (the business need not be fully verified first, since liveness feeds the verification). The endpoint returns 422 when there’s no verification to attach the check to (an individual with no verification started yet, or a business whose KYB hasn’t been submitted or has no beneficial owners on record), or when the identity provider doesn’t support liveness for the subject.
  • Optional: send the user back to your page. Include a redirect object to land the user on your own page after the session instead of the default completion screen:
    success_url is required whenever you send a redirect (reject_url alone is rejected), and both URLs must be HTTPS. Send a field only if you mean it — an empty value is a 422, not “omitted”. This is opt-in per account: each URL’s host must be on your redirect allowlist (subdomains of an allowlisted domain are accepted). A non-HTTPS or non-allowlisted URL — or any redirect at all before your allowlist is set up — is rejected with 422 and no link is minted. The redirect is bound to the link when it’s issued, so a repeat call for a still-active subject keeps the original redirect; supply a different redirect to mint a fresh link with the new one.
    The allowlist is configured by the Kira team — there is no API or dashboard for it. To enable redirects, contact Kira support with the exact domains you want allowlisted (for example app.example.com), and for which environments (sandbox, production, or both). Until the team has registered them, every request carrying a redirect returns 422. Send the same request again once you’re told the allowlist is live — no code change is needed. Reach out the same way to add or remove a domain later.
    The redirect landing is a UX convenience, not the verification result. A user can reach your success_url before Kira has finalized the check. Always treat the user.liveness_completed webhook as the source of truth for the outcome — never the page the user landed on.

Country restrictions

All codes are ISO 3166-1 alpha-3. Rules apply to address_country unless noted.

Blocked

Users with address_country in this list are ineligible for any virtual-account product: AFG BLR MMR CAF CIV CUB COD IRN IRQ LBR LBY PRK RUS SAU SOM SDN SYR UKR YEM VEN ZWE

Enhanced due diligence (EDD)

When an EDD trigger applies, add a proof-of-address document (identifying_information: file_proof_of_address). Individuals (53 countries) — triggered when address_country is in: DZA AGO ARM AZE BHS BHR BGD BMU BOL VGB BRN BFA KHM CMR CXR CCK CRI CYP EGY SLV GIB GRC GTM GGY HTI HND IDN IMN JEY JOR KAZ KEN KWT LBN LIE MYS MCO MNG MOZ NIC NGA OMN PNG PRY PHL ZAF LKA THA TTO TUN UGA UZB VNM Businesses (51 countries) — the same list minus GTM and CCK. For businesses, proof of address is required if any one of these is true:
address_country is in the business EDD list OR formation_country is in the business EDD list OR the company was formed within the last 180 days.

Example payloads

Complete POST /v1/users bodies for each category. Adapt the values. Photos and files go in identifying_information[].documents[] as { "type": "front", "file": … } — note the key is file (not content), and government-ID photos nest under their parent ID entry rather than sitting loose in the array. Each file is a base64 data URI (data:image/jpeg;base64,…, JPEG/PNG/PDF) or — on version 2026-04-14+ — an https:// URL. See Create a user — API reference for the full document schema. The whole request body is capped at 10 MB, and inline base64 counts against it (≈ +33% overhead) — so for large or multiple files, pass file as an https:// URL (fetched server-side, up to 30 MB per file, and off the body budget) rather than base64; see Request size & large document uploads.
Document files must be real, retrievable bytes. Verification downloads each file at submission time — a placeholder or unreachable https:// URL (or junk base64) means the documents cannot be uploaded to the verification provider, and the user stays CREATED even though verification_triggered was true. Replace the <base64> placeholders below with actual file content before posting.

Troubleshooting a KYB that stalls

A business submission has two ways to stop that look nothing alike. Diagnose them in this order.

1. It never triggered — verification_triggered: false

The payload is incomplete for the category. The missing_fields map lists exactly what is outstanding per product; supply it with PUT /v1/users/{id} and verification triggers on its own. Iterate until missing_fields disappears from the response.
eligible: false on every product is expected while this is happening. Product eligibility requires the user to reach VERIFIED, so it stays false throughout submission and review. The signal that your payload is complete is missing_fields disappearing together with verification_triggered: true — not eligible.

2. It triggered but sits in REVIEW — a document was declined

Verification reached the provider and one of your documents was rejected as retryable. This is not a terminal REJECTED, so no user.verification.failed webhook fires and GET /v1/users/{id} shows the document as uploaded with no indication that it was declined.
Per-document rejection reasons are not exposed through the API today. A KYB can sit in REVIEW because one uploaded file was declined, and there is currently no endpoint, field, or webhook that tells you which file or why. identifying_information[].documents[] describes the upload — type, file_name, content_type, uploaded_at, and the download state for URL-sourced files — but carries no review outcome, so a declined document is indistinguishable from an accepted one. If a KYB lingers in REVIEW, contact your Kira representative with the user id and ask for the per-document review result. This is a known gap.
The most common cause is a name mismatch:
business_legal_name must match the company name printed on the documents you upload, character for character. A submission of "ACME HOLDINGS S.A.P.I. DE C.V." against a registration certificate whose registered name reads "ACME HOLDINGS" is declined as a company-data mismatch even though both name the same entity — the legal-form suffix is often recorded as a trade name, not the registered one. Read the registered name off the document and send exactly that. doing_business_as is the right place for the commercial variant.
Other frequent causes: a document that does not cover the group it was sent as (see the four groups above), an unreadable scan, or an expired certificate.

3. Beneficiaries are stuck

Each associated person is verified as its own applicant. Two failure modes are easy to hit and are covered in Known limitations: a partial PUT silently drops nationality and document_number, and omitting document_type submits the person as an ID card, which then demands a back image your passport-only upload never provides.

Bank intent (optional)

capabilities.requested_banks is an optional array recording which bank(s) this subclient plans to use. It’s intent, not authorization — sending a bank slug here does not grant your account access to it; authorization for a bank is a separate, account-level configuration. Omit capabilities entirely to use the standard verification path. PUT /v1/users/{id} also accepts capabilities.requested_banks and verification_mode. On a PUT, requested_banks merges with whatever is already stored — sending ["austin_capital_trust"] for a subclient that already declared ["portage"] results in both being recorded; a PUT can only add a bank, never remove one by omission. Any request body key this endpoint doesn’t define is rejected with 400 instead of being silently dropped. For most banks, declaring one this way just records the intent — no other effect. A small number of banks additionally require moving the subclient’s existing applicant to a stricter verification level; for those, a PUT that declares the bank triggers that move on the applicant already on file (no new applicant is created). When verification_mode is verification_link, a successful move returns a fresh verification_link; a subclient mid-review, or otherwise not yet eligible to move, returns 200 with a descriptive verification_link_error instead — never a 4xx/5xx for a legitimately deferred state. Re-sending the same PUT is also the retry channel: a deferred move is re-attempted on every request that declares the bank, and once the subclient is already at the target level a repeat request simply returns a fresh verification_link (useful when a previous link has expired). A returned link is not lost with the response that carried it — subsequent GET /v1/users/{id} calls keep returning it as verification_link while verification_mode is verification_link. verification_link_error is freeform, human-readable text, not a stable enum — don’t branch your integration on its wording. verification_link_error_severity is the machine-readable field for that: it accompanies verification_link_error on the PUT response with either deferred (nothing is wrong — the move is waiting on something expected, like the applicant’s current review finishing, or was correctly skipped by a business rule) or failed (the move hit a genuine problem).
  • Create a user — API reference — the endpoint, full schema, and live “Try it”.
  • For AI agentseligible_products / missing_fields, conditional tokens, getting a user to VERIFIED in sandbox.
  • Webhooks — the user.created payload with the per-product missing_fields map.
  • Known limitations — sandbox auto-verification behavior and status vs verification_status.