> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kirafin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Update a user

> Updates an existing user. **Every body field is optional** — only the fields you send are written (partial update), even though the method is `PUT`.

**Use `PUT`, NOT `PATCH`.** `PATCH /v1/users/{id}` → `403` with a misleading gateway auth error ("Invalid key=value pair … in Authorization header"); it means "use PUT", not that your `Authorization` header is wrong.

**Primary use:** fill scalar eligibility gaps after KYC so a product flips `eligible: true`. Read `GET /v1/users/{id}` first and clear the tokens in `missing_fields` (e.g. `account_purpose`, `immigration_status`, `additional_info:has_us_bank_account`). A scalar `PUT` clears scalar gaps but **NOT document gaps** — re-submit `identifying_information[].documents[]` for those.

**Field rules:**
- `account_purpose` / `source_of_funds` accept the **combined** individual + business enums on update (see the schema below). `expected_monthly_payments` is a free string; use `expected_monthly_volume` + `expected_transaction_count` for buckets.
- `immigration_status` is an exact string: `U.S. Citizen`, `Permanent U.S. Resident`, `Lawful Permanent Resident of U.S.`, `Non-Permanent U.S. Resident`, or `Non-Resident of U.S.`
- `additional_info.has_us_bank_account` / `has_denied_bank_account` are `Yes` / `No` strings (omitting them silently declares `No`).
- `current_employer` only when `employment_status = employed`.
- Do NOT send `ssn` for non-US individuals or `ein` for non-US businesses (use `international_entity_type`).
- `business_trade_name` is **not** updatable here; `company_name` is accepted as an alias for `business_legal_name`. The representative date-of-birth field is `representative_birth_date` (create uses `representative_date_of_birth`).
- `metadata` is a Stripe-style patch: keys shallow-merge, an empty-string value **deletes** that key, and `{}` **clears all**.

**Re-verification:** changing a field that is KYC/KYB-required for the user category re-triggers verification and sets `requires_reverification: true` in the response. `verification_link` users are never re-queued server-side.

**Response (`200`)** returns the user plus `updated_fields` (every field sent), `requires_reverification`, `verification_triggered`, `eligible_products[]`, `missing_fields`, and `warnings[]`. Document-upload failures do **not** fail the call — they surface in `warnings[]`. A successful update emits a `user.updated` webhook.

{/* version-stamp:start — generated by scripts/stamp-versions.ts, do not edit */}

<Update label="2026-04-14">Changed in this version.</Update>

{/* version-stamp:end */}


## OpenAPI

````yaml /openapi/kira-api.2026-04-14.json put /v1/users/{user_id}
openapi: 3.1.0
info:
  title: Kira API
  version: '2026-04-14'
  description: >-
    REST API for users, KYC/KYB verification, virtual accounts, payouts,
    recipients, and webhooks. Every request requires an `x-api-key` header and a
    bearer access token (see Authentication). Pin your account to version
    `2026-04-14` before integrating.
  contact:
    name: Kira API Support
    email: support@kirafin.ai
servers:
  - url: https://api.balampay.com
    description: Production
  - url: https://api.balampay.com/sandbox
    description: Sandbox
security:
  - bearerAuth: []
    apiKeyAuth: []
tags:
  - name: Authentication
  - name: Versioning
  - name: Users
  - name: Virtual Accounts
  - name: Quotations
  - name: Payouts
  - name: Recipients
  - name: Reference
paths:
  /v1/users/{user_id}:
    put:
      tags:
        - Users
      summary: Update a user
      description: >-
        Updates an existing user. **Every body field is optional** — only the
        fields you send are written (partial update), even though the method is
        `PUT`.


        **Use `PUT`, NOT `PATCH`.** `PATCH /v1/users/{id}` → `403` with a
        misleading gateway auth error ("Invalid key=value pair … in
        Authorization header"); it means "use PUT", not that your
        `Authorization` header is wrong.


        **Primary use:** fill scalar eligibility gaps after KYC so a product
        flips `eligible: true`. Read `GET /v1/users/{id}` first and clear the
        tokens in `missing_fields` (e.g. `account_purpose`,
        `immigration_status`, `additional_info:has_us_bank_account`). A scalar
        `PUT` clears scalar gaps but **NOT document gaps** — re-submit
        `identifying_information[].documents[]` for those.


        **Field rules:**

        - `account_purpose` / `source_of_funds` accept the **combined**
        individual + business enums on update (see the schema below).
        `expected_monthly_payments` is a free string; use
        `expected_monthly_volume` + `expected_transaction_count` for buckets.

        - `immigration_status` is an exact string: `U.S. Citizen`, `Permanent
        U.S. Resident`, `Lawful Permanent Resident of U.S.`, `Non-Permanent U.S.
        Resident`, or `Non-Resident of U.S.`

        - `additional_info.has_us_bank_account` / `has_denied_bank_account` are
        `Yes` / `No` strings (omitting them silently declares `No`).

        - `current_employer` only when `employment_status = employed`.

        - Do NOT send `ssn` for non-US individuals or `ein` for non-US
        businesses (use `international_entity_type`).

        - `business_trade_name` is **not** updatable here; `company_name` is
        accepted as an alias for `business_legal_name`. The representative
        date-of-birth field is `representative_birth_date` (create uses
        `representative_date_of_birth`).

        - `metadata` is a Stripe-style patch: keys shallow-merge, an
        empty-string value **deletes** that key, and `{}` **clears all**.


        **Re-verification:** changing a field that is KYC/KYB-required for the
        user category re-triggers verification and sets
        `requires_reverification: true` in the response. `verification_link`
        users are never re-queued server-side.


        **Response (`200`)** returns the user plus `updated_fields` (every field
        sent), `requires_reverification`, `verification_triggered`,
        `eligible_products[]`, `missing_fields`, and `warnings[]`.
        Document-upload failures do **not** fail the call — they surface in
        `warnings[]`. A successful update emits a `user.updated` webhook.
      operationId: put_v1-users-user-id
      parameters:
        - in: path
          name: user_id
          required: true
          schema:
            type: string
            format: uuid
          description: User UUID.
        - in: header
          name: X-Api-Version
          required: false
          description: >-
            Optional. The date-versioned API version to apply for this request
            (e.g. `2026-04-14`). When sent it always wins, even over your pinned
            account default. When omitted, the API uses your account's pinned
            version if set, otherwise a baseline default.
          schema:
            type: string
            example: '2026-04-14'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateUserRequest'
            examples:
              update-user-eligibility-fields:
                summary: Update User — eligibility fields
                value:
                  immigration_status: Non-Resident of U.S.
                  additional_info:
                    has_us_bank_account: 'No'
                    has_denied_bank_account: 'No'
                  current_employer: Acme
                  account_purpose: receive_payments
                  occupation: Engineer
      responses:
        '200':
          description: Success.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdateUserResponse'
        '400':
          description: Validation error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
        '401':
          description: Unauthorized.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: >-
            Conflict — reserved for uniqueness violations. Note: user `email` is
            NOT enforced unique today — creating or updating two users with the
            same email succeeds; deduplicate emails on your side if your product
            requires it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearerAuth: []
          apiKeyAuth: []
components:
  schemas:
    UpdateUserRequest:
      type: object
      description: >-
        Partial update — every field is optional; only the fields you send are
        written. Accepts both individual and business fields, with combined
        enums for `source_of_funds` / `account_purpose` / volume / count. Any
        key not defined by this schema is rejected with `400` — it is never
        silently dropped.
      properties:
        capabilities:
          type: object
          description: >-
            Per-subclient bank *intent* — merged (union) with whatever is
            already stored. A PUT can only ADD a bank; it never removes a
            previously-declared one by omission. Optional; omit it to leave the
            subclient's declared banks unchanged.
          properties:
            requested_banks:
              type: array
              items:
                type: string
                enum:
                  - portage
                  - slovak_savings_bank
                  - austin_capital_trust
              description: Bank slugs this subclient intends to use.
        verification_mode:
          type: string
          enum:
            - automatic
            - verification_link
          description: >-
            `automatic` (default) leaves the existing verification flow
            unchanged. `verification_link` only returns a hosted KYC URL
            (`verification_link`) when this request also newly declares a bank
            that requires moving the subclient's applicant to a stricter level,
            and that move succeeds — otherwise `verification_mode` alone has no
            visible effect on the response.
        first_name:
          type: string
          minLength: 1
        middle_name:
          type: string
        last_name:
          type: string
          minLength: 1
        birth_date:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
          description: Date of birth, `YYYY-MM-DD`. Must be 18+.
        nationality:
          type: string
          minLength: 3
          maxLength: 3
          description: ISO **alpha-3** nationality.
        gender:
          type: string
          enum:
            - male
            - female
            - other
        immigration_status:
          type: string
          description: >-
            Exact string, e.g. `U.S. Citizen`, `Permanent U.S. Resident`,
            `Non-Resident of U.S.`
        business_legal_name:
          type: string
          minLength: 1
        company_name:
          type: string
          minLength: 1
          description: Backward-compatible alias for `business_legal_name`.
        doing_business_as:
          type: string
        business_type:
          type: string
          enum:
            - limited_liability_company
            - corporation
            - general_partnership
            - limited_liability_partnership
            - sole_proprietor
            - non_profit
            - trust
            - government_organization
            - publicly_traded_company
            - llc
            - sole_prop
            - nonprofit
            - government
            - publicly_traded
            - llp
            - partnership
            - sa_de_cv
            - ltda
            - cooperative
          description: >-
            Legal structure (deprecated aliases accepted). Note:
            `business_trade_name` is NOT updatable via this endpoint.
        business_industry:
          type: array
          items:
            type: string
            enum:
              - crop_production
              - animal_production
              - forestry_logging
              - fishing_hunting_trapping
              - support_activities_agriculture_forestry
              - oil_gas_extraction
              - mining_except_oil_gas
              - support_activities_mining
              - utilities
              - construction_of_buildings
              - heavy_civil_engineering_construction
              - specialty_trade_contractors
              - food_manufacturing
              - beverage_tobacco_product_manufacturing
              - textile_mills
              - textile_product_mills
              - apparel_manufacturing
              - leather_allied_product_manufacturing
              - wood_product_manufacturing
              - paper_manufacturing
              - printing_related_support_activities
              - petroleum_coal_products_manufacturing
              - chemical_manufacturing
              - plastics_rubber_products_manufacturing
              - nonmetallic_mineral_product_manufacturing
              - primary_metal_manufacturing
              - fabricated_metal_product_manufacturing
              - machinery_manufacturing
              - computer_electronic_product_manufacturing
              - electrical_equipment_appliance_component_manufacturing
              - transportation_equipment_manufacturing
              - furniture_related_product_manufacturing
              - miscellaneous_manufacturing
              - merchant_wholesalers_durable_goods
              - merchant_wholesalers_nondurable_goods
              - wholesale_electronic_markets_agents_brokers
              - motor_vehicle_parts_dealers
              - furniture_home_furnishings_stores
              - electronics_appliance_stores
              - building_material_garden_equipment_supplies_dealers
              - food_beverage_stores
              - health_personal_care_stores
              - gasoline_stations
              - clothing_accessories_stores
              - sporting_goods_hobby_book_music_stores
              - general_merchandise_stores
              - miscellaneous_store_retailers
              - nonstore_retailers
              - air_transportation
              - rail_transportation
              - water_transportation
              - truck_transportation
              - transit_ground_passenger_transportation
              - pipeline_transportation
              - scenic_sightseeing_transportation
              - support_activities_transportation
              - postal_service
              - couriers_messengers
              - warehousing_storage
              - publishing_industries
              - motion_picture_sound_recording_industries
              - broadcasting
              - internet_publishing_broadcasting
              - telecommunications
              - data_processing_hosting_related_services
              - other_information_services
              - monetary_authorities_central_bank
              - credit_intermediation_related_activities
              - securities_commodity_contracts_financial_investments
              - insurance_carriers_related_activities
              - funds_trusts_other_financial_vehicles
              - real_estate
              - rental_leasing_services
              - lessors_nonfinancial_intangible_assets
              - professional_scientific_technical
              - management_of_companies
              - administrative_support_services
              - waste_management_remediation_services
              - educational_services
              - ambulatory_health_care_services
              - hospitals
              - nursing_residential_care_facilities
              - social_assistance
              - performing_arts_spectator_sports
              - museums_historical_sites
              - amusement_gambling_recreation_industries
              - accommodation
              - food_services_drinking_places
              - repair_maintenance
              - personal_laundry_services
              - religious_grantmaking_civic_professional_organizations
              - private_households
              - public_administration
          description: Array of NAICS-subsector slugs.
        business_description:
          type: string
        business_website:
          type: string
          format: uri
        formation_date:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        formation_state:
          type: string
        formation_country:
          type: string
          minLength: 3
          maxLength: 3
        representative_first_name:
          type: string
        representative_last_name:
          type: string
        representative_title:
          type: string
        representative_birth_date:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
          description: >-
            Representative date of birth, `YYYY-MM-DD`. On create this field is
            `representative_date_of_birth`.
        representative_ssn:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
          description: E.164 format, e.g. `+14155551234`.
        document_type:
          type: string
        document_number:
          type: string
        document_country:
          type: string
          minLength: 3
          maxLength: 3
        address_street:
          type: string
        address_street_2:
          type: string
        address_city:
          type: string
        address_state:
          type: string
        address_zip_code:
          type: string
        address_country:
          type: string
          minLength: 3
          maxLength: 3
          description: ISO **alpha-3** country.
        residential_address:
          type: object
          description: >-
            Nested residential address (V1). You may instead use the flat
            `address_*` fields.
          properties:
            street_line_1:
              type: string
            street_line_2:
              type: string
            city:
              type: string
            subdivision:
              type: string
            postal_code:
              type: string
            country:
              type: string
              minLength: 3
              maxLength: 3
              description: >-
                Country as an ISO 3166-1 alpha-3 code, exactly 3 letters (e.g.
                `USA`, `MEX`, `BRA`). A 2-letter value (e.g. `US`) is rejected
                with "Must be a 3-letter country code". The same alpha-3 format
                applies to every other country field on this schema
                (`address_country`, `formation_country`, `document_country`, and
                the `associated_persons[]` address/document country fields).
        ssn:
          type: string
          description: US individuals only — do NOT send for non-US individuals.
        cpf:
          type: string
          description: Brazil individual tax ID.
        curp:
          type: string
          description: Mexico individual ID.
        rfc:
          type: string
          description: Mexico tax ID.
        ein:
          type: string
          description: US businesses only — do NOT send for non-US businesses.
        cnpj:
          type: string
          description: Brazil business tax ID.
        tax_id:
          type: string
          description: Generic international tax ID.
        tax_id_type:
          type: string
          description: >-
            Free-text label for the type of the value in `tax_id` (e.g. `"rfc"`,
            `"ein"`). Unvalidated string, not a fixed enum — confirmed by
            successfully setting an arbitrary label (accepted and reported in
            the response's `updated_fields`). For a validated, typed tax
            identifier, prefer `identifying_information[]` instead.
        source_of_funds:
          type: string
          enum:
            - salary
            - self_employment_income
            - investment_proceeds
            - savings_inheritance_gift
            - business_loans
            - inter_company_funds
            - owners_capital
            - sales_of_goods_and_services
            - tax_refund
            - third_party_funds
            - treasury_reserves
            - company_funds
            - gifts
            - inheritance
            - investments_loans
            - pension_retirement
            - sale_of_assets_real_estate
            - savings
            - someone_elses_funds
          description: Combined individual + business enum.
        account_purpose:
          type: string
          enum:
            - receive_payments
            - manage_professional_income
            - make_payments
            - manage_personal_funds
            - investment_trading
            - charitable_donations
            - ecommerce_retail_payments
            - investment_purposes
            - purchase_goods_and_services
            - receive_payments_for_goods_and_services
            - internal_treasury
            - third_party_money_transmission
            - receive_salary
            - receive_payment_for_freelancing
            - operating_a_company
            - payments_to_friends_or_family_abroad
            - personal_or_living_expenses
            - protect_wealth
          description: Combined individual + business enum.
        expected_monthly_payments:
          type: string
          description: >-
            Free-form. Prefer `expected_monthly_volume` +
            `expected_transaction_count`.
        expected_monthly_volume:
          type: string
          enum:
            - less_than_10000
            - 10000_to_49999
            - 50000_to_199999
            - 200000_to_999999
            - 1000000_or_more
            - less_than_50000
            - 50000_to_100000
            - 100000_to_500000
            - 500000_to_1000000
            - 1000000_to_5000000
            - 5000000_to_10000000
            - more_than_10000000
          description: Combined individual + business buckets.
        expected_transaction_count:
          type: string
          enum:
            - 1_to_10
            - 11_to_50
            - 51_to_200
            - more_than_200
            - less_than_10
            - 10_to_25
            - 26_to_50
            - 51_to_100
            - 101_to_500
            - more_than_500
          description: Combined individual + business buckets.
        employment_status:
          type: string
          enum:
            - employed
            - self_employed
            - unemployed
            - retired
            - student
        occupation:
          type: string
        current_employer:
          type: string
          description: Send only when `employment_status = employed`.
        income_source:
          type: string
          description: >-
            Free-text description of the individual's income source. Unlike
            `source_of_funds` (a fixed enum), this field is unvalidated —
            confirmed by successfully setting a value outside the
            `source_of_funds` enum list (accepted and reported in the response's
            `updated_fields`).
        pep_status:
          type: boolean
          description: >-
            Whether the user is a Politically Exposed Person (PEP). Boolean —
            sending a string instead of a boolean is rejected with "Expected
            boolean, received string".
        high_risk_industries:
          type: string
          enum:
            - 'Yes'
            - 'No'
          description: >-
            Business-only KYB attestation, `Yes` or `No`: does the business
            operate in a high-risk industry? Same field as
            `CreateUserRequest.high_risk_industries`.
        is_nbfi_vasp:
          type: string
          enum:
            - 'Yes'
            - 'No'
          description: >-
            Business-only KYB attestation, `Yes` or `No`: is the business a
            Non-Bank Financial Institution (NBFI) or Virtual Asset Service
            Provider (VASP)? Same field as `CreateUserRequest.is_nbfi_vasp`.
        business_legal_history:
          type: string
          enum:
            - 'Yes'
            - 'No'
          description: >-
            Business-only KYB attestation, `Yes` or `No`: has the business ever
            been involved in legal proceedings? Same field and enum as
            `CreateUserRequest.business_legal_history`.
        transaction_countries:
          type: array
          items:
            type: string
          minItems: 1
          description: >-
            Business-only. The countries this business expects to transact with,
            as ISO 3166-1 codes. Some virtual-account onboarding routes require
            it; when it is absent the requirement is reported in
            `missing_fields` rather than inferred, so a business that never
            declared its markets is not recorded as trading anywhere.
        tos_accepted_version:
          type: string
          description: Terms-of-service version the user accepted.
        corporation_taxed_as:
          type: string
        llc_taxed_as:
          type: string
        international_entity_type:
          type: string
          description: Free-text entity type for non-US businesses.
        government_document_type:
          type: string
        additional_info:
          type: object
          properties:
            has_us_bank_account:
              type: string
              enum:
                - 'Yes'
                - 'No'
            has_denied_bank_account:
              type: string
              enum:
                - 'Yes'
                - 'No'
          additionalProperties:
            type: string
          description: >-
            Free-form string map. International users include
            `has_us_bank_account` / `has_denied_bank_account` as `Yes`/`No`
            (case-sensitive; lowercase is not accepted by ACT provisioning).
        identifying_information:
          type: array
          description: >-
            Tax IDs and government documents to add or replace. Each entry
            requires `type` and `issuing_country`.
          items:
            type: object
            required:
              - type
              - issuing_country
            properties:
              type:
                type: string
                enum:
                  - national_id
                  - drivers_license
                  - matriculate_id
                  - military_id
                  - permanent_residency_id
                  - state_or_provincial_id
                  - visa
                  - passport
                  - ssn
                  - itin
                  - ein
                  - curp
                  - rfc
                  - cpf
                  - cnpj
                  - tin
                  - nuip
                  - nit
                  - tax_id
                  - proof_of_address
                  - business_formation
                  - source_of_wealth
                  - ein_letter
                  - bylaws
                  - corporate_resolution
                  - certificate_of_registration
                  - certificate_of_good_standing
                  - board_minutes
                  - portfolio_statement
                description: >-
                  Kind of identity document, tax identifier, or business
                  document. Same value set and advisory-gap behavior as the
                  individual-branch `identifying_information[].type` (see that
                  field), plus business-file types only valid here (attach a
                  file in `documents[]`): `ein_letter`, `bylaws`,
                  `corporate_resolution`, `certificate_of_registration`,
                  `certificate_of_good_standing`, `board_minutes`,
                  `portfolio_statement`. Full value list: government photo IDs
                  (`passport`, `national_id`, `drivers_license`,
                  `matriculate_id`, `military_id`, `permanent_residency_id`,
                  `state_or_provincial_id`, `visa`); tax/identity numbers sent
                  in `number` (`ssn`, `itin`, `ein`, `curp`, `rfc`, `cpf`,
                  `cnpj`, `tin`, `nuip`, `nit`, `tax_id`); and file-attached
                  documents (`proof_of_address`, `business_formation`,
                  `source_of_wealth`, plus the business-file types above).
              issuing_country:
                type: string
                minLength: 3
                maxLength: 3
                description: ISO **alpha-3**. Required.
              number:
                type: string
              description:
                type: string
              expiration:
                type: string
                pattern: ^\d{4}-\d{2}-\d{2}$
                description: Expiry, `YYYY-MM-DD`.
              documents:
                type: array
                items:
                  type: object
                  properties:
                    type:
                      type: string
                      enum:
                        - front
                        - back
                        - selfie
                        - file_proof_of_address
                        - file_business_formation
                        - file_source_of_wealth
                        - file_ein_letter
                        - file_bylaws
                        - file_corporate_resolution
                        - file_certificate_of_registration
                        - file_certificate_of_good_standing
                        - file_board_minutes
                        - file_portfolio_statement
                        - file_fatca
                        - file_company_fiscal_registration
                      description: >-
                        Role of this file within the parent
                        identifying-information entry. Government-ID entries
                        must include **both** `front` and `back` — single-sided
                        types (`passport`, `visa`) may send only `front`.
                        `selfie` is a face-match photo: send it in `documents[]`
                        alongside the ID and Kira submits both to the identity
                        provider, which matches the selfie against the ID
                        portrait; on a pass the resulting biometric verification
                        report is attached to the user automatically, with no
                        interactive session required. (For a higher-assurance
                        check that also proves liveness, use `POST
                        /v1/users/{user_id}/liveness-link` instead.) The
                        `file_*` roles each carry the matching supporting
                        document (`file_proof_of_address` a proof of residence —
                        for individuals and business owners alike — plus
                        formation docs, EIN letter, bylaws, corporate
                        resolution, certificate of registration / good standing,
                        board minutes, portfolio statement). `file_fatca`
                        carries the signed tax self-certification (W-9 or
                        W-8BEN) — Kira does not generate this for you, because
                        the form's federal tax classification cannot be derived
                        from the data you send us — and
                        `file_company_fiscal_registration` the tax-registration
                        certificate. Which of these a given user needs depends
                        on the product: read `missing_fields` for the product
                        you are opening rather than uploading the whole set.
                    file:
                      type: string
                      description: >-
                        Document content. **Two forms are accepted:**


                        - **Base64-encoded file** — a data URI of the form
                        `data:<mime>;base64,<payload>`. Supported MIME types:
                        `image/jpeg`, `image/png`, `application/pdf`. The bytes
                        are uploaded to storage synchronously while the request
                        runs.

                        - **HTTPS URL** — a link to a hosted file (`https://…`;
                        plain `http://` is rejected on every version). Accepted
                        only on API version `2026-04-14` or later. Kira fetches
                        the file **asynchronously**: the host must be on the
                        client's allowed-domains list (defaults to `aiprise.com`
                        / `api.aiprise.com` when none is configured) and the
                        file is re-checked (MIME type + a 30 MB per-file size
                        cap) before it is stored. While the fetch is in flight
                        the stored document carries `download_status:
                        "pending"`; a permanent failure emits a
                        `user.document.download.failed` webhook and is reported
                        in the response `warnings[]` — it never fails the
                        create/update call.


                        **Request-body cap (10 MB):** the whole request is
                        limited to 10 MB and inline base64 counts against it (≈
                        +33% overhead). For large files prefer the `https://`
                        URL form: it is fetched asynchronously, does NOT count
                        toward the body limit, and accepts files up to 30 MB
                        each.
                      pattern: >-
                        ^(data:(image/jpeg|image/png|application/pdf);base64,.+|https://.+)$
                      examples:
                        - data:image/jpeg;base64,/9j/4AAQSkZJRg...
                        - https://api.aiprise.com/documents/passport-front.jpg
                    description:
                      type: string
                  required:
                    - type
                    - file
        associated_persons:
          type: array
          description: >-
            UBOs and authorized signers (businesses). Merged by `email`. Note
            the phone field here is `phone_number`.
          items:
            type: object
            properties:
              first_name:
                type: string
              middle_name:
                type: string
              last_name:
                type: string
              email:
                type: string
                format: email
              birth_date:
                type: string
                pattern: ^\d{4}-\d{2}-\d{2}$
              nationality:
                type: string
              country_of_birth:
                type: string
                description: >-
                  Country of birth of the associated person (ISO 3166-1
                  alpha-3). Must not be blank when supplied.
              occupation:
                type: string
                description: >-
                  Occupation of the associated person. Required by some sponsor
                  banks to activate the person; must not be blank when supplied.
              pep_status:
                type: boolean
                description: >-
                  Whether this person is, or has been, a politically exposed
                  person (PEP). Required by some sponsor banks, which ask it of
                  every beneficial owner and signer and refuse the person
                  without an answer. Send it for each associated person: Kira
                  does not answer it on your behalf, and `false` is a real
                  answer — omitting the field is not the same as declaring
                  `false`.
              gender:
                type: string
                enum:
                  - male
                  - female
                  - other
                description: >-
                  Gender of the associated person. Required by some sponsor
                  banks to verify the person.
              phone_number:
                type: string
              address_street:
                type: string
                maxLength: 70
              address_city:
                type: string
              address_state:
                type: string
              address_zip_code:
                type: string
              address_country:
                type: string
              has_ownership:
                type: boolean
                description: >-
                  Whether this associated person holds an equity stake in the
                  business. When true, also set `ownership_percentage` (0-100).
                  Same field as
                  `CreateUserRequest.associated_persons[].has_ownership`.
              ownership_percentage:
                type: number
                minimum: 0
                maximum: 100
                description: >-
                  This person's ownership stake in the business, as a
                  whole-number percentage from 0 to 100 (e.g. 25 means 25%). Not
                  a 0-1 fraction. 150 is rejected with "Number must be less than
                  or equal to 100".
              has_control:
                type: boolean
                description: >-
                  Whether this associated person exercises management control
                  over the business (e.g. an officer or director), independent
                  of equity. Distinct from `has_ownership` (equity stake, see
                  `ownership_percentage`) and `is_signer` (authorized signer).
                  Boolean; a person can be any combination of the three (all
                  three accepted `true` together on one person).
              is_signer:
                type: boolean
                description: >-
                  Whether this associated person is an authorized signer for the
                  business (can sign/act on its accounts), independent of equity
                  ownership (`has_ownership`) and management control
                  (`has_control`). Boolean.
              title:
                type: string
              document_type:
                type: string
              document_number:
                type: string
              document_country:
                type: string
                minLength: 3
                maxLength: 3
                description: >-
                  Issuing country of `document_type`/`document_number`, as an
                  ISO 3166-1 alpha-3 code, exactly 3 letters (e.g. `USA`,
                  `MEX`). A 2-letter value is rejected with "Must be a 3-letter
                  country code".
              ssn:
                type: string
              tax_id:
                type: string
        documents:
          type: array
          description: >-
            Legacy flat document array. Prefer
            `identifying_information[].documents[]`.
          items:
            type: object
            required:
              - type
              - file
            properties:
              type:
                type: string
                description: >-
                  Document role/type. Free-form on this legacy array (e.g.
                  `front`, `file_proof_of_address`). Prefer
                  `identifying_information[].documents[]`, which validates the
                  role against a fixed set.
              file:
                type: string
                description: >-
                  Document content. **Two forms are accepted:**


                  - **Base64-encoded file** — a data URI of the form
                  `data:<mime>;base64,<payload>`. Supported MIME types:
                  `image/jpeg`, `image/png`, `application/pdf`. The bytes are
                  uploaded to storage synchronously while the request runs.

                  - **HTTPS URL** — a link to a hosted file (`https://…`; plain
                  `http://` is rejected on every version). Accepted only on API
                  version `2026-04-14` or later. Kira fetches the file
                  **asynchronously**: the host must be on the client's
                  allowed-domains list (defaults to `aiprise.com` /
                  `api.aiprise.com` when none is configured) and the file is
                  re-checked (MIME type + a 30 MB per-file size cap) before it
                  is stored. While the fetch is in flight the stored document
                  carries `download_status: "pending"`; a permanent failure
                  emits a `user.document.download.failed` webhook and is
                  reported in the response `warnings[]` — it never fails the
                  create/update call.


                  **Request-body cap (10 MB):** the whole request is limited to
                  10 MB and inline base64 counts against it (≈ +33% overhead).
                  For large files prefer the `https://` URL form: it is fetched
                  asynchronously, does NOT count toward the body limit, and
                  accepts files up to 30 MB each.
                pattern: >-
                  ^(data:(image/jpeg|image/png|application/pdf);base64,.+|https://.+)$
                examples:
                  - data:image/jpeg;base64,/9j/4AAQSkZJRg...
                  - https://api.aiprise.com/documents/passport-front.jpg
              file_name:
                type: string
              description:
                type: string
        metadata:
          type: object
          additionalProperties:
            type: string
            maxLength: 500
          description: >-
            Stripe-style patch: keys shallow-merge, an empty-string value
            deletes that key, `{}` clears all. ≤ 50 keys; key 1–40 chars, no `[`
            or `]`.
      additionalProperties: false
    UpdateUserResponse:
      allOf:
        - $ref: '#/components/schemas/UserResponse'
      description: The updated user plus the update result.
      properties:
        updated_fields:
          type: array
          items:
            type: string
          description: Every field present in the request body (the fields written).
        requires_reverification:
          type: boolean
          description: >-
            True when a KYC/KYB-required field for the user category changed,
            re-triggering verification.
        verification_triggered:
          type: boolean
          description: Whether a verification job was enqueued by this update.
        warnings:
          type: array
          items:
            type: string
          description: >-
            Non-fatal issues (e.g. a document that failed to upload). Present
            only when non-empty.
    ValidationErrorResponse:
      type: object
      description: >-
        Validation / request error. The body shape is **not uniform** across the
        API — it varies by endpoint and by which validation layer rejects the
        request. The fields below are the union of what may appear; treat them
        all as optional. Observed shapes include `{ code, message }`, `{ code,
        message, errors[] }`, `{ code, error, details[] }`, `{ error, details[]
        }`, and a nested `{ error: { code, message, details } }`. Always branch
        on the HTTP status, not on a fixed body shape.
      properties:
        code:
          type: string
        message:
          type: string
        error:
          type:
            - string
            - object
          description: >-
            A short error label (e.g. `"Invalid data"`), or on some endpoints a
            nested `{ code, message, details }` object.
        errors:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              message:
                type: string
            required:
              - field
              - message
        details:
          type:
            - array
            - object
          description: >-
            Per-issue detail. Shape varies by endpoint — typically an array of
            `{ message }` or `{ path, message, code }`, occasionally an object.
    ErrorResponse:
      type: object
      properties:
        message:
          type: string
        code:
          type: string
        statusCode:
          type: number
        error:
          type: string
        timestamp:
          type: string
        path:
          type: string
        details: {}
      required:
        - message
    UserResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: User UUID.
        type:
          type: string
          enum:
            - individual
            - business
          description: '`individual` or `business`.'
        email:
          type: string
          format: email
          description: Email address.
        status:
          type: string
          description: >-
            Lifecycle status: `CREATED`, `VERIFYING`, `REVIEW`, `VERIFIED`, or
            `REJECTED`. Gate product readiness on this, not on
            `verification_status`.
        verification_status:
          type: string
          enum:
            - unverified
            - started
            - in_review
            - verified
            - rejected
            - needs_action
          default: unverified
          description: >-
            KYC/KYB status: `unverified`, `started`, `in_review`, `verified`,
            `rejected`, or `needs_action`.
        verification_mode:
          type: string
          description: '`automatic` or `verification_link`.'
        verification_link:
          type: string
          description: Hosted KYC URL — present only in `verification_link` mode.
        verification_link_error:
          type: string
          description: >-
            Populated if hosted-link generation failed, or on PUT
            /v1/users/{user_id} when a bank-migration triggered by the request
            was deferred or failed. Freeform, human-readable text — branch on
            verification_link_error_severity, not this string.
        verification_link_error_severity:
          type: string
          enum:
            - deferred
            - failed
          description: >-
            Machine-readable companion to verification_link_error, present only
            alongside it on the PUT /v1/users/{user_id} response. `deferred`
            means nothing is wrong — the triggered bank migration is waiting on
            something expected or was correctly skipped by a business rule.
            `failed` means the migration hit a genuine problem.
        created_at:
          type: string
          format: date-time
          description: Creation timestamp (ISO 8601).
        updated_at:
          type: string
          format: date-time
          description: Last-update timestamp (ISO 8601).
        metadata:
          type: object
          additionalProperties: {}
          description: >-
            Your key-value pairs, plus any client-level default metadata
            configured for your account (merged at create time; your request
            keys win on conflict).
        first_name:
          type: string
          description: Given name (individual users).
        last_name:
          type: string
          description: Family name (individual users).
        middle_name:
          type: string
          description: Middle name (individual users), when provided.
        phone:
          type: string
          description: Contact phone in E.164 form (e.g. `+525512345678`).
        birth_date:
          type: string
          description: Date of birth, `YYYY-MM-DD` (individual users).
        nationality:
          type: string
          description: >-
            Nationality as an ISO **alpha-3** country code (e.g. `MEX`, `USA`).
            Users use alpha-3; recipients use alpha-2 — map accordingly.
        gender:
          type: string
          enum:
            - male
            - female
            - other
          description: '`male`, `female`, or `other`, when provided.'
        residential_address:
          type: object
          description: >-
            The individual's address. **The response renests and renames the
            flat create-request fields:** `address_street` → `street_line_1`,
            `address_state` → `subdivision`, `address_zip_code` → `postal_code`,
            `address_city` → `city`, `address_country` → `country`. Parse this
            nested shape — the flat `address_*` request fields do not
            round-trip.
          properties:
            street_line_1:
              type: string
            street_line_2:
              type: string
            city:
              type: string
            subdivision:
              type: string
            postal_code:
              type: string
            country:
              type: string
              description: >-
                Country as an ISO 3166-1 alpha-3 code (e.g. `USA`), returned as
                stored from the alpha-3 value sent on create/update.
        formation_country:
          type: string
        business_legal_name:
          type: string
        company_name:
          type: string
        business_type:
          type: string
        business_trade_name:
          type: string
        business_description:
          type: string
        business_industry:
          type: array
          items:
            type: string
        registered_address:
          type: object
          properties:
            street_line_1:
              type: string
            street_line_2:
              type: string
            city:
              type: string
            subdivision:
              type: string
            postal_code:
              type: string
            country:
              type: string
        physical_address:
          type: object
          properties:
            street_line_1:
              type: string
            street_line_2:
              type: string
            city:
              type: string
            subdivision:
              type: string
            postal_code:
              type: string
            country:
              type: string
        has_material_intermediary_ownership:
          type: boolean
        account_purpose:
          type: string
          description: >-
            Write-only in practice: accepted on create/update but currently NOT
            returned by GET — do not depend on reading it back.
        source_of_funds:
          type: string
          description: >-
            Write-only in practice: accepted on create/update but currently NOT
            returned by GET — do not depend on reading it back.
        eligible_products:
          type: array
          description: >-
            Per-product eligibility, computed from the user's supplied fields
            against the product matrix — present on the create response and on
            the single-resource GET (`GET /v1/users/{user_id}`), even before
            verification. On the list (`GET /v1/users`), present only when the
            request passed `include_eligibility=true`; omitted otherwise. Each
            product carries `eligible` (false until the user reaches the
            product's minimum status, e.g. VERIFIED) and its own
            `missing_fields`. Verification flips `eligible` to true and clears
            `missing_fields`; it does not populate the array. A product that is
            `eligible: false` for a reason no field can clear carries
            `unsupported_reason` instead of `missing_fields` — verification will
            not flip it.
          items:
            type: object
            properties:
              product_id:
                type: string
                description: Product UUID.
              product_code:
                type: string
                description: >-
                  Product code, e.g. `usa-virtual-accounts`,
                  `usa-virtual-accounts-act`.
              product_name:
                type: string
                description: Human-readable product name.
              eligible:
                type: boolean
                description: Whether the user can open this product now.
              missing_fields:
                type: array
                items:
                  type: string
                description: Field tokens still required for this product.
              unsupported_reason:
                type: string
                enum:
                  - enhanced_due_diligence_required
                description: >-
                  Present only when `eligible` is `false` for a reason no field
                  can clear — a capability not offered for this user yet, rather
                  than an incomplete profile. Mutually exclusive with
                  `missing_fields`: check this first, because supplying fields
                  will not change the verdict. `enhanced_due_diligence_required`
                  — the business's industry requires enhanced due diligence
                  (EDD), which is not supported yet; `POST /v1/virtual-accounts`
                  returns `422` with the same explanation. Branch on this value,
                  not on the message text.
        missing_fields:
          type: object
          description: >-
            Map of `product_code` → outstanding field tokens, plus a `general`
            key with the deduplicated union across products. It is populated
            before verification too; the sole exception is that this top-level
            aggregate is omitted on the create response for
            `verification_link`-mode users or when there are zero gaps.
          additionalProperties:
            type: array
            items:
              type: string
        capabilities:
          type: object
          description: >-
            Per-subclient bank intent — which banks this subclient has declared
            it needs. Always present on `GET /v1/users/{id}`, including `{
            "requested_banks": [] }` when nothing is declared — never omitted
            there. On the list (`GET /v1/users`), present only when the request
            passed `include_eligibility=true`; omitted otherwise.
          properties:
            requested_banks:
              type: array
              items:
                type: string
              description: >-
                Banks this subclient has expressed intent to use, e.g.
                `portage`, `slovak_savings_bank`, `austin_capital_trust`. This
                records intent only — it does not by itself authorize the
                subclient for a bank your account isn't already authorized for.
          required:
            - requested_banks
      required:
        - id
        - type
        - email
        - status
        - created_at
        - updated_at
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Access token from `POST /auth` (the `data.access_token` value).
    apiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: API key issued by Kira. Required on every request, including `/auth`.

````