Customers API

The Customers API allows business clients to register and manage customer accounts. Customers registered through this API are automatically linked to your business account.

Use GET /api/v1/client/customers to list all customers linked to your business (paginated, with profile fields and onboarding status).

Customer Registration Options

We provide two endpoints for customer registration, depending on your KYC workflow:

Option 1: Register Customer with KYC Link

Endpoint: POST /api/v1/client/customers

Use this endpoint when you want to:

  • Create a customer account with basic information
  • Receive a KYC onboarding link to share with your customer
  • Let your customer complete KYC verification themselves through the provided link

Best for: Self-service flows where customers handle their own KYC submission.

Option 2: Full Registration with KYC Data

Endpoint: POST /api/v1/client/customers/full

Use this endpoint when you want to:

  • Create a customer account and submit all KYC data in a single request
  • Provide complete customer information, address, compliance fields, identification numbers, and document images
  • Submit everything programmatically without requiring customer interaction

Best for: Programmatic onboarding where you've already collected all KYC information from your customer.

Comparison

FeatureRegister with LinkFull Registration
EndpointPOST /api/v1/client/customersPOST /api/v1/client/customers/full
Required DataBasic user info (name, email, phone, DOB)User info + address + compliance fields + documents
KYC Compliance FieldsCollected via onboarding linkRequired: account_purpose, employment_status, source_of_funds, monthly_volume
KYC DocumentsNot required (customer submits via link)Optional images (raw base64 or data URI)
KYC WorkflowCustomer completes KYC via provided linkAll KYC data submitted in one request
ResponseReturns onboarding linkNo link returned (KYC submitted directly)
Use CaseSelf-service customer onboardingProgrammatic/automated onboarding
TransactionUser creation onlyUser creation + KYC submission (atomic)

Register Customer (With KYC Link)

Register a new customer account for your business. The customer will be automatically associated with your business and can be used for quotes, transactions, and other operations. This endpoint returns a KYC onboarding link that you can share with your customer to complete verification.

Endpoint

POST /api/v1/client/customers

Authentication

This endpoint requires HMAC authentication (Business API Key). Include your API key and signature in the request headers:

http
x-api-key: <your-api-key>x-timestamp: <RFC3339-timestamp>x-signature: <HMAC-signature>

For detailed information on HMAC authentication, see the Authentication Guide.

Request Body

FieldTypeRequiredDescriptionExample
emailstringYesCustomer's email address (must be valid email format)"customer@example.com"
first_namestringYesCustomer's first name"John"
last_namestringYesCustomer's last name"Doe"
middle_namestringNoCustomer's middle name"A"
passwordstringYesCustomer's password (minimum 8 characters)"SecurePass123!"
date_of_birthstringYesCustomer's date of birth (YYYY-MM-DD format)"1990-01-15"
phone_numberstringYesCustomer's phone number (E.164 format)"+1234567890"
country_codestringYesISO 3166-1 alpha-2 country code (2 characters)"US", "NG", "CA"
user_tagstringNoOptional user tag for transfers (4-20 alphanumeric lowercase)"customer123"

Request Example

json
{  "email": "customer@example.com",  "first_name": "John",  "last_name": "Doe",  "middle_name": "A",  "password": "SecurePass123!",  "date_of_birth": "1990-01-15",  "phone_number": "+1234567890",  "country_code": "US",  "user_tag": "customer123"}

Success Response (201 Created)

json
{  "message": "Customer created successfully",  "user": {    "id": "123e4567-e89b-12d3-a456-426614174000",    "email": "customer@example.com",    "first_name": "John",    "last_name": "Doe",    "role": "CUSTOMER",    "created_at": "2025-01-30T12:20:15Z",    "updated_at": "2025-01-30T12:20:15Z"  },  "user_id": "123e4567-e89b-12d3-a456-426614174000",  "onboarding_data": {    "link": "https://onboarding.example.com/session/abc123",    "expires_at": "2025-02-02T12:20:15Z"  }}

Note: The onboarding_data field is only present if the customer's email is not verified. It contains the KYC onboarding URL that you can share with your customer to complete their verification.

Response Fields

FieldTypeDescription
messagestringSuccess message
userobjectCreated customer information
user.idstring (UUID)Unique customer identifier
user.emailstringCustomer's email address
user.first_namestringCustomer's first name
user.last_namestringCustomer's last name
user.rolestringUser role (always "CUSTOMER")
user.created_atstring (ISO 8601)Account creation timestamp
user.updated_atstring (ISO 8601)Last update timestamp
user_idstring (UUID)Customer user ID (same as user.id)
onboarding_dataobject (optional)KYC onboarding session data (only present if email is not verified)
onboarding_data.linkstringURL to send to your customer for KYC completion
onboarding_data.expires_atstring (ISO 8601)When the onboarding link expires (typically 3 days)

Error Responses

Status CodeDescription
400Invalid request format or missing required fields
401Unauthorized - Invalid API key or signature
403Phone number or email already registered
422Password validation failed (must be at least 8 characters)
500Internal server error

Error Response Example

json
{  "error": "phone number already registered"}

Example: cURL

bash
curl -X POST https://api.sznd.app/api/v1/client/customers \  -H "Content-Type: application/json" \  -H "x-api-key: your_api_key_here" \  -H "x-timestamp: 2025-01-30T12:20:15Z" \  -H "x-signature: generated_signature_here" \  -d '{    "email": "customer@example.com",    "first_name": "John",    "last_name": "Doe",    "password": "SecurePass123!",    "date_of_birth": "1990-01-15",    "phone_number": "+1234567890",    "country_code": "US"  }'

Example: Postman Pre-request Script

Add this script to Postman's Pre-request Script tab to automatically generate HMAC signatures:

javascript
// Postman Pre-request Script for HMAC Authentication// Set these variables in your Postman environment:// - api_key: Your API key (e.g., "tf_live_abc123...")// - secret_key: Your secret key (e.g., "sk_live_def456...")
// Get API key and secret key from environment variablesconst apiKey = pm.environment.get("api_key");const secretKey = pm.environment.get("secret_key");
if (!apiKey || !secretKey) {    console.error("Please set 'api_key' and 'secret_key' in your Postman environment variables");    throw new Error("Missing API credentials");}
// Get request body (empty string if no body)let requestBody = "";if (pm.request.body && pm.request.body.raw) {    requestBody = pm.request.body.raw;}
// Generate RFC3339 timestamp (UTC)const now = new Date();const timestamp = now.toISOString();
// Create data to sign: body + "|" + timestampconst dataToSign = requestBody + "|" + timestamp;
// Generate HMAC-SHA256 signatureconst signature = CryptoJS.HmacSHA256(dataToSign, secretKey).toString(CryptoJS.enc.Hex);
// Set headers automaticallypm.request.headers.add({    key: "x-api-key",    value: apiKey});
pm.request.headers.add({    key: "x-timestamp",    value: timestamp});
pm.request.headers.add({    key: "x-signature",    value: signature});
// Log for debugging (optional - remove in production)console.log("API Key:", apiKey);console.log("Timestamp:", timestamp);console.log("Data to sign:", dataToSign);console.log("Signature:", signature);

Setup Instructions:

  1. Set Environment Variables in Postman:

    • Go to your Postman environment
    • Add api_key with your API key value
    • Add secret_key with your secret key value
  2. Add Pre-request Script:

    • Open your request in Postman
    • Go to the Pre-request Script tab
    • Paste the script above
    • The script will automatically generate and add the required headers
  3. Make Requests:

    • The script runs automatically before each request
    • No need to manually calculate signatures
    • Headers are set automatically

Note: Postman uses CryptoJS for HMAC. If you don't have it available, you can use the Node.js crypto module version below.

Example: JavaScript/TypeScript (Node.js)

javascript
const crypto = require('crypto');
const apiKey = "your_api_key_here";const secretKey = "your_secret_key_here";
const body = JSON.stringify({  email: "customer@example.com",  first_name: "John",  last_name: "Doe",  password: "SecurePass123!",  date_of_birth: "1990-01-15",  phone_number: "+1234567890",  country_code: "US"});
// Generate timestampconst timestamp = new Date().toISOString();
// Generate HMAC signatureconst dataToSign = body + "|" + timestamp;const signature = crypto  .createHmac('sha256', secretKey)  .update(dataToSign)  .digest('hex');
// Make requestconst response = await fetch('https://api.sznd.app/api/v1/client/customers', {  method: 'POST',  headers: {    'Content-Type': 'application/json',    'x-api-key': apiKey,    'x-timestamp': timestamp,    'x-signature': signature  },  body: body});
const data = await response.json();console.log(data);

Example: Python

python
import requestsimport hmacimport hashlibimport jsonfrom datetime import datetime
api_key = "your_api_key_here"secret_key = "your_secret_key_here"timestamp = datetime.utcnow().isoformat() + "Z"
body = {    "email": "customer@example.com",    "first_name": "John",    "last_name": "Doe",    "password": "SecurePass123!",    "date_of_birth": "1990-01-15",    "phone_number": "+1234567890",    "country_code": "US"}
body_string = json.dumps(body, separators=(',', ':'))timestamp = datetime.utcnow().isoformat() + "Z"data_to_sign = body_string + "|" + timestampsignature = hmac.new(    secret_key.encode(),    data_to_sign.encode(),    hashlib.sha256).hexdigest()
headers = {    "Content-Type": "application/json",    "x-api-key": api_key,    "x-timestamp": timestamp,    "x-signature": signature}
response = requests.post(    "https://api.sznd.app/api/v1/client/customers",    headers=headers,    json=body)
print(response.json())

Important Notes

  1. Business Association: Customers registered through this endpoint are automatically linked to your business account in the business_customers table.

  2. Customer Type: All customers registered via this API are set as INDIVIDUAL type by default.

  3. KYC Status: New customers start with PENDING KYC status. A KYC onboarding URL is automatically generated and returned in the onboarding_data field if the email is not verified. You can share this URL with your customer to complete their verification.

  4. Unique Constraints:

    • Email addresses must be unique across the system
    • Phone numbers must be unique across the system
    • If a customer with the same email or phone already exists, registration will fail
  5. Password Requirements:

    • Minimum 8 characters
    • Should include a mix of letters, numbers, and special characters for security
  6. User Tag: If provided, the user tag must be:

    • 4-20 characters long
    • Alphanumeric only
    • Lowercase only
    • Unique across the system
  7. Audit Trail: All customer registrations are logged in the business customer audit logs with action "ADDED".

Next Steps

After registering a customer, you can:


Virtual Wallet Onboarding

Onboard a customer to a virtual wallet for a specific currency. This endpoint initiates the onboarding process for supported currencies.

Prerequisites:

  • Customer must have completed KYC verification
  • Customer must be registered and linked to your business account

Endpoint

POST /api/v1/client/virtual-wallet/onboarding

Authentication

This endpoint requires HMAC authentication (Business API Key). Include your API key and signature in the request headers:

http
x-api-key: <your-api-key>x-timestamp: <RFC3339-timestamp>x-signature: <HMAC-signature>

For detailed information on HMAC authentication, see the Authentication Guide.

Request Body

FieldTypeRequiredDescriptionExample
user_idstring (UUID)YesCustomer's user ID (must be a valid UUID)"123e4567-e89b-12d3-a456-426614174000"
currencystringYesCurrency code for virtual wallet onboarding. Must be one of: CAD, USD, EUR, GBP"CAD"

Request Example

json
{  "user_id": "123e4567-e89b-12d3-a456-426614174000",  "currency": "CAD"}

Success Response (200 OK)

json
{  "message": "Customer onboarding completed successfully",  "currency": "CAD",  "user_id": "123e4567-e89b-12d3-a456-426614174000"}

Response Fields

FieldTypeDescription
messagestringSuccess message indicating onboarding completion
currencystringCurrency code that was onboarded
user_idstring (UUID)Customer user ID that was onboarded

Error Responses

Status CodeDescription
400Invalid request format, missing required fields, invalid UUID, or invalid currency
401Unauthorized - Invalid API key or signature
403User has not completed KYC verification
404No KYC record found for the provided user ID
500Internal server error during onboarding process

Error Response Examples

User has not completed KYC (403):

json
{  "error": "user is yet to complete kyc"}

No KYC record found (404):

json
{  "error": "no kyc record for id provided"}

Invalid currency (400):

json
{  "error": "invalid currency"}

Example: cURL

bash
curl -X POST https://api.sznd.app/api/v1/client/virtual-wallet/onboarding \  -H "Content-Type: application/json" \  -H "x-api-key: your_api_key_here" \  -H "x-timestamp: 2025-01-30T12:20:15Z" \  -H "x-signature: generated_signature_here" \  -d '{    "user_id": "123e4567-e89b-12d3-a456-426614174000",    "currency": "CAD"  }'

Important Notes

  1. KYC Requirement: The customer must have completed KYC verification before virtual wallet onboarding can proceed. The system checks the KYC status and will return a 403 error if KYC is not completed.

  2. Supported Currencies: Currently supports the following currencies:

    • CAD (Canadian Dollar)
    • USD (US Dollar)
    • EUR (Euro)
    • GBP (British Pound)
  3. Idempotency: If a customer is already onboarded for a specific currency, the endpoint may return success if the wallet already exists.

  4. Processing Time: The onboarding process typically completes within seconds.

  5. Error Handling: If the onboarding fails, a 500 error will be returned. Check the error message for details.

Full Customer Registration with KYC Data

Register a new customer with complete KYC information including documents in a single transaction. This endpoint creates the user account and submits all KYC data and documents for verification. All operations are performed in a transaction and will be rolled back if any step fails.

Use this endpoint when: You have already collected all KYC information (including documents) from your customer and want to submit everything programmatically in one request.

Endpoint

POST /api/v1/client/customers/full

Authentication

This endpoint requires HMAC authentication (Business API Key). Include your API key and signature in the request headers:

http
x-api-key: <your-api-key>x-timestamp: <RFC3339-timestamp>x-signature: <HMAC-signature>

For detailed information on HMAC authentication, see the Authentication Guide.

Request Body

Basic User Information

FieldTypeRequiredDescriptionExample
emailstringYesCustomer's email address (must be valid email format)"customer@example.com"
first_namestringYesCustomer's first name"John"
last_namestringYesCustomer's last name"Doe"
middle_namestringNoCustomer's middle name"A"
passwordstringYesCustomer's password (minimum 8 characters)"SecurePass123!"
date_of_birthstringYesCustomer's date of birth (YYYY-MM-DD format)"1990-01-15"
phone_numberstringYesCustomer's phone number (E.164 format)"+1234567890"
country_codestringYesISO 3166-1 alpha-2 country code (2 characters)"US", "NG", "CA"
user_tagstringNoOptional user tag for transfers (4-20 alphanumeric lowercase)"customer123"

Address Information

FieldTypeRequiredDescriptionExample
address.street_1stringYesPrimary street address (4–34 characters)"123 Main St"
address.street_2stringNoApartment, suite, or unit number"Apt 4B"
address.statestringYesState or province. Onboarding only: code or subdivision name accepted (code is advised). For beneficiary creation, code only is allowed—see Beneficiaries."NY" or "New York" (onboarding); use "NY" for beneficiaries
address.citystringYesCity name"San Francisco"
address.postal_codestringYesZIP or postal code (format validated per country)"94111"
address.country_codestringYesISO 3166-1 alpha-2 country code"US"

Identification Numbers (Optional)

Array of identification document numbers. Compatible with multiple document types.

FieldTypeRequiredDescriptionExample
identification_numbers[].identification_classstringYesDocument type: "visa", "cct", "passport", "id_card", "drivers_license""passport"
identification_numbers[].identification_numberstringYesDocument number"A12345678"
identification_numbers[].issuing_countrystringYesISO 3166-1 alpha-2 country code"US"
identification_numbers[].issued_atstringNoIssue date (YYYY-MM-DD format)"2020-01-15"
identification_numbers[].expires_atstringNoExpiry date (YYYY-MM-DD format)"2030-01-15"

Document Images (Optional)

Base64-encoded document images. You can provide images for ID card, passport, driver's license, and selfie photo.

ID Card:

  • documents.id_card_front - Base64 encoded front image
  • documents.id_card_back - Base64 encoded back image
  • documents.id_card_number - Document number
  • documents.id_card_issuing_country - ISO country code
  • documents.id_card_issued_at - Issue date (YYYY-MM-DD)
  • documents.id_card_expires_at - Expiry date (YYYY-MM-DD)

Passport:

  • documents.passport_front - Base64 encoded front image
  • documents.passport_back - Base64 encoded back image
  • documents.passport_number - Document number
  • documents.passport_issuing_country - ISO country code
  • documents.passport_issued_at - Issue date (YYYY-MM-DD)
  • documents.passport_expires_at - Expiry date (YYYY-MM-DD)

Driver's License:

  • documents.drivers_license_front - Base64 encoded front image
  • documents.drivers_license_back - Base64 encoded back image
  • documents.drivers_license_number - Document number
  • documents.drivers_license_issuing_country - ISO country code
  • documents.drivers_license_issued_at - Issue date (YYYY-MM-DD)
  • documents.drivers_license_expires_at - Expiry date (YYYY-MM-DD)

Selfie Photo:

  • documents.selfie_photo - Base64 encoded selfie photo

Image encoding: Document image fields accept either raw standard base64 or a data URI (data:image/jpeg;base64,... / data:image/png;base64,...).

KYC Compliance Fields (Required)

These fields are written to the KYC provider vault as custom fields and are required before headless KYC can run. Use the exact enum values below (case-sensitive).

FieldTypeRequiredDescriptionExample
account_purposestringYesIntended use of the account"PersonalOrLivingExpenses"
account_purpose_otherstringConditionalRequired when account_purpose is "Other""Receiving royalties"
employment_statusstringYesCurrent employment status"Employed"
source_of_fundsstringYesOrigin of funds for transactions"Salary"
monthly_volumestringYesExpected monthly transaction volume"LessThan5K"
occupation_codestringNoOccupation classification code"131199"

Allowed account_purpose values:

ValueMeaning
CharitableDonationsCharitable donations
EcommerceRetailPaymentsEcommerce / retail payments
InvestmentPurposesInvestment purposes
OperatingACompanyOperating a company
OtherOther (requires account_purpose_other)
PaymentsToFriendsOrFamilyAbroadPayments to friends or family abroad
PersonalOrLivingExpensesPersonal or living expenses
ProtectWealthProtect wealth
PurchaseGoodsAndServicesPurchase goods and services
ReceivePaymentForFreelancingReceive payment for freelancing
ReceiveSalaryReceive salary

Allowed employment_status values:

Employed · Homemaker · Retired · SelfEmployed · Student · Unemployed

Allowed source_of_funds values:

CompanyFunds · EcommerceReseller · GamblingProceeds · Gifts · GovernmentBenefits · Inheritance · InvestmentsLoans · PensionRetirement · Salary · SaleOfAssetsRealEstate · Savings · SomeoneElsesFunds

Allowed monthly_volume values:

ValueMeaning
LessThan5KLess than $5,000
From5KTo10K$5,000 – $10,000
From10KTo50K$10,000 – $50,000
GreaterThan50KGreater than $50,000

Additional Fields (Optional)

FieldTypeRequiredDescriptionExample
nationalitystringNoISO 3166-1 alpha-2 country code"US"
us_tax_idstringNoUS Social Security Number (SSN9)"123456789"
ssn4stringNoLast 4 digits of SSN"6789"
ssn9stringNoFull 9-digit SSN (alternative to us_tax_id)"123456789"
itinstringNoIndividual Taxpayer Identification Number"912345678"
bvnstringNoBank Verification Number (Nigeria)"12345678901"

Request Example

json
{  "email": "customer@example.com",  "first_name": "John",  "last_name": "Doe",  "middle_name": "A",  "password": "SecurePass123!",  "date_of_birth": "1990-01-15",  "phone_number": "+1234567890",  "country_code": "US",  "user_tag": "customer123",  "address": {    "street_1": "123 Main St",    "street_2": "Apt 4B",    "state": "CA",    "city": "San Francisco",    "postal_code": "94111",    "country_code": "US"  },  "account_purpose": "PersonalOrLivingExpenses",  "employment_status": "Employed",  "source_of_funds": "Salary",  "monthly_volume": "LessThan5K",  "identification_numbers": [    {      "identification_class": "passport",      "identification_number": "A12345678",      "issuing_country": "US",      "issued_at": "2020-01-15",      "expires_at": "2030-01-15"    }  ],  "documents": {    "passport_front": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",    "passport_back": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",    "passport_number": "A12345678",    "passport_issuing_country": "US",    "passport_issued_at": "2020-01-15",    "passport_expires_at": "2030-01-15",    "selfie_photo": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."  },  "nationality": "US",  "us_tax_id": "123456789"}

Success Response (201 Created)

json
{  "message": "Customer created and KYC data submitted successfully",  "user": {    "id": "123e4567-e89b-12d3-a456-426614174000",    "email": "customer@example.com",    "first_name": "John",    "last_name": "Doe",    "role": "CUSTOMER",    "created_at": "2025-01-30T12:20:15Z",    "updated_at": "2025-01-30T12:20:15Z"  },  "user_id": "123e4567-e89b-12d3-a456-426614174000"}

Response Fields

FieldTypeDescription
messagestringSuccess message
userobjectCreated customer information
user.idstring (UUID)Unique customer identifier
user.emailstringCustomer's email address
user.first_namestringCustomer's first name
user.last_namestringCustomer's last name
user.rolestringUser role (always "CUSTOMER")
user.created_atstring (ISO 8601)Account creation timestamp
user.updated_atstring (ISO 8601)Last update timestamp
user_idstring (UUID)Customer user ID (same as user.id)

Error Responses

Status CodeDescription
400Invalid request format, missing required fields, or invalid data
401Unauthorized - Invalid API key or signature
403Phone number or email already registered
500Internal server error (transaction will be rolled back)

Error Response Example

json
{  "error": "failed to submit KYC data"}

Example: cURL

Minimal Example (Required Fields Only):

bash
curl -X POST https://api.sznd.app/api/v1/client/customers/full \  -H "Content-Type: application/json" \  -H "x-api-key: your_api_key_here" \  -H "x-timestamp: 2025-01-30T12:20:15Z" \  -H "x-signature: generated_signature_here" \  -d '{    "email": "customer@example.com",    "first_name": "John",    "last_name": "Doe",    "password": "SecurePass123!",    "date_of_birth": "1990-01-15",    "phone_number": "+1234567890",    "country_code": "US",    "address": {      "street_1": "123 Main St",      "state": "CA",      "city": "San Francisco",      "postal_code": "94111",      "country_code": "US"    },    "account_purpose": "PersonalOrLivingExpenses",    "employment_status": "Employed",    "source_of_funds": "Salary",    "monthly_volume": "LessThan5K"  }'

Complete Example (With All KYC Data and Documents):

bash
curl -X POST https://api.sznd.app/api/v1/client/customers/full \  -H "Content-Type: application/json" \  -H "x-api-key: your_api_key_here" \  -H "x-timestamp: 2025-01-30T12:20:15Z" \  -H "x-signature: generated_signature_here" \  -d '{    "email": "customer@example.com",    "first_name": "John",    "last_name": "Doe",    "middle_name": "A",    "password": "SecurePass123!",    "date_of_birth": "1990-01-15",    "phone_number": "+1234567890",    "country_code": "US",    "user_tag": "customer123",    "address": {      "street_1": "123 Main St",      "street_2": "Apt 4B",      "state": "CA",      "city": "San Francisco",      "postal_code": "94111",      "country_code": "US"    },    "identification_numbers": [      {        "identification_class": "passport",        "identification_number": "A12345678",        "issuing_country": "US",        "issued_at": "2020-01-15",        "expires_at": "2030-01-15"      }    ],    "documents": {      "passport_front": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k=",      "passport_back": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k=",      "passport_number": "A12345678",      "passport_issuing_country": "US",      "passport_issued_at": "2020-01-15",      "passport_expires_at": "2030-01-15",      "selfie_photo": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k="    },    "account_purpose": "PersonalOrLivingExpenses",    "employment_status": "Employed",    "source_of_funds": "Salary",    "monthly_volume": "LessThan5K",    "nationality": "US",    "us_tax_id": "123456789"  }'

Note: In the complete example above, the base64 image strings are truncated for readability. In a real request, you would include the full base64-encoded image data. The images should be clear, legible, and meet the minimum size requirements (20KB per image).

Important Notes

  1. Transaction Safety: All operations (user creation, KYC submission, reference creation) are performed in a single database transaction. If any step fails, the entire operation is rolled back.

  2. Document Format: Document images must be base64-encoded (raw base64 or data:image/...;base64,... data URI). Supported formats include JPEG, PNG, and PDF. Images should be clear and legible.

  3. Document Requirements:

    • Documents must be current and not expired (unless expiry date is not applicable)
    • Both front and back images are required where applicable
    • Minimum image size: 20KB per image
    • Images must be clear and readable
  4. KYC Compliance Fields: account_purpose, employment_status, source_of_funds, and monthly_volume are required. Use only the allowed enum values listed above. Missing or invalid values return 400 before the account is created.

  5. KYC Processing: The KYC data and documents are submitted for verification immediately upon registration. The verification status will be updated via webhooks.

  6. Address Information: Complete address information is required and will be used for verification purposes.

  7. Identification Numbers: You can provide multiple identification numbers (e.g., passport, visa, etc.) in the identification_numbers array.

  8. Error Handling: If the KYC submission fails, the entire transaction (including user creation) will be rolled back to maintain data consistency.


Update Customer

Update an existing customer's information. All fields are optional - only provided fields will be updated. If documents are provided, they will be automatically uploaded to the KYC provider if the customer has an existing KYC inquiry.

Use this endpoint when: You need to update customer information, upload additional documents, or modify address details after initial registration.

Endpoint

PATCH /api/v1/client/customers/:customer_id

Authentication

This endpoint requires HMAC authentication (Business API Key). Include your API key and signature in the request headers:

http
x-api-key: <your-api-key>x-timestamp: <RFC3339-timestamp>x-signature: <HMAC-signature>

For detailed information on HMAC authentication, see the Authentication Guide.

Path Parameters

ParameterTypeRequiredDescriptionExample
customer_idstring (UUID)YesThe UUID of the customer to update"123e4567-e89b-12d3-a456-426614174000"

Request Body

All fields are optional. Only fields you want to update need to be included.

Basic User Information

FieldTypeRequiredDescriptionExample
first_namestringNoCustomer's first name"John"
last_namestringNoCustomer's last name"Doe"
middle_namestringNoCustomer's middle name"A"
phone_numberstringNoCustomer's phone number (E.164 format)"+1234567890"
date_of_birthstringNoCustomer's date of birth (YYYY-MM-DD format)"1990-01-15"
user_tagstringNoOptional user tag for transfers (4-20 alphanumeric lowercase)"customer123"

Note: Email cannot be updated via this endpoint.

Address Information (Optional)

FieldTypeRequiredDescriptionExample
address.street_1stringNoPrimary street address"123 Main St"
address.street_2stringNoApartment, suite, or unit number"Apt 4B"
address.statestringNoState or province"California"
address.citystringNoCity name"San Francisco"
address.postal_codestringNoZIP code or postal code"94111"
address.country_codestringNoISO 3166-1 alpha-2 country code"US"

Note: If address doesn't exist and you're creating it, street_1, city, postal_code, and country_code are required. For updates, all fields are optional and only provided fields will be updated.

Document Images (Optional)

Base64-encoded document images. If provided and the customer has an existing KYC inquiry, documents will be automatically uploaded to the KYC provider. Document uploads are best-effort - errors are logged but don't fail the update.

ID Card:

  • documents.id_card_front - Base64 encoded front image
  • documents.id_card_back - Base64 encoded back image

Passport:

  • documents.passport_front - Base64 encoded front image
  • documents.passport_back - Base64 encoded back image

Driver's License:

  • documents.drivers_license_front - Base64 encoded front image
  • documents.drivers_license_back - Base64 encoded back image

Selfie Photo:

  • documents.selfie_photo - Base64 encoded selfie photo

Additional Fields (Optional)

FieldTypeRequiredDescriptionExample
nationalitystringNoISO 3166-1 alpha-2 country code"US"
us_tax_idstringNoUS Social Security Number (SSN9)"123456789"

Request Examples

Update Basic Information:

json
{  "first_name": "Jane",  "last_name": "Smith",  "phone_number": "+1987654321"}

Update Address:

json
{  "address": {    "street_1": "456 Oak Ave",    "street_2": "Suite 200",    "city": "Los Angeles",    "postal_code": "90001",    "country_code": "US",      "state": "California"  }}

Upload Documents:

json
{  "documents": {    "id_card_front": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",    "id_card_back": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",    "selfie_photo": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."  }}

Complete Update (Multiple Fields):

json
{  "first_name": "Jane",  "last_name": "Smith",  "date_of_birth": "1985-05-20",  "address": {    "street_1": "456 Oak Ave",    "city": "Los Angeles",    "postal_code": "90001",    "country_code": "US"  },  "documents": {    "passport_front": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",    "selfie_photo": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."  }}

Success Response (200 OK)

json
{  "message": "Customer updated successfully",  "user": {    "id": "123e4567-e89b-12d3-a456-426614174000",    "email": "customer@example.com",    "first_name": "Jane",    "last_name": "Smith",    "role": "CUSTOMER",    "created_at": "2025-01-30T12:20:15Z",    "updated_at": "2025-01-30T14:30:45Z"  },  "user_id": "123e4567-e89b-12d3-a456-426614174000"}

Response Fields

FieldTypeDescription
messagestringSuccess message
userobjectUpdated customer information
user.idstring (UUID)Unique customer identifier
user.emailstringCustomer's email address (cannot be changed)
user.first_namestringCustomer's first name
user.last_namestringCustomer's last name
user.rolestringUser role (always "CUSTOMER")
user.created_atstring (ISO 8601)Account creation timestamp
user.updated_atstring (ISO 8601)Last update timestamp
user_idstring (UUID)Customer user ID (same as user.id)

Error Responses

Status CodeDescription
400Invalid request format, invalid customer ID, or invalid date format
401Unauthorized - Invalid API key or signature
403Customer does not belong to your business
404Customer not found
409Phone number or user tag already in use
500Internal server error

Error Response Examples

Customer Not Found (404):

json
{  "error": "customer not found"}

Customer Doesn't Belong to Business (403):

json
{  "error": "customer does not belong to your business"

Phone Number Already in Use (409):

json
{  "error": "phone number already registered"}

Invalid Date Format (400):

json
{  "error": "invalid date format, expected YYYY-MM-DD"}

Example: cURL

Update Basic Information:

bash
curl -X PATCH https://api.sznd.app/api/v1/client/customers/123e4567-e89b-12d3-a456-426614174000 \  -H "Content-Type: application/json" \  -H "x-api-key: your_api_key_here" \  -H "x-timestamp: 2025-01-30T14:30:45Z" \  -H "x-signature: generated_signature_here" \  -d '{    "first_name": "Jane",    "last_name": "Smith",    "phone_number": "+1987654321"  }'

Update Address:

bash
curl -X PATCH https://api.sznd.app/api/v1/client/customers/123e4567-e89b-12d3-a456-426614174000 \  -H "Content-Type: application/json" \  -H "x-api-key: your_api_key_here" \  -H "x-timestamp: 2025-01-30T14:30:45Z" \  -H "x-signature: generated_signature_here" \  -d '{    "address": {      "street_1": "456 Oak Ave",      "city": "Los Angeles",      "postal_code": "90001",      "country_code": "US"    }  }'

Upload Documents:

bash
curl -X PATCH https://api.sznd.app/api/v1/client/customers/123e4567-e89b-12d3-a456-426614174000 \  -H "Content-Type: application/json" \  -H "x-api-key: your_api_key_here" \  -H "x-timestamp: 2025-01-30T14:30:45Z" \  -H "x-signature: generated_signature_here" \  -d '{    "documents": {      "id_card_front": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",      "selfie_photo": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."    }  }'

Example: JavaScript/TypeScript (Node.js)

javascript
const crypto = require('crypto');
const apiKey = "your_api_key_here";const secretKey = "your_secret_key_here";const customerId = "123e4567-e89b-12d3-a456-426614174000";
const body = JSON.stringify({  first_name: "Jane",  last_name: "Smith",  phone_number: "+1987654321",  address: {    street_1: "456 Oak Ave",    city: "Los Angeles",    postal_code: "90001",    country_code: "US"  }});
// Generate timestampconst timestamp = new Date().toISOString();
// Generate HMAC signatureconst dataToSign = body + "|" + timestamp;const signature = crypto  .createHmac('sha256', secretKey)  .update(dataToSign)  .digest('hex');
// Make requestconst response = await fetch(`https://api.sznd.app/api/v1/client/customers/${customerId}`, {  method: 'PATCH',  headers: {    'Content-Type': 'application/json',    'x-api-key': apiKey,    'x-timestamp': timestamp,    'x-signature': signature  },  body: body});
const data = await response.json();console.log(data);

Example: Python

python
import requestsimport hmacimport hashlibimport jsonfrom datetime import datetime
api_key = "your_api_key_here"secret_key = "your_secret_key_here"customer_id = "123e4567-e89b-12d3-a456-426614174000"
body = {    "first_name": "Jane",    "last_name": "Smith",    "phone_number": "+1987654321",    "address": {        "street_1": "456 Oak Ave",        "city": "Los Angeles",        "postal_code": "90001",        "country_code": "US"    }}
body_string = json.dumps(body, separators=(',', ':'))timestamp = datetime.utcnow().isoformat() + "Z"data_to_sign = body_string + "|" + timestampsignature = hmac.new(    secret_key.encode(),    data_to_sign.encode(),    hashlib.sha256).hexdigest()
headers = {    "Content-Type": "application/json",    "x-api-key": api_key,    "x-timestamp": timestamp,    "x-signature": signature}
response = requests.patch(    f"https://api.sznd.app/api/v1/client/customers/{customer_id}",    headers=headers,    json=body)
print(response.json())

Important Notes

  1. Partial Updates: All fields are optional. Only fields you provide will be updated. Fields not included in the request will remain unchanged.

  2. Email Cannot Be Updated: Email addresses cannot be changed via this endpoint. If you need to change an email, you'll need to create a new customer account.

  3. Document Uploads:

    • Documents are automatically uploaded to the KYC provider if the customer has an existing KYC inquiry
    • Document uploads are best-effort - if upload fails, the error is logged but the customer update still succeeds
    • If no KYC inquiry exists, document uploads are skipped (no error returned)
  4. Address Updates:

    • If address doesn't exist, it will be created (requires street_1, city, postal_code, and country_code)
    • If address exists, only provided fields will be updated
    • Partial address updates are supported
  5. Phone Number Validation:

    • Phone numbers must be unique across the system
    • If updating to a phone number that's already in use by another customer, the update will fail with a 409 error
  6. User Tag Validation:

    • User tags must be 4-20 characters, alphanumeric, lowercase
    • User tags must be unique across the system
    • If updating to a tag that's already in use, the update will fail with a 409 error
  7. Date of Birth Format: Must be in YYYY-MM-DD format (e.g., "1990-01-15").

  8. Transaction Safety: All database updates happen within a transaction. If any update fails, the entire operation is rolled back.

  9. Business Ownership: You can only update customers that belong to your business. Attempting to update a customer from another business will result in a 403 error.

  10. Document Format: Document images must be base64-encoded. Supported formats include JPEG, PNG, and PDF. Images should be clear and legible.


List Customers

List customers registered under your business. Results are paginated and include basic profile fields (name, email, phone, date of birth, country code, user tag) plus Footprint onboarding status for each customer.

Use this endpoint when: You need to display a customer directory, sync customers into your system, or check onboarding status in bulk without calling the per-customer onboarding endpoint for each row.

Endpoint

GET /api/v1/client/customers

Authentication

This endpoint requires HMAC authentication (Business API Key). Include your API key and signature in the request headers:

http
x-api-key: <your-api-key>x-timestamp: <RFC3339-timestamp>x-signature: <HMAC-signature>

For detailed information on HMAC authentication, see the Authentication Guide.

Query Parameters

ParameterTypeRequiredDescriptionExample
page_idintegerNoPage number (1-based). Default: 11
page_sizeintegerNoItems per page. Default: 20, max: 10020
searchstringNoSearch across email, first name, last name, middle name, and phone number"john@example.com"
created_between_fromstring (RFC3339)NoFilter customers linked on or after this time. Requires tz when set."2025-01-01T00:00:00Z"
created_between_tostring (RFC3339)NoFilter customers linked on or before this time. Requires tz when set."2025-12-31T23:59:59Z"
tzstring (IANA)ConditionalTimezone for date filters. Required when created_between_from or created_between_to is set."America/Toronto"

Success Response (200 OK)

json
{  "data": [    {      "customer_id": "123e4567-e89b-12d3-a456-426614174000",      "email": "customer@example.com",      "first_name": "John",      "last_name": "Doe",      "middle_name": "A",      "phone_number": "+1234567890",      "date_of_birth": "1990-01-15",      "country_code": "US",      "user_tag": "customer123",      "is_email_verified": true,      "status": "ACTIVE",      "customer_type": "INDIVIDUAL",      "kyc_status": "PENDING",      "risk_level": "LOW",      "onboarding_status": {        "kyc_status": "PASS",        "message": "KYC verification completed successfully"      },      "created_at": "2025-01-15T10:30:00Z",      "updated_at": "2025-01-20T14:00:00Z"    }  ],  "pagination": {    "page_id": 1,    "page_size": 20,    "total_count": 150,    "total_pages": 8  }}

Response Fields

Customer object (data[])

FieldTypeDescription
customer_idstring (UUID)Unique customer user ID
emailstringCustomer email address
first_namestringFirst name
last_namestringLast name
middle_namestring (optional)Middle name, if set
phone_numberstring (optional)Phone number, if set
date_of_birthstring (optional)Date of birth in YYYY-MM-DD format
country_codestring (optional)ISO 3166-1 alpha-2 country code from the customer's latest address
user_tagstring (optional)Transfer tag, if set
is_email_verifiedbooleanWhether the customer's email is verified
statusstringBusiness customer link status
customer_typestringCustomer type (e.g. INDIVIDUAL)
kyc_statusstringKYC status stored on the business–customer link
risk_levelstringAssigned risk level
onboarding_statusobjectFootprint-derived onboarding status (see below)
created_atstring (ISO 8601)When the customer was linked to your business
updated_atstring (ISO 8601)Last update to the business–customer link

onboarding_status object

FieldTypeDescription
kyc_statusstringPASS, PENDING, or KYC_NOT_INITIATED
messagestring (optional)Human-readable onboarding status message

Pagination object

FieldTypeDescription
page_idintegerCurrent page (1-based)
page_sizeintegerNumber of items per page
total_countintegerTotal customers matching the query
total_pagesintegerTotal number of pages

Onboarding Status Values

StatusDescription
PASSKYC verification completed successfully (Footprint status is PASS, ACCEPTED, VERIFIED, or APPROVED)
PENDINGKYC has been initiated but is not yet complete
KYC_NOT_INITIATEDNo Footprint KYC record exists for this customer

Error Responses

Status CodeDescription
400Invalid query parameters (e.g. date filter without tz)
401Unauthorized — invalid API key or signature
500Internal server error

Request Examples

cURL

bash
curl -X GET \  "https://api.sznd.app/api/v1/client/customers?page_id=1&page_size=20" \  -H "Content-Type: application/json" \  -H "x-api-key: your_api_key_here" \  -H "x-timestamp: 2025-01-30T12:00:00Z" \  -H "x-signature: your_hmac_signature_here"

JavaScript/TypeScript

javascript
const timestamp = new Date().toISOString();const signature = generateHMACSignature("", timestamp); // Empty body for GET request
const response = await fetch(  "https://api.sznd.app/api/v1/client/customers?page_id=1&page_size=20",  {    method: "GET",    headers: {      "Content-Type": "application/json",      "x-api-key": apiKey,      "x-timestamp": timestamp,      "x-signature": signature,    },  });
const data = await response.json();console.log(data.pagination.total_count, data.data.length);

Python

python
import requestsfrom datetime import datetime, timezone
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")signature = generate_hmac_signature("", timestamp)
response = requests.get(    "https://api.sznd.app/api/v1/client/customers",    params={"page_id": 1, "page_size": 20, "search": "john@example.com"},    headers={        "Content-Type": "application/json",        "x-api-key": api_key,        "x-timestamp": timestamp,        "x-signature": signature,    },)response.raise_for_status()data = response.json()print(data["pagination"]["total_count"], len(data["data"]))

Notes

  1. Business scope: Only customers linked to your business are returned. Customers belonging to other businesses are never included.

  2. Pagination: Use page_id and page_size to walk through results. total_count and total_pages in the response tell you when you've fetched all pages.

  3. Onboarding status: Each row includes onboarding_status so you can check KYC state in one call. Use GET /api/v1/client/customers/:customer_id/onboarding-status when you only need status for a single customer.

  4. Optional fields: date_of_birth, country_code, user_tag, middle_name, and phone_number are omitted from the response when not set on the customer.

  5. Date filters: When using created_between_from or created_between_to, you must also pass tz (IANA timezone, e.g. America/Toronto).


Get Customer Onboarding Status

Get the KYC/onboarding status of a customer registered by your business. This endpoint returns the current KYC verification status from the KYC provider (Footprint or Persona).

Use this endpoint when: You need to check the KYC verification status of a customer to determine if they can proceed with transactions or if additional verification is required.

Endpoint

GET /api/v1/client/customers/:customer_id/onboarding-status

Authentication

This endpoint requires HMAC authentication (Business API Key). Include your API key and signature in the request headers:

http
x-api-key: <your-api-key>x-timestamp: <RFC3339-timestamp>x-signature: <HMAC-signature>

For detailed information on HMAC authentication, see the Authentication Guide.

Path Parameters

ParameterTypeRequiredDescriptionExample
customer_idstring (UUID)YesThe unique identifier of the customer"123e4567-e89b-12d3-a456-426614174000"

Success Response (200 OK)

json
{  "kyc_status": "PASS",  "message": "KYC verification completed successfully"}

Response Fields

FieldTypeDescription
kyc_statusstringThe KYC verification status. Possible values: PASS, PENDING, KYC_NOT_INITIATED
messagestring (optional)Human-readable message describing the KYC status

KYC Status Values

StatusDescription
PASSKYC verification has been completed successfully. The customer has a verified status (PASS, ACCEPTED, VERIFIED, or APPROVED) from either Footprint or Persona.
PENDINGKYC verification is in progress or pending. The customer has a KYC record but verification is not yet complete.
KYC_NOT_INITIATEDNo KYC verification has been started. The customer does not have any KYC records with Footprint or Persona.

Error Responses

Status CodeDescription
400Invalid customer ID format
401Unauthorized - Invalid API key or signature
403Forbidden - Customer does not belong to your business
404Customer not found
500Internal server error

Error Response Example

json
{  "error": "customer does not belong to your business or customer not found"}

Request Example

cURL

bash
curl -X GET \  "https://api.sznd.app/api/v1/client/customers/123e4567-e89b-12d3-a456-426614174000/onboarding-status" \  -H "Content-Type: application/json" \  -H "x-api-key: your_api_key_here" \  -H "x-timestamp: 2025-01-30T12:00:00Z" \  -H "x-signature: your_hmac_signature_here"

JavaScript/TypeScript

javascript
const customerId = "123e4567-e89b-12d3-a456-426614174000";const timestamp = new Date().toISOString();const signature = generateHMACSignature("", timestamp); // Empty body for GET request
const response = await fetch(  `https://api.sznd.app/api/v1/client/customers/${customerId}/onboarding-status`,  {    method: "GET",    headers: {      "Content-Type": "application/json",      "x-api-key": apiKey,      "x-timestamp": timestamp,      "x-signature": signature,    },  });
const data = await response.json();console.log(data);// {//   "kyc_status": "PASS",//   "message": "KYC verification completed successfully"// }

Python

python
import requestsimport hmacimport hashlibfrom datetime import datetime
api_key = "your_api_key_here"secret_key = "your_secret_key_here"customer_id = "123e4567-e89b-12d3-a456-426614174000"
timestamp = datetime.utcnow().isoformat() + "Z"# For GET requests, sign empty body + timestampdata_to_sign = "" + "|" + timestampsignature = hmac.new(    secret_key.encode(),    data_to_sign.encode(),    hashlib.sha256).hexdigest()
headers = {    "Content-Type": "application/json",    "x-api-key": api_key,    "x-timestamp": timestamp,    "x-signature": signature}
response = requests.get(    f"https://api.sznd.app/api/v1/client/customers/{customer_id}/onboarding-status",    headers=headers)
print(response.json())# {#   "kyc_status": "PASS",#   "message": "KYC verification completed successfully"# }

Important Notes

  1. Business Ownership: You can only check the onboarding status of customers that belong to your business. Attempting to check a customer from another business will result in a 403 error.

  2. KYC Provider: The endpoint checks both Footprint and Persona external references. If a customer has a successful status (PASS, ACCEPTED, VERIFIED, or APPROVED) from either provider, the status will be PASS.

  3. Status Priority: The endpoint checks Footprint first, then Persona. If either provider has a successful status, PASS is returned. Otherwise, if a record exists with a non-successful status, PENDING is returned.

  4. No KYC Record: If the customer has no KYC records with either provider, the status will be KYC_NOT_INITIATED.

  5. Status Updates: KYC status is updated via webhooks when the KYC provider processes verification. You can also poll this endpoint to check the current status.

  6. Use Cases:

    • Check if a customer can proceed with transactions (status = PASS)
    • Determine if KYC verification is still pending
    • Verify if a customer has started the KYC process

Related Resources