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

# Onboarding Lifecycle

> End-to-end onboarding flow for partners and customers. Covers customer creation, terms and conditions, identification, status tracking, and activation.

Iron gives you full visibility into your customer onboarding: clear status at every stage, written feedback from our compliance team when something needs fixing, and real-time insight into your customer's payment abilities.

## Steps to Onboard and Activate a Customer

<Steps>
  <Step title="Create a new customer">
    `POST /api/customers`

    Create a customer record. Present the terms and conditions next, before you create an identification.

    The status the customer is created in depends on the API version you send:

    | `X-API-Version`            | Created status                                |
    | -------------------------- | --------------------------------------------- |
    | `2026-08-01` or later      | `SigningsRequired` — terms are the first step |
    | Earlier, or header omitted | `IdentificationRequired`                      |

    <Note>
      The version is pinned to the customer when you create it, and it drives that customer's onboarding for its lifetime. Existing customers keep the behaviour they were created with, so adopting `2026-08-01` only affects customers you create after you start sending it. See [API Versioning](/versioning).
    </Note>

    <Accordion title="API Example">
      <CodeGroup>
        ```bash theme={null}
        curl --request POST \
             --header 'content-type: application/json; charset=utf-8' \
             --header 'idempotency-key: <unique-request-id>' \
             --header 'x-api-key: <your-api-key>' \
             --data '{"name": "new_amazing_customer", "email": "customer@example.com", "customer_type": "Person"}' \
             --url 'https://api.sandbox.iron.xyz/api/customers'
        ```
      </CodeGroup>
    </Accordion>
  </Step>

  <Step title="Present and sign the terms and conditions">
    `GET /api/terms-and-conditions?country={ISO3}`

    Ask your customer for their country with a form field or selector, then fetch and present the matching terms. Do not derive the country from their IP address. The terms contain the data sharing agreement, so your customer must accept them before Iron collects KYC data. Record acceptance for each document via `POST /api/customers/{id}/signings`. See [Terms and Conditions](#terms-and-conditions).

    <Accordion title="API Example">
      <CodeGroup>
        ```bash Get terms for a country theme={null}
        curl --request GET \
             --header 'accept: application/json; charset=utf-8' \
             --header 'x-api-key: <your-api-key>' \
             --url 'https://api.sandbox.iron.xyz/api/terms-and-conditions?country=DEU'
        ```

        ```bash Record the signing theme={null}
        curl --request POST \
             --header 'accept: application/json; charset=utf-8' \
             --header 'content-type: application/json; charset=utf-8' \
             --header 'idempotency-key: <unique-request-id>' \
             --header 'x-api-key: <your-api-key>' \
             --data '{
               "content_id": "<id_from_terms_response>",
               "signed": true
             }' \
             --url 'https://api.sandbox.iron.xyz/api/customers/<customer_id>/signings'
        ```
      </CodeGroup>

      **Example response from terms-and-conditions:**

      <CodeGroup>
        ```json theme={null}
        [
          {
            "display_name": "Terms and Conditions",
            "id": "019ababb-ddd6-7f02-8f5d-7469a0e8afb6",
            "url": "https://example.com/terms-and-conditions"
          }
        ]
        ```
      </CodeGroup>

      Pass the `url` to your customer for review. The signing request takes `content_id` (the `id` from the terms response) and `signed: true`.
    </Accordion>
  </Step>

  <Step title="Verify the customer's identity">
    `POST /api/customers/{id}/identifications/v2`

    Create an identification using one of these methods:

    * Hosted Iron KYC link
    * SumSub token sharing
    * Outsourcing

    <Warning>
      This endpoint is not an upsert. Every call creates a new identification record, and the newest record drives the customer's status. If you create a new identification while another is in flight, the customer resets to `IdentificationRequired` and the older identification stops counting, even if it is approved later.

      Create one identification, then wait for it to reach a terminal status (`Approved`, `Declined`, or `Expired`) before creating another. To track progress, subscribe to the `identification_status` webhook or poll `GET /api/customers/{id}/identifications`. Never re-call this endpoint to refresh status.
    </Warning>

    <Accordion title="API Example">
      <CodeGroup>
        ```bash theme={null}
        curl --request POST \
             --header 'accept: application/json; charset=utf-8' \
             --header 'content-type: application/json; charset=utf-8' \
             --header 'idempotency-key: <unique-request-id>' \
             --header 'x-api-key: <your-api-key>' \
             --url 'https://api.sandbox.iron.xyz/api/customers/<customer_id>/identifications/v2' \
             --data '{"type": "Link"}'
        ```
      </CodeGroup>
    </Accordion>
  </Step>

  <Step title="KYC is approved">
    Once approved, Iron validates the signed terms against the verified region. If the terms match and nothing else is outstanding, the customer moves straight to `Active`. If not, the customer status is `SigningsRequired`.
  </Step>

  <Step title="Sign any outstanding documents">
    `GET /api/customers/{id}/required-signings`

    Check the customer `status` after approval. `Active` means nothing is outstanding. `SigningsRequired` means documents are waiting: call `required-signings`, present each returned document to your customer, for example the correct region's terms after a mismatch, and mark each as signed via `POST /api/customers/{id}/signings`. The endpoint derives the region from the approved identification, so use it for every signing after KYC.

    When the customer requires no signings, the response depends on your API version: `2026-08-01` and later return `200` with an empty list, earlier versions return `409 Conflict`.

    <Warning>
      An empty list does **not** always mean "nothing to sign". Before identification is approved there is no verified region to derive terms from, so a customer in `SigningsRequired` also returns `200 []`. Treat an empty list on a `SigningsRequired` customer as *"ask the customer for their country"* and fetch the terms with [`GET /api/terms-and-conditions?country={ISO3}`](#terms-and-conditions). Only an empty list on an `Active` customer means nothing is outstanding. Sandbox behaves the same way, so you can rehearse this before going live.
    </Warning>

    <Accordion title="API Example">
      <CodeGroup>
        ```bash Retrieve signings theme={null}
        curl --request GET \
             --header 'accept: application/json; charset=utf-8' \
             --header 'x-api-key: <your-api-key>' \
             --url 'https://api.sandbox.iron.xyz/api/customers/<customer_id>/required-signings'
        ```

        ```bash Sign a document theme={null}
        curl --request POST \
             --header 'accept: application/json; charset=utf-8' \
             --header 'content-type: application/json; charset=utf-8' \
             --header 'idempotency-key: <unique-request-id>' \
             --header 'x-api-key: <your-api-key>' \
             --data '{
               "content_id": "<id_from_required_signings>",
               "signed": true
             }' \
             --url 'https://api.sandbox.iron.xyz/api/customers/<customer_id>/signings'
        ```
      </CodeGroup>

      **Example response from required-signings:**

      <CodeGroup>
        ```json theme={null}
        [
          {
            "display_name": "Terms and Conditions",
            "id": "019ababb-ddd6-7f02-8f5d-7469a0e8afb6",
            "url": "https://example.com/terms-and-conditions"
          }
        ]
        ```
      </CodeGroup>

      Pass the `url` to your customer for review. The signing request takes `content_id` (the `id` from `required-signings`) and `signed: true`.
    </Accordion>
  </Step>

  <Step title="Customer is activated">
    Once all required signings are complete, the customer status becomes `Active`.
  </Step>

  <Step title="Check payout rail availability">
    `abilities.fiat_payout` is nested by currency, then by rail. Check the specific rail you plan to use, for example `abilities.fiat_payout.usd.ach === "Active"`, before initiating payouts.

    ```json theme={null}
    {
      "fiat_payout": {
        "usd": { "ach": "Active", "wire": "Active", "rtp": "Unavailable", "fednow": "Unavailable", "swift": "Active" },
        "eur": { "sepa": "Active", "swift": "Active" },
        "gbp": { "fps": "Active", "chaps": "Active", "swift": "Active" }
      }
    }
    ```

    Each rail also has a `_thirdparty` variant (e.g. `ach_thirdparty`) for payouts to a third party. The abilities object also returns `fiat_deposit` with the same shape and a `currencies` array listing the flows (mint, redeem, onramp, offramp, swap) available per currency.
  </Step>
</Steps>

<Note>
  Before an active customer can transact, register their wallet addresses for [Travel Rule](/travel-rule) compliance. Self-hosted wallets register with a signed proof-of-ownership message, or, for US and Rest of World customers, by [self-attestation](/crypto-addresses#register-a-self-attested-crypto-address). See the [Crypto Addresses guide](/crypto-addresses).
</Note>

<Note>
  A customer's status reverts from `Active` to `SigningsRequired` or `IdentificationRequired` when new compliance actions are required (e.g. updated terms and conditions, fraud review, enhanced due diligence).
</Note>

## Terms and Conditions

The terms and conditions contain the data sharing agreement between Iron and your customer. Your customer must accept them before Iron collects KYC data, so present the terms right after creating the customer and before creating an identification.

### Fetching Terms Before KYC

`GET /api/terms-and-conditions?country={ISO3}`

| Parameter | Required | Description                                                    |
| --------- | -------- | -------------------------------------------------------------- |
| `country` | Yes      | ISO 3166-1 alpha-3 code of the customer's country (e.g. `DEU`) |

Ask your customer for their country with a form field or selector. Do not use IP geolocation: a traveler or VPN user would receive the wrong terms, and the signed terms must match the region that identification verifies later.

Iron maps the country to a terms region (USA, UK, EEA, Canada, Australia, or rest of world) and returns that region's documents. The response is a list with `id`, `url`, and `display_name`, the same shape as `required-signings`. Present each `url` to your customer, then record acceptance via `POST /api/customers/{id}/signings` with `content_id` and `signed: true`.

`required-signings` derives the region from an existing identification, so use `GET /api/terms-and-conditions?country={ISO3}` before KYC and `required-signings` after.

A malformed country code returns `400` with a plain string body:

```json theme={null}
"Invalid country code. Expected a 3-letter ISO 3166-1 alpha-3 code."
```

### Validation After KYC

After the identification is approved, Iron compares the signed terms with the region of the verified identification. Countries in the same region share one set of terms, so a mismatch only happens across regions. On a mismatch, the customer status is `SigningsRequired` and `GET /api/customers/{id}/required-signings` returns the correct terms to present and sign. Because `required-signings` reads the region from the approved identification, it returns the right version after KYC.

<Note>
  The mismatched signing is kept for audit, not removed. `GET /api/customers/{id}/signings` therefore lists both the original signing and the re-signed terms. If you check signing state yourself, match on the `content_id` that `required-signings` returned rather than on the presence of any signing.
</Note>

## Handling Missing Information

If an identification is incomplete, the customer's status is `IdentificationRequired` and a `url` is returned on the Identification object. Redirect your customer to this URL. It opens a hosted step-up flow that collects only the missing data.

This occurs when:

* A data point is found to be invalid, expired, or inconsistent
* A limit triggers additional due diligence requirements
* A Business submission is created without all required documents or beneficiary proofs of address (see [Incomplete Submissions](/kyb#incomplete-submissions))

## Mapping Onboarding Statuses in Your App

Use the customer's `status` and `identification_status` together to drive a three-stage progress stepper. Both fields are returned on the customer object.

| `status`                 | `identification_status` | Stage             | What to show                                                                                      |
| ------------------------ | ----------------------- | ----------------- | ------------------------------------------------------------------------------------------------- |
| `SigningsRequired`       | None                    | Submission        | Terms first (`2026-08-01`). Ask for the customer's country, present its terms, record the signing |
| `IdentificationRequired` | None                    | Submission        | Present terms for the customer's country, record the signing, then prompt to start KYC            |
| `IdentificationRequired` | `Pending`               | Submission        | Prompt customer to complete KYC                                                                   |
| `IdentificationRequired` | `Expired`               | Submission        | Previous attempt expired. Prompt to restart                                                       |
| `IdentificationRequired` | `Declined`              | Submission        | Show `review_comment`, prompt to retry                                                            |
| `IdentificationRequired` | `Processed`             | Compliance Review | Show waiting state. No customer action needed                                                     |
| `IdentificationRequired` | `PendingReview`         | Compliance Review | Show waiting state. Under active review                                                           |
| `SigningsRequired`       | `Approved`              | Activation        | KYC approved. Signings outstanding (e.g. terms for the verified region). Call `required-signings` |
| `Active`                 | `Approved`              | Complete          | Fully onboarded. Customer can transact                                                            |

<Note>
  Typical compliance review turnaround is 24-48 hours. Customers can re-enter `SigningsRequired` at any time (e.g. updated terms, or terms signed for a different region than KYC verified). Use the `abilities` endpoint to confirm the specific banking rail you need is `Active` (e.g. `abilities.fiat_payout.usd.ach`). That's when the customer is truly ready to transact.
</Note>

### Tracking EDD Status

Each identification includes a `with_edd` field that indicates whether Enhanced Due Diligence was applied. This field is an optional boolean:

* `true`: EDD was triggered (either by the partner or automatically by Iron's AML checks)
* `false`: EDD was explicitly not required
* `null`: Identification was created before this feature was available

`with_edd` can be set in two ways:

1. **Partner-initiated.** Pass `with_edd: true` (Link flow) or include `edd_questionnaire` (Token/Person flow) when creating the identification. See [Proactively Increasing Customer Limits](/limits-and-minimum#proactively-increasing-customer-limits).
2. **Automatically by Iron.** If AML checks determine EDD is required (e.g. customer resides in a high-risk jurisdiction), Iron sets `with_edd` to `true` server-side.

Use `status` and `with_edd` together to understand where a customer is in the verification process:

| `status`        | `with_edd`       | Meaning                                                           |
| --------------- | ---------------- | ----------------------------------------------------------------- |
| `Pending`       | `null` / `false` | Standard KYC in progress                                          |
| `Pending`       | `true`           | EDD flow in progress                                              |
| `PendingReview` | `null` / `false` | Standard KYC under manual review                                  |
| `PendingReview` | `true`           | EDD complete at verification provider, awaiting compliance review |
| `Approved`      | `null` / `false` | Standard KYC approved, no EDD                                     |
| `Approved`      | `true`           | Approved through EDD                                              |
| `Declined`      | `null` / `false` | Standard KYC rejected                                             |
| `Declined`      | `true`           | Rejected after EDD review                                         |

### Edge Cases

| `status`       | Meaning                                                                              |
| -------------- | ------------------------------------------------------------------------------------ |
| `Suspended`    | Blocked. Can happen at any stage. Contact support.                                   |
| `UserRequired` | Partner-area user must be created first. Only applies if `requires_user` is enabled. |

### Displaying Onboarding Comments to Your Customer

When a customer's KYC submission is incomplete or needs correction, our onboarding team writes feedback explaining what to fix. This feedback is available on the identification object via `review_comment` and `step_status.*.comment` fields. Display these comments to your customer so they know exactly what to fix before resubmitting.

Show these comments when `identification_status` is `Pending` or `Declined`.

<Warning>
  Do not display `step_status` results directly to the customer. The per-step breakdown (e.g. "identity: Declined, selfie: Approved") creates confusion and support tickets. Instead, extract the comment fields and combine them into a single message.
</Warning>

<Accordion title="Implementation example">
  **Example identification response with feedback:**

  <CodeGroup>
    ```json theme={null}
    {
      "id": "abc-123",
      "status": "Declined",
      "with_edd": false,
      "review_comment": "Please provide a clearer photo of your ID.",
      "step_status": {
        "identity": { "result": "Declined", "comment": "Document is blurry", "retry": true },
        "selfie": { "result": "Approved", "comment": null, "retry": false },
        "questionnaire": { "result": "Approved", "comment": null, "retry": false }
      }
    }
    ```
  </CodeGroup>

  **Extract and display all comments in a single message box:**

  <CodeGroup>
    ```typescript theme={null}
    function getVerificationMessages(identification: Identification): string[] {
      const messages: string[] = [];
      if (identification.step_status) {
        for (const step of Object.values(identification.step_status)) {
          if (step?.comment) { messages.push(step.comment); }
        }
      }
      if (identification.review_comment) {
        messages.push(identification.review_comment);
      }
      return messages;
    }
    ```
  </CodeGroup>
</Accordion>

***

## API Endpoints

| Endpoint                                       | Purpose                                                                                                          |
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `POST /api/customers`                          | Create the customer record                                                                                       |
| `GET /api/customers/{id}`                      | Customer object with `status` and `identification_status`                                                        |
| `POST /api/customers/{id}/identifications/v2`  | Create an identification (`Link`, `Token`, `Person`, or `Business`)                                              |
| `GET /api/customers/{id}/identifications`      | Identifications with `status`, `step_status`, and `review_comment`                                               |
| `GET /api/customers/{id}/required-signings`    | Documents outstanding after KYC. Returns `409` when nothing is outstanding                                       |
| `POST /api/customers/{id}/signings`            | Record acceptance of one document                                                                                |
| `GET /api/customers/{id}/abilities`            | Customer capabilities: `fiat_deposit` and `fiat_payout` (nested by currency then rail) plus a `currencies` array |
| `GET /api/terms-and-conditions?country={ISO3}` | Current terms documents for a country. Use before an identification exists                                       |

<Note>
  `IDEMPOTENCY-KEY` is required on every mutating endpoint above (`POST /api/customers`, `POST /api/customers/{id}/identifications/v2`, `POST /api/customers/{id}/signings`). Send a fresh UUID per operation. See [Idempotency](/idempotency).
</Note>

## Status Reference

### Customer Status

Returned on the customer object.

| Status                   | Description                                                                                                                               |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `IdentificationRequired` | Must complete KYC/KYB                                                                                                                     |
| `SigningsRequired`       | Must sign required documents. Reached before identification on `2026-08-01`, and after approval to re-sign terms for the verified region  |
| `Active`                 | Fully onboarded. Can transact                                                                                                             |
| `Suspended`              | Blocked for compliance or fraud reasons                                                                                                   |
| `UserRequired`           | Partner-area user must be created first                                                                                                   |
| `Archived`               | Terminal. Set by `PUT /api/customers/{id}/archive`. The customer and all their identifications are permanently blocked from every service |

### Identification Status

Returned on each identification object.

| Status          | Description                                                                                                             |
| --------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `Pending`       | Customer has not started, or a business submission is waiting on missing items (resume `url` set on the identification) |
| `Processed`     | Documents submitted, awaiting review                                                                                    |
| `PendingReview` | Under compliance review                                                                                                 |
| `Approved`      | Approved                                                                                                                |
| `Declined`      | Rejected                                                                                                                |
| `Expired`       | Expired due to inactivity                                                                                               |
| `Archived`      | Terminal. Set when the customer is archived. The record no longer drives customer status                                |

**Typical Flow:** `Pending` → `Processed` → `PendingReview` → `Approved` / `Declined`

The identification object also includes `with_edd` (boolean, nullable) to indicate whether EDD was applied. See [Tracking EDD Status](#tracking-edd-status) for the full interpretation table.

### Ability Status

Returned on each rail leaf under `abilities.fiat_payout` and `abilities.fiat_deposit` (e.g. `abilities.fiat_payout.usd.ach`).

| Status        | Description                   |
| ------------- | ----------------------------- |
| `Active`      | Available for use             |
| `Pending`     | Activation in progress        |
| `Unavailable` | Not offered for this customer |
| `Blocked`     | Blocked for this customer     |
| `Maintenance` | Temporarily unavailable       |
