Inyo

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.


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, the request fails closed with 502 KYC_UPSTREAM_ERROR (retry it).
  • 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.

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

FieldTypeRequiredDescription
firstNamestringFor Level 1First name
lastNamestringFor Level 1Last name
phoneNumberstringFor Level 1Phone with country code (e.g., +15551234567)
emailstringFor US personsEmail address
genderstringFor US personsMale, Female, or Other
birthDatestringFor US personsFormat: yyyy-MM-dd
externalIdstringNoYour internal reference ID
addressobjectFor Level 1Residential address
address.countryCodestringYes (in address)ISO 3166-1 alpha-2
address.stateCodestringFor USUS state code (e.g., CA)
address.citystringYes (in address)City name
address.line1stringYes (in address)Street address
address.line2stringNoAdditional address info
address.zipcodestringYes (in address)Postal code
documentsarrayFor Level 2+Identity documents
documents[].typestringYes (in doc)Document type β€” see the accepted values below
documents[].documentstringYes (in doc)Document number
documents[].countryCodestringYes (in doc)Issuing country (ISO 3166-1 alpha-2)
documents[].stateCodestringPer rules (v2 only)Issuing state/province (max 32) β€” required by rule for some country/type combinations, e.g. US driver's licenses
documents[].expireDatestringNoExpiration date (yyyy-MM-dd)
documents[].issuerstringNoIssuing authority or state
occupationstringFor Level 2Person's occupation
employerNamestringNoEmployer name

Accepted Document Types

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

v2 valuev1 value (deprecated)Description
passportPASSPORTPassport
drivers_licenseDRIVER_LICENSEDriver's license
dniDNIDNI (Spain / Argentina / Peru)
ccCCCC (Colombia cΓ©dula de ciudadanΓ­a)
cpfCPFCPF (Brazil)
idIDGeneric government ID
consular_idCONSULAR_IDConsular ID (matrΓ­cula consular)
voter_idVOTER_IDVoter ID
ssnSSNUS Social Security number
itinITINUS ITIN (IRS)
otherOTHEROther government-issued document

Note the plural drivers_license β€” v2 corrects v1's singular DRIVER_LICENSE to align with the KYC standard. For a smooth migration, v2 also accepts the legacy uppercase v1 values and the singular driver_license (all matched case-insensitively and normalized), but new integrations should send the lowercase values.

These are declared document numbers β€” to verify document images, create a KYC verification session.

β“˜ The deprecated v1 PATCH /people/{personId} accepts a narrower set for documents[].type (PASSPORT, DRIVER_LICENSE, DNI, CC, CPF, ID, SSN, ITIN) 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

{
  "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.
  • Documents are append-only β€” updating a person's documents inserts new records and retires the old ones, preserving the compliance audit trail.

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. Documents are additive β€” each update appends new document records while the history stays visible.

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.

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 CodeMeaning
200Address is valid β€” the response includes { "valid": true, "normalized": { ... } }
422Validation 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

LevelTypical Required FieldsDescription
Level 0(none)Cannot transact
Level 1firstName, lastName, address, phoneNumberBasic KYC
Level 2SSN, occupation, document IDEnhanced KYC
Level 3Proof of source of fundsFull KYC

These are customizable per tenant. See Trust Level Limits 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.

All Endpoints

OperationMethodEndpoint
Create person (v2)POST/organizations/{tenant}/v2/people
Update person (v2)PATCH/organizations/{tenant}/v2/people/{personId}
Create KYC verification sessionPOST/organizations/{tenant}/v2/people/{personId}/kycSession
Create person (v1 β€” deprecated)POST/organizations/{tenant}/people
Update person (v1 β€” deprecated)PATCH/organizations/{tenant}/people/{personId}
Get personGET/organizations/{tenant}/people/{personId}
Get person with detailsGET/organizations/{tenant}/people/{personId}/details
Update addressPUT/organizations/{tenant}/people/{personId}/address
Update employer addressPUT/organizations/{tenant}/people/{personId}/employerAddress
Update place of birthPUT/organizations/{tenant}/people/{personId}/placeOfBirth
Check addressGET/organizations/{tenant}/addresses/check