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

# International onboarding

> The extra business profile and beneficial-owner data the FX provider requires before a business can hold an international account, and when an identity document is mandatory.

A business that can already pay domestically cannot necessarily pay internationally. The currency
provider has to know who it is dealing with, which means two things beyond ordinary entity creation:
a completed business profile, and the people behind the business recorded as **beneficial owners**.

The gate is enforced when you create an international receiving account — `POST /accounts` with
`type: international-fx` or `international-gusd` returns `409 ENTITY_ONBOARDING_INCOMPLETE` while
anything is outstanding — and reported continuously by `GET /limitations` on
`payment.international:write`.

## What is checked

| `missingFields` | Fix |
| - | - |
| `website` | `PATCH /entities/{entityId}` with `website` |
| `description` | `PATCH /entities/{entityId}` with `description` |
| `beneficialOwners` | Record a person with the `fx-account-holder` type (below) |
| `beneficialOwners.identityDocument` | Attach a government ID to the people who need one (below) |

The same dot-paths appear in the `409`'s `details[].validationErrors[].field` and in a limitation's
`missingFields`, so one mapping in your integration covers both.

## Beneficial owners

Read the current set with `GET /v2/entities/{entityId}/beneficial-owners`, and write it with
`PUT /v2/entities/{entityId}/beneficial-owners`.

**The `PUT` replaces the whole set.** Entries carrying an `id` are updated, entries without one are
created, and anyone the payload omits is deleted. That is deliberate: ownership percentages are only
meaningful as a set — the provider validates their total — so a partial update has no coherent
meaning. Read the set, modify it, send it back whole.

```json theme={null}
{
  "data": [
    {
      "firstName": "Dana",
      "lastName": "Okonjo",
      "dateOfBirth": "1984-03-11",
      "address": {
        "line1": "500 Congress Ave",
        "city": "Austin",
        "state": "TX",
        "postalCode": "78701",
        "countryCode": "US"
      },
      "usResident": true,
      "ownershipPercentage": 0.6,
      "jobTitle": "chief-executive-officer",
      "types": ["ubo", "director", "fx-account-holder"],
      "taxInfo": { "type": "ssn", "identifier": "123-45-6789" },
      "email": "dana@example.com"
    }
  ]
}
```

### The three roles

| Type | Who it is |
| - | - |
| `ubo` | An ultimate beneficial owner — someone holding a material stake in the business |
| `director` | A control person, whatever their stake |
| `fx-account-holder` | The individual who acts for the business towards the currency provider |

One person usually holds several roles; the example above is all three. The rules the API enforces:

* **Exactly one `fx-account-holder`** — and its absence is what keeps
  `payment.international:write` blocked. More than one is `400`.
* **At most one `director`.** A set still being assembled may have none; the missing-director case is
  reported by `GET /limitations` rather than by rejecting the write.
* **A `ubo` must hold at least 0.25** (25%) and carry `ownershipPercentage`. Anyone below that is not
  a beneficial owner — give them the `director` type instead.
* **At most four `ubo` entries**, and their `ownershipPercentage` values may not total more than 1.
  Both are checked here rather than left to the provider, whose own error does not say which entry
  or total was at fault.
* At most 20 people in one request. `firstName`/`lastName` are at least 2 characters, and
  `dateOfBirth` must put the person between 18 and 120 years old.

### Identification depends on residency

* `usResident: true` → `taxInfo` with an `ssn` or `itin` (9 digits, dashes optional).
* `usResident: false` → `internationalIdType` (`passport`, `driver-license` or `national-id`) and
  `internationalId`.

Nothing is echoed back in full. `GET` returns `taxInfo.identifierLast4`, the last four characters of
`internationalId`, and a `hasIdentityDocument` boolean — never the document itself.

## When an identity document is required

Recording the numbers is not always enough: the provider sometimes wants to see the ID. There are
exactly two cases, and both are rejected with `400 VALIDATION_ERROR` naming the person, so you never
discover them from a failed payment later:

1. **Any non-US resident** among the people you record.
2. **Every beneficial owner** — including US residents — when the entity's `businessType` is
   `sole-proprietorship`.

Upload the scan with [`POST /attachments`](https://developers.melio.com/docs/payment-attachments) and reference it on the person:

```json theme={null}
{
  "firstName": "Luca",
  "lastName": "Ferrari",
  "dateOfBirth": "1979-08-02",
  "address": { "line1": "Via Roma 12", "city": "Milano", "postalCode": "20121", "countryCode": "IT" },
  "usResident": false,
  "internationalIdType": "passport",
  "internationalId": "YA1234567",
  "ownershipPercentage": 0.4,
  "types": ["ubo"],
  "identityDocument": {
    "frontAttachmentId": "att_9f8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "backAttachmentId": "att_1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
  }
}
```

Only the front is required. The provider's guidance asks for both sides of a driver's licence or
national ID, and you should send `backAttachmentId` when you have it, but a front-only upload is
accepted. The two ids must differ.

The attachment must belong to the same entity as the person — an id from another entity is a `404`.
Documents are handed to compliance as part of the write: if that hand-off fails the whole request
fails with `503 COMPLIANCE_UPLOAD_FAILED` and nothing is saved, so a retry is safe and a person can
never be left on file claiming a document that was never delivered.

## Putting it together

```
PATCH /entities/{entityId}          → website, description
POST  /attachments                  → ids for any required IDs
PUT   /entities/{entityId}/beneficial-owners
                                    → the complete set, one fx-account-holder
GET   /limitations                  → payment.international:write allowed?
POST  /accounts                     → the international receiving account
```

If `GET /limitations` still reports a reason after that, read its `code`: `MissingInformation` lists
what to send, while `LocationRestricted`, `AccountBlocked` and `AccountUnderReview` are not something
more data will fix — `contactEmail`, when present, is where to take it.

## See also

* [International payments](https://developers.melio.com/docs/international-payments) — the two rails and their payment payloads
* [Payment attachments](https://developers.melio.com/docs/payment-attachments) — uploading documents


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.