# Customers

Onboard the businesses you serve, so you can open accounts, convert and pay out for them. The API calls a customer a **sub-client**. Multigate checks each customer (KYB, know your business: verifying the company, its owners and its documents) before it can transact.

## When to use it

Use customers when you hold money or make payments **for other businesses**: a fintech's or marketplace's merchants, a remittance company's business clients, a bank's corporate clients, or a corporate's subsidiaries. If you only move your own money, you don't need customers.

There are two types:

| `subClientType` | For | Review |
|---|---|---|
| `client` | An external business you serve. | Approved in your business, then Multigate screens the business name (checks it automatically against sanctions and watch lists). A possible match goes to Multigate's compliance team. |
| `subsidiary` | A company in your own group. | Approved in your business only. Multigate doesn't screen it. |

## Before you start

- You've completed the [Quickstart](../01-get-started/03-quickstart.md) environment setup: `MG_BASE_URL`, `MG_API_KEY` and `MG_CLIENT_ID` (your client ID).
- Your own business must be `active` and approved on Multigate.
- Your API user needs the *manage customers* permission. To skip the second approver, it also needs the *approve customers* permission (see the submit step below).
- Your webhook URL is registered, so you receive the review outcome (`client` / `subclient_approved`).
- Have the customer's registration details, its directors and beneficial owners (people who own or control it, usually 25% or more), and its KYB documents as PDF or image files.

## IDs you'll use

| ID | Where you get it | Where you send it |
|---|---|---|
| Your client ID (`MG_CLIENT_ID`) | `currentAuthenticatedClientId` in [Get the current user](api:getCurrentUser) | The `{clientId}` part of every customer URL. |
| Customer ID | `entityId` returned by [Create a customer](api:createSubClient) | The `{id}` part of customer URLs; `onBehalfOf` for accounts, payouts and conversions; `subClientId` when you save a beneficiary; `clientEntityId` to list its beneficiaries. |
| Person ID | `entityId` returned by [Add a director or owner](api:createShareholder) | `businessUserId` when you upload or list that person's documents; `{shareHolderId}` in [Update a director or owner](api:updateShareholder). |

Every example below uses customer ID `Kx81mQ2pZr4tV9wYa3Bc5` and person ID `Hq3vT8nLm2Xc5RzW`: replace them with yours.

## How onboarding works

```mermaid
sequenceDiagram
    autonumber
    participant You as Your backend
    participant MG as Multigate
    participant AP as Your approver
    participant CO as Multigate compliance
    You->>MG: POST create customer
    MG-->>You: entityId, status draft
    You->>MG: POST add each director and owner
    You->>MG: POST upload each document
    You->>MG: POST submit for review
    MG-->>You: approval request, customer now processing
    alt Your API user can't approve customers
        MG->>AP: Email "Sub-client Creation Request for Approval"
        AP->>MG: Approve in the dashboard
    end
    MG->>MG: Screen the business name
    alt Nothing found
        MG-)You: Webhook client / subclient_approved, status active
    else Possible match
        MG->>CO: Review
        CO->>MG: Approve or decline
        MG-)You: Webhook client / subclient_approved, status active or rejected
    end
```

A `subsidiary` skips screening: it becomes `active` as soon as it's approved in your business, and no webhook is sent.

<Steps>
<Step title="Create the customer">

```bash
curl -X POST "$MG_BASE_URL/client-service/api/v1/clients/$MG_CLIENT_ID/sub-clients" \
  -H "mp-api-key: $MG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subClientType": "client",
    "businessName": "Kola Stores Ltd",
    "businessType": "Limited Liability Company",
    "customerType": "corporate",
    "businessAddressLine1": "4 Allen Avenue",
    "city": "Ikeja",
    "state": "Lagos",
    "countryOfRegistration": "NGA",
    "operatingCountries": ["NGA"],
    "businessContactEmail": "finance@kolastores.example",
    "registrationNumber": "RC1234567"
  }'
```

```json
{
  "message": "Sub-Client information saved successfully",
  "entityId": "Kx81mQ2pZr4tV9wYa3Bc5",
  "businessName": "Kola Stores Ltd",
  "subClientType": "client",
  "status": "draft",
  "approvalStatus": "pending",
  "stagesRemaining": ["documentation", "director", "owner"],
  "parentClientId": "Ab3kP9xQ2mN7vR1sT4uV6"
}
```

Store the returned `entityId`: it's the customer ID. The customer is `draft`: nothing is reviewed until you submit it.

Required fields: `subClientType`, `businessName`, `businessType` (`Public Limited Company`, `Limited Liability Company`, `Ordinary Business Registration` or `Government Agency`), `businessAddressLine1`, `city`, `state`, `countryOfRegistration`, `operatingCountries` and `businessContactEmail`. Countries are upper-case ISO 3166-1 alpha-3 codes (`NGA`, `GHA`). `customerType` is `corporate` or `financialInstitution`. See [Create a customer](api:createSubClient) for every optional field.

- `businessContactEmail` must not be used by any other customer on Multigate, including declined ones. A duplicate returns `409`.
- Give each customer a distinct `businessName`. Two customers with the same name can't both be used for payouts and conversions.
- Creating isn't idempotent. If the call times out, look for the customer with [List customers](api:listSubClients) (`businessName` filter) before you retry.

</Step>
<Step title="Add directors and owners">

Add each person with [Add a director or owner](api:createShareholder), one call per person. Use `designation` to say what they are.

```bash
curl -X POST "$MG_BASE_URL/client-service/api/v1/clients/$MG_CLIENT_ID/sub-clients/Kx81mQ2pZr4tV9wYa3Bc5/business-users" \
  -H "mp-api-key: $MG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Mrs", "firstName": "Adaeze", "lastName": "Kola", "gender": "female",
    "occupation": "Director", "dateOfBirth": "1980-04-12",
    "identificationType": "PASSPORT", "identificationNumber": "A01234567",
    "issuedDate": "2022-01-10", "expiryDate": "2032-01-09",
    "countryIso3Code": "NGA", "country": "Nigeria",
    "address": "4 Allen Avenue", "city": "Ikeja", "state": "Lagos", "postalCode": "100001",
    "designation": "BeneficialOwnerDirector", "shareholderPercentage": 60
  }'
```

The response is the person, with their `entityId`: store it as the person ID. Dates are `YYYY-MM-DD`; the identity document's `issuedDate` can't be in the future and its `expiryDate` can't be in the past.

`stagesRemaining` is a checklist for you; Multigate doesn't enforce it:

| `designation` | Removes from `stagesRemaining` |
|---|---|
| `BeneficialOwner`, `BeneficialOwnerSignatory`, `BeneficialOwnerDirector`, `BeneficialOwnerDirectorSignatory` | `owner` |
| `Director`, `DirectorSignatory` | `director` |
| `Signatory` | nothing |

A `BeneficialOwnerDirector` clears `owner` only. Uploading documents never clears `documentation`.

</Step>
<Step title="Upload documents">

Upload each file with [Upload a customer document](api:uploadSubClientKyc) as `multipart/form-data`, one file per call. Company documents go on the customer. A person's documents go on the customer too, with the person ID in `businessUserId`.

A company document:

```bash
curl -X POST "$MG_BASE_URL/client-service/api/v1/clients/$MG_CLIENT_ID/sub-clients/Kx81mQ2pZr4tV9wYa3Bc5/upload-kyc" \
  -H "mp-api-key: $MG_API_KEY" \
  -F "file=@certificate.pdf" \
  -F "documentType=CERTIFICATE_OF_INCORPORATION" \
  -F "issueDate=2019-03-14"
```

A director's passport:

```bash
curl -X POST "$MG_BASE_URL/client-service/api/v1/clients/$MG_CLIENT_ID/sub-clients/Kx81mQ2pZr4tV9wYa3Bc5/upload-kyc" \
  -H "mp-api-key: $MG_API_KEY" \
  -F "file=@adaeze-passport.pdf" \
  -F "documentType=PASSPORT" \
  -F "businessUserId=Hq3vT8nLm2Xc5RzW" \
  -F "issueDate=2022-01-10" \
  -F "expiryDate=2032-01-09"
```

Pick the `documentType` that matches each file:

| Document | `documentType` | Uploaded on |
|---|---|---|
| Certificate of incorporation or registration | `CERTIFICATE_OF_INCORPORATION` or `REGISTRATION_CERTIFICATE` | The customer |
| Memorandum and articles of association | `ARTICLE_OF_ASSOCIATION` | The customer |
| List of directors, list of shareholders | `LIST_OF_DIRECTORS`, `LIST_OF_SHAREHOLDERS` | The customer |
| Ownership chart | `SHAREHOLDING_STRUCTURE_CHART` | The customer |
| Business proof of address (for example a utility bill) | `PROOF_OF_ADDRESS` | The customer |
| Tax registration, business or regulatory licence | `TAX_REGISTRATION`, `BUSINESS_LICENSE`, `REGULATORY_LICENSE` | The customer |
| Board resolution | `BOARD_RESOLUTION` | The customer |
| Recent bank statement | `RECENT_BANK` | The customer |
| Passport, national ID card, NIN slip, driver's licence, voter's card | `PASSPORT`, `IDCARD`, `NIN`, `DRIVERS_LICENSE`, `VOTERS_CARD` | The person (`businessUserId`) |
| Personal proof of address | `PROOF_OF_ADDRESS` | The person (`businessUserId`) |

Check what's uploaded with [List customer documents](api:listSubClientDocuments) (add `businessUserId` for a person's documents).

- Send PDF or image files. Plain-text files (`.txt`, `.csv`) fail with `500`.
- A second file with the same `documentType` is added to the same document. Files can't be removed through the API.
- The API doesn't say which documents are required, and submission doesn't check them. Your Multigate contact tells you which documents your market requires; a customer without them can be declined.

</Step>
<Step title="Submit for review">

```bash
curl -X POST "$MG_BASE_URL/client-service/api/v1/clients/$MG_CLIENT_ID/sub-clients/Kx81mQ2pZr4tV9wYa3Bc5/submit-for-approval" \
  -H "mp-api-key: $MG_API_KEY"
```

The response is the approval request (`type` `creation`), not the customer. What happens next depends on your API user:

- **It has both the *manage customers* and *approve customers* permissions:** the customer is approved in your business at once. A `client` becomes `processing` and is screened; a `subsidiary` becomes `active`.
- **It doesn't:** the customer becomes `processing` with `approvalStatus` `pending`. Your approvers receive a "Sub-client Creation Request for Approval" email and approve or decline in the dashboard. If they decline, the customer is removed: it disappears from lists, its email can't be reused, and no webhook is sent.

:::warning[Submit once]
Submit only `draft` customers, and only once. The endpoint doesn't check the status: submitting again moves an `active` or `rejected` customer back to `processing`, even when the call then fails with `409`. Use [Resubmit a declined customer](api:resubmitSubClient) for declined customers.
:::

</Step>
<Step title="Wait for the outcome">

- **Nothing found by screening:** the customer becomes `active`, usually within minutes, and you receive `client` / `subclient_approved`.
- **Possible match:** Multigate compliance reviews it. The customer stays `processing` with no webhook until they decide; this can take days if they need more information.

When `subclient_approved` arrives, **check `status`**: `active` means approved, `rejected` means declined. The same event is used for both.

If declined, read `approvalComments`, fix what was asked (update the customer, add people or documents), then call [Resubmit a declined customer](api:resubmitSubClient). Compliance reviews it again and you receive `subclient_approved` with the new outcome.

If you miss the webhook, poll [Get a customer](api:getSubClient) no more than every few minutes.

</Step>
</Steps>

## Customer statuses

```mermaid
stateDiagram-v2
    direction LR
    [*] --> draft: created
    draft --> processing: submitted
    draft --> active: subsidiary approved at once
    processing --> active: screening clear, compliance approves, or subsidiary approved
    processing --> rejected: compliance declines
    processing --> inactive: your approver declines, customer removed
    rejected --> processing: resubmitted
    active --> inactive: deactivated
    inactive --> active: reactivated
    active --> deleted: deleted
    active --> blocked: blocked by Multigate
    blocked --> active: unblocked by Multigate
```

| `status` | Meaning | Can you open accounts, pay out or convert for it? | What to do |
|---|---|---|---|
| `draft` | Created, not submitted. | No | Add people and documents, then submit. |
| `processing` | Submitted. Waiting for your approver, for screening, or for Multigate compliance. | No | Wait for `subclient_approved`. |
| `active` | Approved. | Yes | Open accounts ([Accounts](02-accounts.md)). |
| `rejected` | Declined by Multigate compliance. | No | Read `approvalComments`, fix, then resubmit. |
| `inactive` | Deactivated in your business. Customers declined by your own approver are also set to `inactive`, but they're removed, so you won't see them. | No | Reactivate it in the dashboard. |
| `deleted` | Deleted in your business. | No | Create a new customer (with a new email). |
| `blocked` | Blocked by Multigate compliance. | No | Contact Multigate. |

Deactivating, reactivating and deleting customers are dashboard actions that go through your approvers. Accounts are opened only for `active` customers ("Sub-Client not active" otherwise).

`approvalStatus` shows approval inside **your** business only: `pending` (created, or waiting for your approver), `approved` (approved in your business; Multigate's review may still be running) or `declined` (declined by your approver). It stays `approved` when Multigate compliance declines the customer. Always use `status` for the outcome.

## The `subclient_approved` webhook

The body is the customer, as returned by [Get a customer](api:getSubClient): `entityId`, `businessName`, `status`, `approvalStatus`, `parentClientId` (your client ID) and the other customer fields.

- **Sent:** for a `client` customer, when screening finds nothing (`status` `active`) or when Multigate compliance decides (`active` or `rejected`), including after a resubmission.
- **Not sent:** for subsidiaries, when your approver declines, while compliance is still reviewing, or for creating, updating, uploading or submitting.
- `approvalComments` and `approvalDate` appear only after a compliance decision. `approvalDate` isn't ISO 8601 (it looks like `Sat Oct 10 2026 14:20:00 GMT+0000 (Coordinated Universal Time)`).
- De-duplicate by `entityId` and `status`: the same outcome can arrive more than once.

## Things to know

- **Active customers become usable for payouts and conversions after a short delay** (up to a few minutes) while the change reaches those services. Until then, payouts for them fail with "Invalid sub onBehalfOf".
- **Updates aren't reviewed.** [Update a customer](api:updateSubClient) works in any status and applies at once. It doesn't check country codes, prohibited countries or email uniqueness, so send the same kind of values you'd send on create.
- **Countries.** A customer registered or operating in a country Multigate prohibits is refused when you create it ("The selected country or countries are prohibited"). Use upper-case ISO 3166-1 alpha-3 codes.
- **Email domains.** Production refuses disposable email domains (yopmail.com, mailinator.com and similar) for the customer and its people. The sandbox accepts them.
- **Emails go to your business.** Approval requests go to your approvers, and approval results ("Subclient Creation Request Approved", "Sub-client Creation Request Declined") go to the user who submitted, which is your API user. Customers never receive emails from these steps.

:::caution[Sandbox: reviews are slow]
In the sandbox, a submitted `client` customer can stay `processing` for **days**, because screening results are collected far less often than in production. Until it's `active` you can't open accounts for it. Ask your Multigate contact to complete the review for your test customers. See [Sandbox vs production](../01-get-started/05-sandbox-vs-production.md#customers-and-compliance-review).
:::

## Errors

| Message | Cause | Fix |
|---|---|---|
| Client not found (`400`) | The `clientId` in the path isn't your client ID, or your business isn't active. | Use `currentAuthenticatedClientId` from [Get the current user](api:getCurrentUser). |
| Client is inactive (`400`) | Your own business isn't active and approved yet. | Contact Multigate. |
| Sub-Client not found (`400`) | The customer doesn't exist, isn't yours, or was declined by your approver and removed. | Check the `entityId`. |
| Resource not found (`404`) | Your API user lacks the permission. | Ask your administrator for the *manage customers* permission. |
| Sub-Client already exists (`409`) | The contact email is already used by a customer on Multigate. | Use the customer's own unique email. |
| The selected country or countries are prohibited (`400`) | A country Multigate can't serve. | Remove it. |
| Country not found (`404`) | An unknown country code. | Use upper-case ISO 3166-1 alpha-3 codes. |
| Email domain is not allowed (`400`) | A disposable email domain, in production. | Use a real business domain. |
| ShareHolder not found (`404`) | The `businessUserId` isn't a person on this customer. | Use the `entityId` from [Add a director or owner](api:createShareholder). |
| Pending approval request for creation already exists (`409`) | A submission is still open. | Wait for the outcome. Don't submit again. |
| Sub-Client not eligible for resubmission (`400`) | Only `rejected` customers can be resubmitted. | Check `status`. |
| Sub-Client in review (`400`) | The customer is already being reviewed. | Wait for the outcome. |

## Next

<CardGroup cols={2}>
<Card title="Open accounts for the customer" icon="lucide-landmark" href="02-accounts.md">
Wallets and virtual accounts with `onBehalfOf`.
</Card>
<Card title="Acting for customers" icon="lucide-users" href="../02-concepts/06-acting-for-customers.md">
How `onBehalfOf` works across the API.
</Card>
</CardGroup>
