---
description: >-
  Sender identity management for remittances — v2 person creation with rule-driven document validation and synchronous KYC verification, trust level tiers, and velocity limits.
---

# Sender

The Sender is the participant who initiates and pays for a transaction. Every sender must pass through KYC (Know Your Customer) screening, and their transaction limits are determined by their compliance level.

***

### Initial Compliance Setup

At the beginning of your integration, the Inyo compliance team works with your organization to define a custom compliance framework tailored to your product and customer profile. This framework determines:

* **Compliance levels** — tiers that define how much a customer can send within set timeframes (24h, 30d, 180d)
* **Validation rules** — the required data and documents for each level (e.g., name, SSN, proof of income)
* **Risk controls** — thresholds that trigger enhanced due diligence

This configuration is unique per tenant and directly impacts how participants are verified and which operations they can perform.

***

### Creating a Sender (v2 — Recommended)

**Endpoint:** `POST /organizations/{tenant}/v2/people`\
**Authentication:** Tenant-level (`x-api-key`)

> ⚠️ The v1 endpoint `POST /organizations/{tenant}/people` is **deprecated**. Use v2 — it takes the same payload and adds document validation and verification integrated into the KYC solution.

The v2 endpoint accepts the same request shape as v1 (see the field tables below), plus one additional per-document field — `documents[].stateCode` (issuing state/province, max 32 chars) — and applies a stricter, safer pipeline:

* **Rule-driven document validation** — per country + document type, tenant-configured rules can require extra fields (e.g., `stateCode` for US driver's licenses) and enforce format regexes. Violations return `422 VALIDATION_ERROR` with per-field errors under `errors["documents.{i}.{field}"]`, and **nothing is persisted**.
* **Synchronous KYC number verification** — where the rule requires it, the document number is verified against the Inyo360 KYC service during the request. A KYC rejection returns `422` with the rejection detail; if the KYC service is unavailable (or not configured for your tenant), the request fails closed with `422 KYC_UNAVAILABLE` — retry with backoff, and if it persists, contact Inyo support.
* **Verification verdicts on record** — verified documents carry `kyc_verdict`/`kyc_verified_at` on the person's document records.
* Same idempotency as v1: if a person with the same `externalId` exists, the existing person is returned with `200`; a new person returns `201`.

No fields are technically *required* to create a person — but to use them as a sender, they must reach at least **Compliance Level 1**, which typically requires first name, last name, address, and phone number.

```bash
curl --request POST \
  --url https://{FQDN}/organizations/$TENANT/v2/people \
  --header 'Content-Type: application/json' \
  --header "x-api-key: $API_KEY" \
  --data '{
  "firstName": "John",
  "lastName": "Doe",
  "email": "john.doe@example.com",
  "birthDate": "1990-01-15",
  "phoneNumber": "+15551234567",
  "gender": "Male",
  "externalId": "your-internal-id-001",
  "address": {
    "countryCode": "US",
    "stateCode": "CA",
    "city": "San Francisco",
    "line1": "123 Market St",
    "zipcode": "94105"
  },
  "documents": [
    {
      "type": "drivers_license",
      "document": "D12345678",
      "countryCode": "US",
      "stateCode": "CA",
      "expireDate": "2030-06-30"
    }
  ],
  "occupation": "Software Engineer"
}'
```

#### Request Fields

| Field | Type | Required | Description |
| ----- | ---- | -------- | ----------- |
| `firstName` | string | For Level 1 | First name |
| `lastName` | string | For Level 1 | Last name |
| `phoneNumber` | string | For Level 1 | Phone with country code (e.g., `+15551234567`) |
| `email` | string | For US persons | Email address |
| `gender` | string | For US persons | `Male`, `Female`, or `Other` |
| `birthDate` | string | For US persons | Format: `yyyy-MM-dd` |
| `externalId` | string | No | Your internal reference ID |
| `address` | object | For Level 1 | Residential address |
| `address.countryCode` | string | Yes (in address) | ISO 3166-1 alpha-2 |
| `address.stateCode` | string | For US | US state code (e.g., `CA`) |
| `address.city` | string | Yes (in address) | City name |
| `address.line1` | string | Yes (in address) | Street address |
| `address.line2` | string | No | Additional address info |
| `address.zipcode` | string | Yes (in address) | Postal code |
| `documents` | array | For Level 2+ | Identity documents |
| `documents[].type` | string | Yes (in doc) | Document type — see the accepted values below |
| `documents[].document` | string | Yes (in doc) | Document number |
| `documents[].countryCode` | string | Yes (in doc) | Issuing country (ISO 3166-1 alpha-2) |
| `documents[].stateCode` | string | Per rules (v2 only) | Issuing state/province (max 32) — required by rule for some country/type combinations, e.g. US driver's licenses |
| `documents[].expireDate` | string | No | Expiration date (`yyyy-MM-dd`). Also accepted as `expiryDate`; `expireDate` wins if both are sent. |
| `documents[].issuer` | string | No | Issuing authority or state |
| `occupation` | string | For Level 2 | Person's occupation |
| `employerName` | string | No | Employer name |

#### Accepted Document Types

On the v2 endpoints, `documents[].type` uses **lowercase** values, following the same standard as the KYC solution:

| v2 value | v1 value (deprecated) | Description |
| -------- | --------------------- | ----------- |
| `passport` | `PASSPORT` | Passport |
| `drivers_license` | `DRIVERS_LICENSE` | Driver's license |
| `dni` | `DNI` | DNI (Spain / Argentina / Peru) |
| `cc` | `CC` | CC (Colombia cédula de ciudadanía) |
| `cpf` | `CPF` | CPF (Brazil) |
| `id` | `ID` | Generic government ID |
| `consular_id` | `CONSULAR_ID` | Consular ID (matrícula consular) |
| `voter_id` | `VOTER_ID` | Voter ID |
| `ssn` | `SSN` | US Social Security number |
| `itin` | `ITIN` | US ITIN (IRS) |
| `other` | `OTHER` | Other government-issued document |

> Note the plural in the driver's-license value — v2 uses `drivers_license` and v1's canonical value is now `DRIVERS_LICENSE`, aligning with the KYC standard. Both APIs accept the legacy singular (`DRIVER_LICENSE` on v1, `driver_license` on v2) case-insensitively for backwards compatibility, but the plural is canonical: it's what's stored and what outbound `DocumentUpdatedEvents` webhooks emit.

These are **declared** document numbers — to verify document *images*, create a [KYC verification session](uploading-documents.md#kyc-verification-sessions-recommended).

> ⓘ The deprecated v1 `PATCH /people/{personId}` accepts a narrower set for `documents[].type` (`PASSPORT`, `DRIVERS_LICENSE`, `DNI`, `CC`, `CPF`, `ID`, `SSN`, `ITIN`; the singular `DRIVER_LICENSE` is also accepted) and restricts `documents[].countryCode` to `US`, `BR`, `CO`, `PE`, `KE`. The v2 `PATCH /v2/people/{personId}` has no such restriction — its document rules mirror v2 create exactly.

#### Sample Response

```json
{
  "id": "48066496-9445-41b7-acbe-85e069a77cb7",
  "firstName": "John",
  "lastName": "Doe",
  "mainAddressId": "9c2ea7e5-51a2-4ea1-83cf-8948754486f8",
  "phoneNumber": "+15551234567",
  "email": "john.doe@example.com",
  "gender": "Male",
  "birthDate": "1990-01-15",
  "externalId": "your-internal-id-001",
  "updatedAt": "2025-01-15T12:00:00",
  "documents": [],
  "occupation": "Software Engineer",
  "documentId": null,
  "sourceOfFundsId": null,
  "employerName": null,
  "employerAddressId": null
}
```

> **Save the returned `id`** — this is the `senderId` used in all subsequent API calls.

**Behavior notes:**

* **Idempotent on `externalId`** — if a person with the same `externalId` already exists in your tenant, create returns the existing person (`200`) instead of creating a duplicate.
* **Senders must be 18+** — `birthDate` is rejected if the person would be under 18.
* **Document updates are number-aware** on `PATCH` — if the submitted `documents[].type` + `documents[].document` match an existing active document, that row is **updated in place** (expiry, issuer, state, country, and KYC verdicts refreshed). If the document number differs (or there's no prior document of that type), the previous row is retired and a new one is inserted, preserving the compliance audit trail.
* **`expiryDate` is accepted as an alias** for `expireDate` on create and update. If both are present, the explicit `expireDate` wins.

***

### Updating a Sender (v2 — Recommended)

**Endpoint:** `PATCH /organizations/{tenant}/v2/people/{personId}`\
**Authentication:** Tenant-level

> ⚠️ The v1 endpoint `PATCH /organizations/{tenant}/people/{personId}` is **deprecated**. The v2 update applies the same rule-driven validation and synchronous KYC number verification as v2 create, and its document rules mirror create exactly (v1's update accepted a narrower document set).

Only fields included in the request body are updated; omitted fields remain unchanged. For documents, an update to an existing document number is applied **in place** rather than duplicated — see the note under [Creating a Sender](#creating-a-sender-v2-recommended). Re-sending the same document with refreshed metadata (e.g. a corrected `expireDate`) updates the existing record instead of piling up duplicates.

```bash
curl --request PATCH \
  --url https://{FQDN}/organizations/$TENANT/v2/people/$PERSON_ID \
  --header 'Content-Type: application/json' \
  --header "x-api-key: $API_KEY" \
  --data '{
  "occupation": "Consultant",
  "documents": [
    {
      "type": "drivers_license",
      "document": "D87654321",
      "countryCode": "US",
      "stateCode": "CA",
      "expireDate": "2031-01-31"
    }
  ]
}'
```

***

### Address Verification

Use the address check endpoint to pre-validate an address **before** creating or updating a sender. It applies the same country-aware validation rules (including per-country zipcode formats) as the real save endpoints, so a passing check here means the address will be accepted on `POST /people`.

```bash
curl --request GET \
  --url "https://{FQDN}/organizations/$TENANT/addresses/check?countryCode=US&stateCode=CA&city=San+Francisco&line1=123+Market+St&zipcode=94105" \
  --header "x-api-key: $API_KEY"
```

**Query parameters:** `line1`, `city`, `stateCode`, and `countryCode` are required; `zipcode` is validated against the destination country's format rules.

| Response Code | Meaning |
| ------------- | ------- |
| `200` | Address is valid — the response includes `{ "valid": true, "normalized": { ... } }` |
| `422` | Validation failed — `VALIDATION_ERROR` with a per-field breakdown |

***

### Creating Business Senders (KYB)

For B2B use cases, you can create a company as a sender:

**Endpoint:** `POST /organizations/{tenant}/companies`\
**Authentication:** Tenant-level

Companies follow a similar compliance level system but with different required fields (business registration, EIN, etc.). Contact your Inyo account manager for your tenant-specific KYB configuration.

***

### Compliance Levels

| Level | Typical Required Fields | Description |
| ----- | ----------------------- | ----------- |
| Level 0 | (none) | Cannot transact |
| Level 1 | firstName, lastName, address, phoneNumber | Basic KYC |
| Level 2 | SSN, occupation, document ID | Enhanced KYC |
| Level 3 | Proof of source of funds | Full KYC |

> These are customizable per tenant. See [Trust Level Limits](trust-level-limits.md) for details.

***

### Best Practices

* **Progressive onboarding** — Collect only Level 1 fields at signup. Prompt for more data when the user needs higher limits.
* **Pre-validate addresses** — Use the address check endpoint before creating the sender to avoid holds.
* **Sync compliance state** — After profile updates, re-check the compliance level to see if it has upgraded.
* **Handle restricted users** — If a sender is `Restricted`, check `GET /participants/{id}/complianceLevels` to understand why and what action is needed.

***

### Related Pages

* [Trust Level Limits](trust-level-limits.md) — Check and upgrade compliance levels
* [Verifying Documents](uploading-documents.md) — KYC verification sessions and document verification
* [Test Data](test-data.md) — Sandbox testing scenarios for compliance flows

### All Endpoints

| Operation | Method | Endpoint |
| --------- | ------ | -------- |
| Create person (v2) | `POST` | `/organizations/{tenant}/v2/people` |
| Update person (v2) | `PATCH` | `/organizations/{tenant}/v2/people/{personId}` |
| Create KYC verification session | `POST` | `/organizations/{tenant}/v2/people/{personId}/kycSession` |
| Create person (v1 — deprecated) | `POST` | `/organizations/{tenant}/people` |
| Update person (v1 — deprecated) | `PATCH` | `/organizations/{tenant}/people/{personId}` |
| Get person | `GET` | `/organizations/{tenant}/people/{personId}` |
| Get person with details | `GET` | `/organizations/{tenant}/people/{personId}/details` |
| Update address | `PUT` | `/organizations/{tenant}/people/{personId}/address` |
| Update employer address | `PUT` | `/organizations/{tenant}/people/{personId}/employerAddress` |
| Update place of birth | `PUT` | `/organizations/{tenant}/people/{personId}/placeOfBirth` |
| Check address | `GET` | `/organizations/{tenant}/addresses/check` |
