Skip to main content

Documentation Index

Fetch the complete documentation index at: https://ramps-docs-sync-onboarding-examples.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

This guide provides comprehensive information about creating customers in the Grid API, including customer types, onboarding models, registration, and management.

Onboarding model

There are two models for regulated and unregulated platforms.
  • Regulated platforms: Use your existing compliance processes. Create individual and business customers directly via POST /customers. The information you supply is used for beneficiary/counterparty compliance screening.
  • Unregulated platforms: Grid performs KYC (individuals) and KYB (businesses). Choose either the hosted link flow (redirect or embed Grid’s provider for verification) or direct API onboarding — for INDIVIDUAL customers, submit personal information through POST /customers; for BUSINESS customers, also register beneficial owners via POST /beneficial-owners. Both paths produce the same kycStatus transitions and emit the same CUSTOMER.KYC_APPROVED / CUSTOMER.KYC_REJECTED / CUSTOMER.KYC_PENDING (and CUSTOMER.KYB_* equivalents) webhooks. While verification is pending, allow customers to finish account setup but block funding and money movement.

Customer Types

The Grid API supports both individual and business customers. While the API schema itself makes most Personally Identifiable Information (PII) optional at initial creation, specific fields may become mandatory based on the currencies the customer will transact with. Your platform’s configuration (retrieved via GET /config) includes a supportedCurrencies array. Each currency object within this array has a providerRequiredCustomerFields list. If a customer is intended to use a specific currency, any fields listed for that currency must be provided when creating or updating the customer. The only required field for all customers is customerType (INDIVIDUAL or BUSINESS). It’s recommended to also pass a platformCustomerId to tie the customer to your internal identifier, but if you don’t, one will be generated automatically. If using sending and receiving to just-in-time UMA addresses, you’ll also need to specify the bank account information

Creating Customers

Regulated platforms have lighter KYC requirements since they handle compliance verification internally.
The KYC/KYB flow allows you to onboard customers through direct API calls.Regulated financial institutions can:
  • Direct API Onboarding: Create customers directly via API calls with minimal verification
  • Internal KYC/KYB: Handle identity verification through your own compliance systems
  • Reduced Documentation: Only provide essential customer information required by your payment counterparty or service provider.
  • Faster Onboarding: Streamlined process for known, verified customers

Creating Customers via Direct API

For regulated platforms, you can create customers directly through the API without requiring external KYC verification:To register a new customer in the system, use the POST /customers endpoint:
curl -X POST "https://api.lightspark.com/grid/2025-10-13/customers" \
  -H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "platformCustomerId": "customer_12345",
    "customerType": "INDIVIDUAL",
    "fullName": "Jane Doe",
    "birthDate": "1992-03-25",
    "nationality": "US",
    "address": {
      "line1": "123 Pine Street",
      "city": "Seattle",
      "state": "WA",
      "postalCode": "98101",
      "country": "US"
    }
  }'
The examples below show a more comprehensive set of data. Not all fields are strictly required by the API for customer creation itself, but become necessary based on currency and UMA provider requirements if using UMA.
{
  "platformCustomerId": "9f84e0c2a72c4fa",
  "customerType": "INDIVIDUAL",
  "fullName": "John Sender",
  "birthDate": "1985-06-15",
  "address": {
    "line1": "Paseo de la Reforma 222",
    "line2": "Piso 15",
    "city": "Ciudad de México",
    "state": "Ciudad de México",
    "postalCode": "06600",
    "country": "MX"
  }
}
Unregulated platforms rely on Grid to run KYC for individuals and KYB for businesses. You can onboard customers either through the hosted KYC/KYB link flow below, or by submitting customer data directly through the API. Both paths produce the same kycStatus transitions and emit the same CUSTOMER.KYC_APPROVED / CUSTOMER.KYC_REJECTED / CUSTOMER.KYC_PENDING (and CUSTOMER.KYB_* equivalents) webhooks.
Either path works for unregulated platforms:
  • Hosted flow: Redirect customers to a Grid-hosted link (or embed the provider SDK) for identity verification. Best when you want Grid to handle the entire collection UX.
  • Direct API onboarding: Collect customer information in your own UI and submit it via the API. For INDIVIDUAL customers (KYC), personal information goes through POST /customers. For BUSINESS customers (KYB), you also register beneficial owners via POST /beneficial-owners. Submit for review with POST /verifications.
The hosted KYC flow provides a secure, hosted interface where customers can complete their identity verification and onboarding process.The flow is two steps: create the customer with the information you have, then generate a hosted KYC link for that customer. The customer’s kycStatus stays PENDING until they complete the hosted flow.

1. Create the customer

Create the customer with POST /customers, supplying at least customerType and any fields you already have. See Configuring Customers for the full list of optional pre-fill fields.
curl -X POST "https://api.lightspark.com/grid/2025-10-13/customers" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "customerType": "INDIVIDUAL",
    "platformCustomerId": "9f84e0c2a72c4fa",
    "region": "US",
    "currencies": ["USD", "USDC"],
    "email": "jane.doe@example.com",
    "fullName": "Jane Doe"
  }'
Persist the returned id (the Grid customer ID) — you’ll need it for the next step.
curl -X POST "https://api.lightspark.com/grid/2025-10-13/customers/Customer:019542f5-b3e7-1d02-0000-000000000001/kyc-link" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "redirectUri": "https://yourapp.com/onboarding-complete"
  }'
Response:
{
  "kycUrl": "https://kyc.lightspark.com/onboard/abc123def456",
  "expiresAt": "2027-01-15T14:32:00Z",
  "provider": "SUMSUB",
  "token": "_act-sbx-jwt-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
The response always includes kycUrl for the hosted flow. For providers that support direct SDK integration (currently SUMSUB), a token is also returned — you can pass this to the provider’s web SDK to embed verification in your own UI instead of redirecting. Both paths update the customer’s kycStatus identically.

Complete KYC Process

1

Create the customer

Call POST /customers with customerType and any pre-fill fields you have. The returned id is the customer’s Grid ID; their kycStatus is PENDING until verification completes.
2

Generate the KYC link

Call POST /customers/{customerId}/kyc-link. Each call returns a fresh single-use kycUrl and expiresAt; previously-issued links remain single-use but aren’t invalidated.
The redirectUri you pass is embedded in the generated kycUrl and is used to automatically return the customer to your application after they complete verification.
3

Send the customer through verification

Redirect the customer to kycUrl, or — if you want to embed the flow directly — initialize the provider’s SDK with the returned token.
The hosted URL is single-use and expires at expiresAt. If a customer needs to retry, call the endpoint again to generate a new link.
4

Track the decision

Reaching your redirectUri only means the customer finished the hosted flow — not that they were approved. Wait for the final decision in one of two ways:
  • Webhook (recommended): Subscribe to CUSTOMER.KYC_APPROVED / CUSTOMER.KYC_REJECTED (and CUSTOMER.KYB_APPROVED / CUSTOMER.KYB_REJECTED for business customers) to be notified when the customer reaches a terminal status. CUSTOMER.KYC_PENDING (and the KYB_PENDING sibling) also fires when the customer is submitted for review — subscribe to it as well if you want to surface an “under review” state to the customer.
  • Polling: Call GET /customers/{customerId} and inspect kycStatus.
5

Handle completion

On APPROVED, the customer is ready to transact — proceed with account setup and unlock funding. On REJECTED, surface the appropriate next step (for example, regenerate the link or request manual review).

Direct API Onboarding

Prefer to collect identity information in your own UI and submit it to Grid yourself? Use the API directly instead of redirecting to a hosted link. The customer’s kycStatus transitions the same way and you receive the same CUSTOMER.KYC_APPROVED / CUSTOMER.KYC_REJECTED / CUSTOMER.KYC_PENDING (and CUSTOMER.KYB_* equivalents) webhooks.The shape of the flow depends on the customer type:
  • KYC (INDIVIDUAL customers) — supply the customer’s personal information through the customer endpoint. No beneficial owners are involved.
  • KYB (BUSINESS customers) — create the business customer, then register its beneficial owners, directors, and officers individually.
1

Create the customer with personal information

Call POST /customers with customerType: INDIVIDUAL and the personal information collected from the customer (legal name, date of birth, address, nationality, etc.). The returned id is the customer’s Grid ID; kycStatus starts at PENDING.
curl -X POST "https://api.lightspark.com/grid/2025-10-13/customers" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "customerType": "INDIVIDUAL",
    "platformCustomerId": "9f84e0c2a72c4fa",
    "fullName": "Jane Doe",
    "birthDate": "1985-06-15",
    "nationality": "US",
    "email": "jane.doe@example.com",
    "address": {
      "line1": "123 Pine Street",
      "city": "Seattle",
      "state": "WA",
      "postalCode": "98101",
      "country": "US"
    }
  }'
2

Upload supporting documents (if requested)

Some jurisdictions or currencies require an ID document or proof of address. Upload them with POST /documents using multipart/form-data, referencing the customer by customerId.
curl -X POST "https://api.lightspark.com/grid/2025-10-13/documents" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -F "documentHolder=Customer:019542f5-b3e7-1d02-0000-000000000001" \
  -F "documentType=PASSPORT" \
  -F "country=US" \
  -F "file=@./passport.jpg"
3

Submit for verification

Call POST /verifications to submit the customer for review. The response includes a verificationStatus. If anything is missing, verificationStatus is RESOLVE_ERRORS and the errors array describes exactly what to collect before retrying.
curl -X POST "https://api.lightspark.com/grid/2025-10-13/verifications" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001"
  }'
Submitted successfully:
{
  "id": "Verification:019542f5-b3e7-1d02-0000-000000000002",
  "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
  "verificationStatus": "IN_PROGRESS",
  "errors": [],
  "createdAt": "2025-10-03T12:00:00Z"
}
Blocked by missing data:
{
  "id": "Verification:019542f5-b3e7-1d02-0000-000000000001",
  "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
  "verificationStatus": "RESOLVE_ERRORS",
  "errors": [
    {
      "resourceId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
      "type": "MISSING_PROOF_OF_ADDRESS_DOCUMENT",
      "acceptedDocumentTypes": ["PROOF_OF_ADDRESS"],
      "reason": "Proof of address document is required"
    }
  ],
  "createdAt": "2025-10-03T12:00:00Z"
}
4

Track the decision

Track terminal kycStatus transitions via the CUSTOMER.KYC_APPROVED / CUSTOMER.KYC_REJECTED webhook (recommended) or by polling GET /customers/{customerId}. CUSTOMER.KYC_PENDING also fires when the customer is submitted for review — subscribe to it if you want to surface an “under review” state. On APPROVED, unlock funding and money movement.

Individual customers

In some cases, only the above fields are required at customer creation. Beyond those base requirements, additional fields commonly associated with individual customers include:
  • Full name
  • Date of birth (YYYY-MM-DD format)
  • Physical address (including country, state, city, postalCode)
Note: Check the providerRequiredCustomerFields for each relevant currency in your platform’s configuration to determine which of these fields are strictly mandatory at creation/update time for that customer to transact in those currencies.

Business customers

Beyond the base requirements, additional fields commonly associated with business customers include:
  • Business information:
    • Legal name (this is often required, check providerRequiredCustomerFields)
    • Registration number (optional, unless specified by providerRequiredCustomerFields)
    • Tax ID (optional, unless specified by providerRequiredCustomerFields)
  • Physical address (including country, state, city, postalCode)
Note: Check the providerRequiredCustomerFields for each relevant currency in your platform’s configuration to determine which of these fields are strictly mandatory at creation/update time for that customer to transact in those currencies. When creating or updating customers, the customerType field must be specified as either INDIVIDUAL or BUSINESS.
There can be multiple customers with the same platformCustomerId but different UMA addresses. This is useful if you want to track multiple UMA addresses and/or bank accounts for the same customer in your platform.

Customer Creation Process

Creating a new individual customer (regulated institutions)

To register a new customer directly, use the POST /customers endpoint (regulated institutions):
curl -sS -X POST "https://api.lightspark.com/grid/2025-10-13/customers" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "platformCustomerId": "9f84e0c2a72c4fa",
    "customerType": "INDIVIDUAL",
    "fullName": "Jane Doe",
  }'
The API allows creating a customer with minimal PII. However, to enable transactions for a customer in specific currencies, you must include any PII fields mandated by the providerRequiredCustomerFields for those currencies (found in your platform’s configuration via GET /config). The examples below show a more comprehensive set of data. Not all fields are strictly required by the API for customer creation itself, but become necessary based on currency and provider requirements. Example request body for an individual customer with UMA instant payments enabled (ensure all providerRequiredCustomerFields for intended currencies are included):
Typically bank account information is provided separately via internal and external account management. However, when using UMA for instant payments, since funding and withdrawals are instant, bank account information can be provided at time of customer creation.
{
  "umaAddress": "$john.sender@mycompany.com",
  "platformCustomerId": "9f84e0c2a72c4fa",
  "customerType": "INDIVIDUAL",
  "fullName": "John Sender",
  "birthDate": "1985-06-15",
  "address": {
    "line1": "Paseo de la Reforma 222",
    "line2": "Piso 15",
    "city": "Ciudad de México",
    "state": "Ciudad de México",
    "postalCode": "06600",
    "country": "MX"
  }
}
UMA addresses follow the format $username@domain. For your platform:
  1. The domain part will be your configured UMA domain (set in platform configuration)
  2. The username part can be chosen by you or your customers, following these rules:
    • Must start with a $ symbol. This is to differentiate from email addresses and clearly identify an uma address.
    • The username portion is limited to a-z0-9-_.+
    • Addresses are case-insensitive, but by convention are written only with lowercase letters
    • Like email addresses, the maximum number of characters for the username portion of the address is 64 characters (including the $).
The Grid API validates these requirements and will return an error if they are not met.

Creating a new business customer (regulated institutions)

curl -sS -X POST "https://api.lightspark.com/grid/2025-10-13/customers" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "umaAddress": "$acme.corp@mycompany.com",
    "platformCustomerId": "b87d2e4a9c13f5b",
    "customerType": "BUSINESS",
    "businessInfo": {
      "legalName": "Acme Corporation",
      "registrationNumber": "789012345",
      "taxId": "123-45-6789"
    },
    "address": {
      "line1": "456 Oak Avenue",
      "line2": "Floor 12",
      "city": "New York",
      "state": "NY",
      "postalCode": "10001",
      "country": "US"
    }
  }'

Onboarding customers (unregulated institutions)

Unregulated institutions should initiate a hosted KYC/KYB flow in two steps: create the customer, then generate a hosted KYC link for that customer. While KYC is pending, allow account setup but block funding and money movement.
  1. Create the customer with POST /customers, supplying any information you already have. Only customerType is required.
curl -sS -X POST "https://api.lightspark.com/grid/2025-10-13/customers" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "customerType": "INDIVIDUAL",
    "platformCustomerId": "9f84e0c2a72c4fa",
    "region": "US",
    "currencies": ["USD", "USDC"],
    "email": "jane.doe@example.com",
    "fullName": "Jane Doe"
  }'
  1. Generate a hosted KYC link for the new customer:
curl -sS -X POST "https://api.lightspark.com/grid/2025-10-13/customers/Customer:019542f5-b3e7-1d02-0000-000000000001/kyc-link" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "redirectUri": "https://app.example.com/onboarding/completed"
  }'
Response:
{
  "kycUrl": "https://kyc.grid.example/onboard/abc123",
  "expiresAt": "2027-01-15T14:32:00Z",
  "provider": "SUMSUB",
  "token": "_act-sbx-jwt-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
  1. Redirect the customer to kycUrl, or pass the optional token to the provider’s SDK to embed verification directly in your UI.
  2. After the user is redirected back to your app, they can continue with account setup until KYC review is complete.
  3. Track the decision via the CUSTOMER.KYC_APPROVED / CUSTOMER.KYC_REJECTED webhook (use the CUSTOMER.KYB_* siblings for business customers), or poll GET /customers/{customerId} and inspect kycStatus. On APPROVED, the customer is ready to transact and you can unlock funding.

Handling KYC/KYB Webhooks

After a customer completes the KYC/KYB verification process, you’ll receive webhook notifications about their KYC status. These notifications are sent to your configured webhook endpoint.
For regulated platforms, customers are created with APPROVED KYC status by default.
Webhook Payload (sent to your endpoint):
{
  "id": "Webhook:019542f5-b3e7-1d02-0000-000000000020",
  "type": "CUSTOMER.KYC_APPROVED",
  "timestamp": "2025-07-21T17:32:28Z",
  "data": {
    "id": "Customer:019542f5-b3e7-1d02-0000-000000000001",
    "platformCustomerId": "9f84e0c2a72c4fa",
    "customerType": "INDIVIDUAL",
    "umaAddress": "$john.doe@uma.domain.com",
    "kycStatus": "APPROVED",
    "fullName": "John Michael Doe",
    "birthDate": "1990-01-15",
    "nationality": "US",
    "address": {
      "line1": "123 Main Street",
      "line2": "Apt 4B",
      "city": "San Francisco",
      "state": "CA",
      "postalCode": "94105",
      "country": "US"
    },
    "createdAt": "2025-07-21T17:32:28Z",
    "updatedAt": "2025-07-21T17:32:28Z",
    "isDeleted": false
  }
}
Webhook Headers:
  • Content-Type: application/json
  • X-Grid-Signature: {"v": "1", "s": "base64_signature..."}
id
string
required
Unique identifier for this webhook delivery. Use this for idempotency to prevent processing duplicate webhooks.
type
string
required
Status-specific event type. KYC/KYB webhooks use CUSTOMER.* types:
  • CUSTOMER.KYC_APPROVED: Individual customer verification completed successfully
  • CUSTOMER.KYC_REJECTED: Individual customer verification was rejected
  • CUSTOMER.KYC_PENDING: Individual customer is awaiting review
  • CUSTOMER.KYB_APPROVED / CUSTOMER.KYB_REJECTED / CUSTOMER.KYB_PENDING: business equivalents (only fires for customerType: BUSINESS)
data
object
required
The full customer resource object, same as the corresponding GET /customers/{id} endpoint would return. Includes all customer fields such as id, kycStatus, fullName, birthDate, nationality, address, etc.
CUSTOMER.KYC_PENDING (or CUSTOMER.KYB_PENDING) fires when a customer enters kycStatus: PENDING (for example, after they are submitted for review). Final outcomes use CUSTOMER.KYC_APPROVED / CUSTOMER.KYC_REJECTED (and the CUSTOMER.KYB_* siblings for business customers).
// Example webhook handler for KYC status updates.
// CUSTOMER.KYC_APPROVED and CUSTOMER.KYC_REJECTED are terminal decisions;
// CUSTOMER.KYC_PENDING is an intermediate signal that the customer has been
// submitted for review (useful for surfacing "we're still reviewing" UI).
app.post('/webhooks/kyc-status', async (req, res) => {
  const { type, data } = req.body;

  switch (type) {
    case 'CUSTOMER.KYC_APPROVED':
      // Activate customer account
      await activateCustomer(data.id);
      await sendWelcomeEmail(data.id);
      break;

    case 'CUSTOMER.KYC_REJECTED':
      // Notify support and customer
      await notifySupport(data.id, 'KYC_REJECTED');
      await sendRejectionEmail(data.id);
      break;

    case 'CUSTOMER.KYC_PENDING':
      // Intermediate: customer submitted for review, no decision yet
      await markCustomerUnderReview(data.id);
      break;

    case 'CUSTOMER.KYB_APPROVED':
      // Business customer decision: activate
      await activateCustomer(data.id);
      await sendWelcomeEmail(data.id);
      break;

    case 'CUSTOMER.KYB_REJECTED':
      // Business customer decision: notify
      await notifySupport(data.id, 'KYB_REJECTED');
      await sendRejectionEmail(data.id);
      break;

    case 'CUSTOMER.KYB_PENDING':
      // Intermediate: business customer submitted for review
      await markCustomerUnderReview(data.id);
      break;

    default:
      // Log unexpected types
      console.log(`Unexpected webhook type ${type} for customer ${data.id}`);
  }

  res.status(200).send('OK');
});

Customer management

Retrieving customer information

You can retrieve customer information using either the Grid-assigned customer ID or your platform’s customer ID:
curl -sS -X GET "https://api.lightspark.com/grid/2025-10-13/customers/{customerId}" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
or list customers with a filter:
curl -sS -G "https://api.lightspark.com/grid/2025-10-13/customers" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  --data-urlencode "umaAddress={umaAddress}" \
  --data-urlencode "platformCustomerId={platformCustomerId}" \
  --data-urlencode "customerType={customerType}" \
  --data-urlencode "createdAfter={createdAfter}" \
  --data-urlencode "createdBefore={createdBefore}" \
  --data-urlencode "cursor={cursor}" \
  --data-urlencode "limit={limit}"
Note that this example shows all available filters. You can use any combination of them.

Data validation

The Grid API performs validation on all customer data. Common validation rules include:
  • All required fields must be present based on customer type
  • Date of birth must be in YYYY-MM-DD format and represent a valid date
  • Names must not contain special characters
  • Bank account information must follow country-specific formats
  • Addresses must include all required fields including country code
If validation fails, the API will return a 400 Bad Request response with detailed error information.

Bulk customer import operations

For scenarios where you need to add many customers to the system at once, the API provides a CSV file upload endpoint.

CSV file upload

For large-scale customer imports, you can upload a CSV file containing customer information:
curl -sS -X POST "https://api.lightspark.com/grid/2025-10-13/customers/bulk/csv" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -F "file=@customers.csv"
The CSV file should follow a specific format with required and optional columns based on customer type. Here’s an example:
umaAddress,platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,businessLegalName
$john.doe@uma.domain.com,cust_user123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US,
$acme@uma.domain.com,cust_biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US,Acme Corp
CSV Upload Best Practices
  1. Use a spreadsheet application to prepare your CSV file
  2. Validate data before upload (e.g., date formats, required fields)
  3. Include a header row with column names
  4. Use UTF-8 encoding for special characters
  5. Keep file size under 100MB for optimal processing
You can track the job status through:
  1. Webhook notifications (if configured)
  2. Status endpoint:
curl -sS -X GET "https://api.lightspark.com/grid/2025-10-13/customers/bulk/jobs/{jobId}" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
Example job status response:
{
  "id": "Job:019542f5-b3e7-1d02-0000-000000000006",
  "status": "PROCESSING",
  "progress": {
    "total": 5000,
    "processed": 2500,
    "successful": 2450,
    "failed": 50
  },
  "errors": [
    {
      "correlationId": "cust_biz456",
      "code": "INVALID_FIELD",
      "message": "Invalid bank account number"
    }
  ]
}