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

# Onboard an entity by API

> Create an Entity for your client and complete its Know Your Customer review through the Fintoc API without using the dashboard, for platforms and marketplaces.

Use the API to create and onboard an `Entity` that can own `Account` objects after approval, without using the dashboard.

This flow is for platforms that create an `Entity` for each client, such as marketplaces or wallets that hold balances under each client's legal name. To create `Account` objects under your own root `Entity`, see [Create more accounts](/guides/transfers/manage-accounts/creating-more-accounts) instead.

<Info>
  **Availability**

  Onboarding by API supports Mexican `Entity` objects. For entities outside Mexico, create the `Entity` from the dashboard.
</Info>

```mermaid theme={null}
flowchart LR
    A[Create entity] --> B[Create onboarding]
    B --> C[Upload company documents]
    C --> D[Upload representative documents]
    D --> E[Upload shareholder documents]
    E --> F[Submit for review]
    F -->|entity.onboarding.approved| G[Create accounts]
    F -->|entity.onboarding.rejected| B
```

## Before you start

You need a secret key from the [dashboard](https://dashboard.fintoc.com/), under **Developers → API Keys**. The key you send selects the mode: `sk_test_...` runs in `test`, and `sk_live_...` runs in `live`.

Use `test` mode to build and verify your integration. Use `live` mode to onboard each real client. These are separate activities, not two phases of one flow. A `test` onboarding is a different object from a `live` onboarding and never becomes a `live` onboarding. Onboarding a client in `test` does not advance that client's `live` onboarding. The examples below use a test key.

## Step 1: Create the entity

An `Entity` is the legal account holder. Create one for your client with their legal name and Mexican tax ID (RFC). Set `country_code` to `mx`, and set `holder_id` to the client's RFC.

```bash theme={null}
curl --request POST \
     --url https://api.fintoc.com/v2/entities \
     --header 'Authorization: YOUR_TEST_SECRET_KEY' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "country_code": "mx",
  "holder_name": "Test Entity 1",
  "holder_id": "AAA010101AAA"
}
'
```

```json Response theme={null}
{
  "id": "ent_2daFu0zqqDtZGJaSi2TGI2Mm1nN",
  "object": "entity",
  "country_code": "mx",
  "holder_id": "AAA010101AAA",
  "holder_name": "Test Entity 1",
  "is_root": false,
  "mode": "test",
  "status": "waiting_initialization"
}
```

Save the `Entity` ID (`ent_...`); every onboarding call uses this value. See [The Entity object](/api/transfers-api/entities/entity-object) for the full attribute list.

## Step 2: Create the onboarding

The `Onboarding` holds the Know Your Customer review for one `Entity`. The review includes company information, the legal representatives, the transactional profile, and the shareholders. Create one onboarding per `Entity` and pass the structured data in the request.

An `Entity` holds at most one onboarding per mode, so the same `Entity` can hold one `live` onboarding and one `test` onboarding at the same time. A second onboarding in the same mode returns `409 Conflict`, even when the first one is already `rejected`.

```bash theme={null}
curl --request POST \
     --url https://api.fintoc.com/v2/entities/ent_2daFu0zqqDtZGJaSi2TGI2Mm1nN/onboardings \
     --header 'Authorization: YOUR_TEST_SECRET_KEY' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "company_information": {
    "business_activity": "Servicios financieros",
    "business_address": "Av. Insurgentes 456, CDMX",
    "fiscal_address": "Av. Reforma 123, CDMX",
    "incorporation_date": "2020-01-15",
    "phone": "+521111111111",
    "settlement_account": "646969000000000000"
  },
  "legal_representatives": [
    {
      "first_name": "Test Customer 1",
      "last_name": "Customer",
      "email": "rep@example.com",
      "nationality": "mx",
      "identification_number": "AAAA010101HDFAAA01",
      "position": "Director General"
    }
  ],
  "transactional_profile": {
    "resource_origins": ["trusts", "investments"],
    "monthly_amount_range": "1_500000",
    "monthly_operations_range": "1_15000"
  },
  "shareholders": [
    {
      "type": "natural_person",
      "name": "Test Customer 2",
      "last_name": "Customer",
      "holder_id": "AAAA010101AAA",
      "nationality": "mx",
      "percentage": 60
    },
    {
      "type": "legal_entity",
      "name": "Test Entity 2",
      "holder_id": "AAA010101AAA",
      "nationality": "mx",
      "percentage": 40,
      "children": [
        {
          "type": "natural_person",
          "name": "Test Customer 3",
          "last_name": "Customer",
          "holder_id": "AAAA010101AAA",
          "nationality": "mx",
          "percentage": 80
        }
      ]
    }
  ]
}
'
```

```json Response (abridged) theme={null}
{
  "id": "onbprc_0ujsswThIGTUYm2K8FjOOfXtY1K",
  "object": "onboarding",
  "entity_id": "ent_2daFu0zqqDtZGJaSi2TGI2Mm1nN",
  "status": "in_progress",
  "source": "api",
  "submittable": false,
  "submitted_at": null,
  "reviewed_at": null,
  "legal_representatives": [
    {
      "id": "onblr_0ujsswThIGTUYm2K8FjOOfXtY1K",
      "object": "onboarding_legal_representative",
      "documents": [
        { "slot_key": "identification", "status": "missing" },
        { "slot_key": "power_of_attorney", "status": "missing" }
      ]
    }
  ],
  "shareholders": [
    {
      "id": "onbsh_0ujsswThIGTUYm2K8FjOOfXtY1K",
      "object": "onboarding_shareholder",
      "type": "natural_person",
      "parent_id": null,
      "document": { "slot_key": "identification", "status": "missing" }
    },
    {
      "id": "onbsh_8anBwgZktbZH6ydyHa6Tm0eM",
      "object": "onboarding_shareholder",
      "type": "legal_entity",
      "parent_id": null,
      "document": { "slot_key": "articles_of_incorporation", "status": "missing" }
    },
    {
      "id": "onbsh_9bnCxhAlucAI7zezIb7Un1fN",
      "object": "onboarding_shareholder",
      "type": "natural_person",
      "parent_id": "onbsh_8anBwgZktbZH6ydyHa6Tm0eM",
      "document": { "slot_key": "identification", "status": "missing" }
    }
  ],
  "documents": [
    { "slot_key": "tax_registration_certificate", "status": "missing" },
    { "slot_key": "settlement_bank_statement", "status": "missing" },
    { "slot_key": "proof_of_address", "status": "missing" },
    { "slot_key": "shareholder_structure", "status": "missing" },
    { "slot_key": "articles_of_incorporation", "status": "missing" }
  ]
}
```

A block labeled `Response (abridged)` shows only the fields that the step changes, not the whole object. The response above also returns `data`, which echoes the company information and transactional profile you sent. Each legal representative and shareholder also carries the identity fields from the request. See [The Onboarding object](/api/transfers-api/onboardings/onboarding-object) for the full shape.

Four details to note in the response:

* `submittable` is `false` until every required field and document is in place.
* `documents` lists each company document slot and its `status`, either `missing` or `uploaded`. Read this array to identify outstanding documents.
* Each legal representative has its own `documents` array with two slots.
* Each shareholder has a single `document` slot. A `legal_entity` shareholder can include nested `children`.

Every onboarding opens five company slots, two slots per legal representative, and one slot per shareholder. The onboarding above declares one legal representative and three shareholders, so it opens ten slots in total. Steps 3, 4, and 5 fill them.

Some fields use Mexico-specific identifiers. The legal representative's `identification_number` is a Mexican Unique Population Registry Code (CURP). The `settlement_account` is an 18-digit standardized Mexican bank account number (CLABE). See [The Onboarding object](/api/transfers-api/onboardings/onboarding-object) for every field.

## Step 3: Upload the company documents

Upload one file to each company document slot in the `documents` array. Send the file as `multipart/form-data` in the `file` field. The maximum file size is 20 MB. Uploading to a slot that already has a file replaces the existing file.

Each slot accepts its own content types:

| `slot_key`                     | Accepted content types                       |
| :----------------------------- | :------------------------------------------- |
| `tax_registration_certificate` | `application/pdf`                            |
| `settlement_bank_statement`    | `application/pdf`                            |
| `proof_of_address`             | `application/pdf`, `image/jpeg`, `image/png` |
| `shareholder_structure`        | `application/pdf`                            |
| `articles_of_incorporation`    | `application/pdf`                            |

```bash theme={null}
curl --request PUT \
     --url https://api.fintoc.com/v2/entities/ent_2daFu0zqqDtZGJaSi2TGI2Mm1nN/onboardings/onbprc_0ujsswThIGTUYm2K8FjOOfXtY1K/documents/tax_registration_certificate \
     --header 'Authorization: YOUR_TEST_SECRET_KEY' \
     --header 'accept: application/json' \
     --form 'file=@tax_registration_certificate.pdf'
```

```json Response (abridged) theme={null}
{
  "id": "onbprc_0ujsswThIGTUYm2K8FjOOfXtY1K",
  "object": "onboarding",
  "documents": [
    {
      "slot_key": "tax_registration_certificate",
      "status": "uploaded",
      "filename": "tax_registration_certificate.pdf",
      "uploaded_at": "2026-01-15T14:30:00Z"
    }
  ]
}
```

Repeat for each remaining slot: `settlement_bank_statement`, `proof_of_address`, `shareholder_structure`, and `articles_of_incorporation`.

## Step 4: Upload each legal representative's documents

Each legal representative needs two documents. Use the legal representative `id` (`onblr_...`) from the Step 2 response, and pass the slot as the last path segment. The `identification` slot accepts `application/pdf`, `image/jpeg`, and `image/png`. The `power_of_attorney` slot accepts `application/pdf` only.

```bash theme={null}
curl --request PUT \
     --url https://api.fintoc.com/v2/entities/ent_2daFu0zqqDtZGJaSi2TGI2Mm1nN/onboardings/onbprc_0ujsswThIGTUYm2K8FjOOfXtY1K/legal_representatives/onblr_0ujsswThIGTUYm2K8FjOOfXtY1K/documents/identification \
     --header 'Authorization: YOUR_TEST_SECRET_KEY' \
     --header 'accept: application/json' \
     --form 'file=@identification.pdf'
```

```json Response (abridged) theme={null}
{
  "id": "onbprc_0ujsswThIGTUYm2K8FjOOfXtY1K",
  "object": "onboarding",
  "legal_representatives": [
    {
      "id": "onblr_0ujsswThIGTUYm2K8FjOOfXtY1K",
      "object": "onboarding_legal_representative",
      "documents": [
        {
          "slot_key": "identification",
          "status": "uploaded",
          "filename": "identification.pdf",
          "uploaded_at": "2026-01-15T14:30:00Z"
        },
        { "slot_key": "power_of_attorney", "status": "missing" }
      ]
    }
  ]
}
```

Repeat for the `power_of_attorney` slot, and for every other legal representative you declared.

## Step 5: Upload each shareholder's document

Each declared shareholder needs one document. Use the shareholder `id` (`onbsh_...`) from the Step 2 response. This path carries no slot, because Fintoc derives the slot from the shareholder's `type`: `identification` for a `natural_person`, and `articles_of_incorporation` for a `legal_entity`. The slot accepts `application/pdf`, `image/jpeg`, and `image/png`.

```bash theme={null}
curl --request PUT \
     --url https://api.fintoc.com/v2/entities/ent_2daFu0zqqDtZGJaSi2TGI2Mm1nN/onboardings/onbprc_0ujsswThIGTUYm2K8FjOOfXtY1K/shareholders/onbsh_0ujsswThIGTUYm2K8FjOOfXtY1K/document \
     --header 'Authorization: YOUR_TEST_SECRET_KEY' \
     --header 'accept: application/json' \
     --form 'file=@identification.pdf'
```

```json Response (abridged) theme={null}
{
  "id": "onbprc_0ujsswThIGTUYm2K8FjOOfXtY1K",
  "object": "onboarding",
  "shareholders": [
    {
      "id": "onbsh_0ujsswThIGTUYm2K8FjOOfXtY1K",
      "object": "onboarding_shareholder",
      "document": {
        "slot_key": "identification",
        "status": "uploaded",
        "filename": "identification.pdf",
        "uploaded_at": "2026-01-15T14:30:00Z"
      }
    }
  ]
}
```

## Step 6: Submit for review

Once `submittable` is `true`, submit the onboarding for Fintoc to review. The onboarding must be `in_progress` and include every required field and document. After submission, the onboarding moves to `submitted` and can no longer be modified.

```bash theme={null}
curl --request POST \
     --url https://api.fintoc.com/v2/entities/ent_2daFu0zqqDtZGJaSi2TGI2Mm1nN/onboardings/onbprc_0ujsswThIGTUYm2K8FjOOfXtY1K/submit \
     --header 'Authorization: YOUR_TEST_SECRET_KEY' \
     --header 'accept: application/json'
```

```json Response (abridged) theme={null}
{
  "id": "onbprc_0ujsswThIGTUYm2K8FjOOfXtY1K",
  "object": "onboarding",
  "entity_id": "ent_2daFu0zqqDtZGJaSi2TGI2Mm1nN",
  "status": "submitted",
  "source": "api",
  "submittable": false,
  "submitted_at": "2026-01-15T15:00:00Z",
  "reviewed_at": null
}
```

`submittable` turns `false` once the onboarding is `submitted`, because a submitted onboarding can no longer accept changes.

If a required field or document is missing, or the onboarding is no longer `in_progress`, the call returns a `422 Unprocessable Entity` error. The `param` field names the slot or field that blocks the submission:

```json Error response theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "required_document_missing",
    "message": "A required document is missing.",
    "param": "tax_registration_certificate",
    "doc_url": "https://docs.fintoc.com/reference/errors"
  }
}
```

## Step 7: Track the review

An onboarding moves through `pending`, `in_progress`, `submitted`, and then `approved`, `rejected`, or `cancelled`. Fintoc sends one of these webhook events when it approves or rejects the onboarding:

| Event                        | Onboarding result                                    |
| :--------------------------- | :--------------------------------------------------- |
| `entity.onboarding.approved` | The `Entity` passed review and is ready to transact. |
| `entity.onboarding.rejected` | The review failed. Create a new onboarding to retry. |

Subscribe to these events in the dashboard under **Developers → Webhooks**, or poll the onboarding directly:

```bash theme={null}
curl --request GET \
     --url https://api.fintoc.com/v2/entities/ent_2daFu0zqqDtZGJaSi2TGI2Mm1nN/onboardings/onbprc_0ujsswThIGTUYm2K8FjOOfXtY1K \
     --header 'Authorization: YOUR_TEST_SECRET_KEY' \
     --header 'accept: application/json'
```

```json Response (abridged) theme={null}
{
  "id": "onbprc_0ujsswThIGTUYm2K8FjOOfXtY1K",
  "object": "onboarding",
  "entity_id": "ent_2daFu0zqqDtZGJaSi2TGI2Mm1nN",
  "status": "approved",
  "source": "api",
  "submittable": false,
  "submitted_at": "2026-01-15T15:00:00Z",
  "reviewed_at": "2026-01-16T09:00:00Z"
}
```

Check `status` to track the onboarding. Once `status` is `approved`, the `Entity` is ready to transact.

## Step 8: Create accounts once approved

After the `Entity` is approved, create one or more `Account` objects under the `Entity`. Pass the `Entity` ID as `entity_id`. Each `Account` has its own balance and root account number. Outbound transfer receipts show the client's legal name. See [Create more accounts](/guides/transfers/manage-accounts/creating-more-accounts) for account creation details.

```bash theme={null}
curl --request POST \
     --url https://api.fintoc.com/v2/accounts \
     --header 'Authorization: YOUR_TEST_SECRET_KEY' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "entity_id": "ent_2daFu0zqqDtZGJaSi2TGI2Mm1nN",
  "description": "Client settlement account"
}
'
```

```json Response theme={null}
{
  "id": "acc_8anBwgZktbZH6ydyHa6Tm0eM",
  "object": "account",
  "mode": "test",
  "description": "Client settlement account",
  "root_account_number": "000000000000000000",
  "root_account_number_id": "acno_0ujsswThIGTUYm2K8FjOOfXtY1K",
  "available_balance": 0,
  "currency": "MXN",
  "entity": {
    "id": "ent_2daFu0zqqDtZGJaSi2TGI2Mm1nN",
    "holder_name": "Test Entity 1",
    "holder_id": "AAA010101AAA"
  }
}
```

## Test the integration

In `test` mode, run the full sequence above with your `sk_test_...` key. Create the `Entity` and onboarding, upload a sample file to every slot, and submit the onboarding. Confirm that `submittable` turns `true` only after every slot reports `uploaded`.

Fintoc selects the mode from the API key you send, not from a request parameter. Onboarding request and response bodies do not carry a `mode` field. Onboardings are isolated per mode. A `test` key cannot read or act on a `live` onboarding, and a `live` key cannot read or act on a `test` onboarding. Both cases return `404 Not Found` with the code `missing_resource`. Fintoc resolves `entity_id` in the API key's mode, so a `test` key must reference an `Entity` that exists in `test` mode.

### Force a review outcome

In `live` mode, submitting starts a real Know Your Customer review that Fintoc's compliance team decides. In `test` mode, submitting triggers a simulated review. Use the simulated review to exercise the approved and rejected paths without waiting.

Fintoc decides the simulated review from the `business_activity` value you sent in `company_information`. This mechanism works like a test card number: a magic value forces a specific outcome. Three cases drive the simulated review:

| `business_activity` | Resulting `status` | Webhook event                |
| :------------------ | :----------------- | :--------------------------- |
| `illegal`           | `rejected`         | `entity.onboarding.rejected` |
| `suspicious`        | `submitted`        | No event                     |
| Any other value     | `approved`         | `entity.onboarding.approved` |

The `suspicious` case leaves `reviewed_at` set to `null`. Use it to model an onboarding that is still under review, and check how your integration behaves while it waits for a decision.

<Warning>
  Fintoc compares the whole string exactly, including case and surrounding whitespace. `ILLEGAL`, `Illegal`, and `"illegal "` with a trailing space all fall through to `approved`.
</Warning>

### Read the simulated result

`POST .../submit` returns `200 OK` with `status` set to `submitted` and `reviewed_at` set to `null`, in both modes. The simulated review runs after the response, so do not treat the submit response as the review outcome. Poll the onboarding as shown in Step 7, or listen for the webhook event.

Fintoc delivers `entity.onboarding.approved` and `entity.onboarding.rejected` to the webhook endpoints registered in `test` mode, and the event carries `"mode": "test"`.

```json Webhook event theme={null}
{
  "id": "evt_2xK9mP4nQrStUvWxYz1234567",
  "object": "event",
  "type": "entity.onboarding.approved",
  "mode": "test",
  "created_at": "2026-01-16T09:00:00.000Z",
  "data": {
    "object_name": "onboarding_process",
    "id": "onbprc_0ujsswThIGTUYm2K8FjOOfXtY1K",
    "entity": {
      "id": "ent_2daFu0zqqDtZGJaSi2TGI2Mm1nN",
      "holder_id": "AAA010101AAA",
      "holder_name": "Test Entity 1",
      "nationality": "mx"
    },
    "status": "approved",
    "submitted_at": "2026-01-15T15:00:00Z",
    "reviewed_at": "2026-01-16T09:00:00Z"
  }
}
```

The webhook payload names the resource `onboarding_process` in `object_name`, which is the same object the API returns with `"object": "onboarding"`. The rejected event carries the same payload with `"type": "entity.onboarding.rejected"` and `"status": "rejected"`.

### What test mode does not simulate

In `test` mode, Fintoc reproduces the review outcome and the webhook, and nothing else in the compliance flow:

* No rejection reason. A rejected onboarding exposes no field that explains the decision, in the API or in the webhook payload.
* No emails. Fintoc sends no submission notification to the legal representatives in `test` mode.

## What's next

* [Create more accounts](/guides/transfers/manage-accounts/creating-more-accounts) for an approved `Entity`.
* Review [The Onboarding object](/api/transfers-api/onboardings/onboarding-object) and [the onboarding endpoints](/api/transfers-api/onboardings/entities-onboardings-create) in the API reference.
