Endpoints
| Method | Endpoint | Description | Rate Limit |
|---|
| GET | /v1/bank/list | List supported banks and their codes | 300 req/min |
| POST | /v1/aggregator/user | Create or retrieve an end-user | 120 req/min |
| POST | /v1/aggregator/user/:id/bank-account | Add a bank account for an end-user | 60 req/min |
| GET | /v1/aggregator/user/:id/bank-account/list | List bank accounts for an end-user | 200 req/min |
| GET | /v1/aggregator/quote | Get a locked FX rate | 120 req/min |
| POST | /v1/aggregator/offramp | Create an offramp (USDT → LKR transfer) | 60 req/min |
| GET | /v1/aggregator/offramp/:id | Get offramp status | 300 req/min |
| GET | /v1/aggregator/report/ledger | Float balance ledger reconciliation report | 60 req/min |
| GET | /v1/aggregator/report/offramp | Offramp transaction reconciliation report | 60 req/min |
The Offramp API is only available to merchants with the AGGREGATOR role. Contact [email protected] to enable this for your account.
Flow Overview
Look up bank codes
Call GET /v1/bank/list to get CEFTS bank codes. This is a one-time reference — cache the list and reuse it.
Create or retrieve end-user
Call POST /v1/aggregator/user once per end-user. Safe to call on every offramp — returns the existing record if the user already exists.
Register their bank account
Call POST /v1/aggregator/user/:id/bank-account to add a Sri Lankan bank account. Store the returned userBankId.
Lock an FX rate
Call GET /v1/aggregator/quote with the USDT amount. The returned fxLockId is valid for 60 seconds.
Initiate the transfer
Call POST /v1/aggregator/offramp with the fxLockId, userId, and userBankId.
Poll status or receive webhook
Call GET /v1/aggregator/offramp/:id to check status, or provide a webhookUrl to receive payment.completed / payment.failed events.
Steps 2 and 3 only need to run once per end-user and bank account. Both IDs are reusable across multiple offramps.
Authentication
All endpoints require HMAC authentication:
x-api-key: Your API key ID (e.g. ak_live_xxx)
x-timestamp: Current Unix timestamp in milliseconds
x-signature: HMAC-SHA256 signature
List Supported Banks
Returns all active Sri Lankan banks and their CEFTS codes. Use this to look up the bankCode required when registering a bank account.
Example Request
curl "https://api.ceypay.io/v1/bank/list" \
-H "x-api-key: ak_live_abc123" \
-H "x-timestamp: 1748430000000" \
-H "x-signature: your_signature_here"
Example Response
[
{ "code": 7056, "name": "Commercial Bank PLC" },
{ "code": 7083, "name": "Bank of Ceylon" },
{ "code": 7010, "name": "People's Bank" }
]
Response Fields
| Field | Type | Description |
|---|
code | number | CEFTS bank code — pass as bankCode when registering a bank account |
name | string | Full bank name |
Create or Retrieve User
Upserts an end-user by your internal user ID. Returns the existing record if the user already exists.
Request Body
| Field | Type | Required | Description |
|---|
externalUserId | string | Yes | User ID in your system (max 255 chars) |
CeyPay only stores the externalUserId you provide. No personal or identifying information about your end-users is collected or retained on our end.
Example Request
curl -X POST "https://api.ceypay.io/v1/aggregator/user" \
-H "Content-Type: application/json" \
-H "x-api-key: ak_live_abc123" \
-H "x-timestamp: 1748430000000" \
-H "x-signature: your_signature_here" \
-d '{
"externalUserId": "usr_1234567890"
}'
Example Response
{
"userId": "550e8400-e29b-41d4-a716-446655440000",
"externalUserId": "usr_1234567890",
"createdAt": "2026-05-28T10:00:00.000Z"
}
Response Fields
| Field | Type | Description |
|---|
userId | string (UUID) | CeyPay’s internal user ID — use this in subsequent requests |
externalUserId | string | The ID you provided |
createdAt | string | When the user was first registered (ISO 8601) |
Add Bank Account
Registers a Sri Lankan bank account for an end-user. Returns a userBankId used when creating offramps. A single end-user can have multiple bank accounts registered — each gets its own userBankId.
POST /v1/aggregator/user/:id/bank-account
Path Parameters
| Parameter | Type | Description |
|---|
id | string (UUID) | Aggregator user ID from POST /v1/aggregator/user |
Request Body
| Field | Type | Required | Description |
|---|
bankCode | number | Yes | CEFTS bank code from GET /v1/bank/list |
accountNumber | string | Yes | Bank account number (max 100 chars) |
accountName | string | Yes | Account holder name (max 255 chars) |
beneficiaryMobile | string | No | Beneficiary mobile number (e.g. +94771234567) |
beneficiaryEmail | string | No | Beneficiary email address (max 255 chars) |
Example Request
curl -X POST "https://api.ceypay.io/v1/aggregator/user/550e8400-e29b-41d4-a716-446655440000/bank-account" \
-H "Content-Type: application/json" \
-H "x-api-key: ak_live_abc123" \
-H "x-timestamp: 1748430000000" \
-H "x-signature: your_signature_here" \
-d '{
"bankCode": 7056,
"accountNumber": "1234567890",
"accountName": "John Doe"
}'
Example Response
{
"userBankId": "7f4e2100-a1b2-4c3d-8e9f-556677889900",
"bankCode": 7056,
"bankName": "Commercial Bank PLC",
"accountNumber": "1234567890",
"accountName": "John Doe",
"beneficiaryMobile": "+94771234567",
"beneficiaryEmail": "[email protected]",
"createdAt": "2026-05-28T10:01:00.000Z"
}
Response Fields
| Field | Type | Description |
|---|
userBankId | string (UUID) | Bank account ID — use this in POST /v1/aggregator/offramp |
bankCode | number | CEFTS bank code |
bankName | string | Resolved bank name |
accountNumber | string | Account number |
accountName | string | Account holder name |
beneficiaryMobile | string | null | Beneficiary mobile number |
beneficiaryEmail | string | null | Beneficiary email address |
createdAt | string | ISO 8601 timestamp |
List Bank Accounts
Returns all registered bank accounts for an end-user.
GET /v1/aggregator/user/:id/bank-account/list
Path Parameters
| Parameter | Type | Description |
|---|
id | string (UUID) | Aggregator user ID |
Example Request
curl "https://api.ceypay.io/v1/aggregator/user/550e8400-e29b-41d4-a716-446655440000/bank-account/list" \
-H "x-api-key: ak_live_abc123" \
-H "x-timestamp: 1748430000000" \
-H "x-signature: your_signature_here"
Example Response
[
{
"userBankId": "7f4e2100-a1b2-4c3d-8e9f-556677889900",
"bankCode": 7056,
"bankName": "Commercial Bank PLC",
"accountNumber": "1234567890",
"accountName": "John Doe",
"beneficiaryMobile": "+94771234567",
"beneficiaryEmail": "[email protected]",
"createdAt": "2026-05-28T10:01:00.000Z"
}
]
Get a Quote
Fetches the current USDT/LKR exchange rate and locks it for 60 seconds. Use the returned fxLockId in POST /v1/aggregator/offramp before it expires.
Query Parameters
| Parameter | Type | Required | Description |
|---|
amount_usdt | number | Yes | USDT amount to convert (min: 0.00000001, max: 1,000,000) |
Example Request
curl "https://api.ceypay.io/v1/aggregator/quote?amount_usdt=1000" \
-H "x-api-key: ak_live_abc123" \
-H "x-timestamp: 1748430000000" \
-H "x-signature: your_signature_here"
Example Response
{
"fxLockId": "a3b4c5d6-e7f8-4a9b-b0c1-d2e3f4a5b6c7",
"rateUsdtLkr": 295.50,
"amountUsdt": 1000.00,
"amountLkr": 295500.00,
"expiresAt": "2026-05-28T10:01:00.000Z"
}
Response Fields
| Field | Type | Description |
|---|
fxLockId | string (UUID) | Rate lock ID — pass this to POST /v1/aggregator/offramp |
rateUsdtLkr | number | Locked exchange rate (USDT per LKR) |
amountUsdt | number | USDT amount you requested |
amountLkr | number | LKR equivalent at the locked rate |
expiresAt | string | When this rate lock expires — 60 seconds from issuance (ISO 8601) |
Submit the offramp before expiresAt. Expired or already-used fxLockId values are rejected. Fetch a fresh quote if the lock expires before you submit.
Create Offramp
Initiates a USDT → LKR bank transfer. This endpoint is idempotent on externalRef — submitting the same key twice returns the original offramp unchanged.
POST /v1/aggregator/offramp
Request Body
| Field | Type | Required | Description |
|---|
fxLockId | string (UUID) | Yes | Rate lock ID from GET /v1/aggregator/quote |
userId | string (UUID) | Yes | End-user ID from POST /v1/aggregator/user |
userBankId | string (UUID) | Yes | Bank account ID from POST /v1/aggregator/user/:id/bank-account |
externalRef | string | Yes | Your idempotency key — must be unique per transfer (max 255 chars) |
webhookUrl | string | No | URL to receive payment.completed or payment.failed webhook |
Example Request
curl -X POST "https://api.ceypay.io/v1/aggregator/offramp" \
-H "Content-Type: application/json" \
-H "x-api-key: ak_live_abc123" \
-H "x-timestamp: 1748430000000" \
-H "x-signature: your_signature_here" \
-d '{
"fxLockId": "a3b4c5d6-e7f8-4a9b-b0c1-d2e3f4a5b6c7",
"userId": "550e8400-e29b-41d4-a716-446655440000",
"userBankId": "7f4e2100-a1b2-4c3d-8e9f-556677889900",
"externalRef": "withdrawal-9876543",
"webhookUrl": "https://api.yourplatform.com/ceypay/webhook"
}'
Example Response
{
"paymentId": "c1d2e3f4-a5b6-4c7d-8e9f-001122334455",
"status": "PENDING",
"amountUsdt": 1000.00,
"amountLkr": 295500.00,
"rateUsdtLkr": 295.50,
"externalRef": "withdrawal-9876543",
"bankRef": null,
"completedAt": null,
"failedAt": null,
"createdAt": "2026-05-28T10:00:45.000Z"
}
Response Fields
| Field | Type | Description |
|---|
paymentId | string (UUID) | CeyPay’s offramp ID — use this to poll status |
status | string | Current status (see Offramp Status) |
amountUsdt | number | USDT amount debited |
amountLkr | number | LKR amount credited to the bank account |
rateUsdtLkr | number | Exchange rate applied |
externalRef | string | Your idempotency key |
bankRef | string | null | Bank transaction reference — available once COMPLETED |
completedAt | string | null | ISO 8601 timestamp when transfer completed |
failedAt | string | null | ISO 8601 timestamp when transfer failed |
createdAt | string | ISO 8601 timestamp when offramp was created |
Get Offramp
Retrieve the current status of an offramp.
GET /v1/aggregator/offramp/:id
Path Parameters
| Parameter | Type | Description |
|---|
id | string (UUID) | Offramp paymentId |
Example Request
curl "https://api.ceypay.io/v1/aggregator/offramp/c1d2e3f4-a5b6-4c7d-8e9f-001122334455" \
-H "x-api-key: ak_live_abc123" \
-H "x-timestamp: 1748430000000" \
-H "x-signature: your_signature_here"
Example Response (Completed)
{
"paymentId": "c1d2e3f4-a5b6-4c7d-8e9f-001122334455",
"status": "COMPLETED",
"amountUsdt": 1000.00,
"amountLkr": 295500.00,
"rateUsdtLkr": 295.50,
"externalRef": "withdrawal-9876543",
"bankRef": "BOC-TX-123456",
"completedAt": "2026-05-28T10:05:00.000Z",
"failedAt": null,
"createdAt": "2026-05-28T10:00:45.000Z"
}
Error Responses
| Status | Description |
|---|
| 403 | Offramp does not belong to your merchant account |
| 404 | Offramp not found |
Offramp Status
| Status | Description |
|---|
PENDING | Offramp created, queued for processing |
PROCESSING | Bank transfer in progress |
COMPLETED | Transfer settled — bankRef and completedAt are set |
FAILED | Transfer failed — failedAt is set; see webhook failureReason |
Webhooks
Set webhookUrl in your offramp request to receive a notification when the transfer settles or fails. Webhooks use the same ED25519 signature scheme as other CeyPay webhooks — see Webhook Integration Guide for signature verification details.
payment.completed
Fired when the bank transfer settles successfully.
{
"event": "payment.completed",
"paymentId": "c1d2e3f4-a5b6-4c7d-8e9f-001122334455",
"externalRef": "withdrawal-9876543",
"amountLkr": 295500.00,
"bankRef": "BOC-TX-123456",
"failureReason": null,
"completedAt": "2026-05-28T10:05:00.000Z",
"failedAt": null
}
payment.failed
Fired when the bank transfer cannot be completed.
{
"event": "payment.failed",
"paymentId": "c1d2e3f4-a5b6-4c7d-8e9f-001122334455",
"externalRef": "withdrawal-9876543",
"amountLkr": 295500.00,
"bankRef": null,
"failureReason": "Invalid account number",
"completedAt": null,
"failedAt": "2026-05-28T10:05:00.000Z"
}
X-Webhook-Signature: <base64-encoded ED25519 signature>
X-Webhook-Timestamp: 1748430300000
X-Webhook-Attempt: 1
Content-Type: application/json
User-Agent: CeyPay-Webhook/2.0
Retry Policy
| Attempt | Delay After Previous |
|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 2 minutes |
| 4 | 4 minutes |
| 5 | 8 minutes |
CeyPay retries on connection timeout, non-2xx responses, or network errors. Respond with 200 immediately and process the event asynchronously.
Idempotency
POST /v1/aggregator/offramp is idempotent on externalRef. If you submit the same key twice (e.g. after a network timeout), CeyPay returns the original offramp unchanged — no duplicate transfer is created.
Use a stable, meaningful key such as your internal withdrawal ID:
"externalRef": "withdrawal-9876543"
Error Responses
| Status | Error | Description |
|---|
| 400 | Bad Request | Validation error, expired FX quote, or already-used fxLockId |
| 401 | Unauthorized | Invalid API key or HMAC signature |
| 403 | Forbidden | Offramp belongs to another merchant, or account lacks Aggregator access |
| 404 | Not Found | Offramp not found |
| 429 | Too Many Requests | Rate limit exceeded |
See Error Codes for detailed error handling.
Reconciliation Reports
Two read-only endpoints are available for reconciling your float balance and offramp activity against your own records. Both endpoints are paginated and support date-range filtering.
Float Balance Ledger Report
Returns a paginated log of every LKR movement on your float (top-ups, offramp debits, and refunds) together with period summary statistics.
GET /v1/aggregator/report/ledger
Query Parameters
| Parameter | Type | Required | Description |
|---|
page | number | No | Page number, 1-indexed (default: 1) |
limit | number | No | Items per page, max 100 (default: 50) |
type | string | No | Filter by entry type: CREDIT, DEBIT, or REFUND |
startDate | string | No | Inclusive start date — ISO 8601 format, e.g. 2026-05-01 |
endDate | string | No | Inclusive end date — ISO 8601 format, e.g. 2026-05-31 |
sortOrder | string | No | asc or desc (default: desc) |
Example Request
curl "https://api.ceypay.io/v1/aggregator/report/ledger?startDate=2026-05-01&endDate=2026-05-31&limit=50" \
-H "x-api-key: ak_live_abc123" \
-H "x-timestamp: 1748430000000" \
-H "x-signature: your_signature_here"
Example Response
{
"pagination": {
"currentPage": 1,
"totalPages": 1,
"totalCount": 13,
"limit": 50,
"hasNext": false,
"hasPrev": false
},
"summary": {
"currentBalanceLkr": 500000.00,
"totalCreditsLkr": 750000.00,
"totalDebitsLkr": 295500.00,
"totalRefundsLkr": 0.00,
"netMovementLkr": 454500.00,
"creditCount": 3,
"debitCount": 10,
"refundCount": 0,
"openingBalanceLkr": 45500.00,
"closingBalanceLkr": 500000.00
},
"ledger": [
{
"id": "d4e5f6a7-b8c9-4d0e-1f2a-334455667788",
"type": "DEBIT",
"amountLkr": 295500.00,
"balanceAfter": 500000.00,
"aggregatorOfframpId": "c1d2e3f4-a5b6-4c7d-8e9f-001122334455",
"bankRef": null,
"notes": null,
"createdAt": "2026-05-28T10:00:45.000Z"
},
{
"id": "a1b2c3d4-e5f6-4a7b-8c9d-001122334455",
"type": "CREDIT",
"amountLkr": 1000000.00,
"balanceAfter": 795500.00,
"aggregatorOfframpId": null,
"bankRef": "BOC-TOPUP-789",
"notes": "May float top-up",
"createdAt": "2026-05-02T08:00:00.000Z"
}
]
}
Summary Fields
| Field | Type | Description |
|---|
currentBalanceLkr | number | Live float balance at the time of the request |
totalCreditsLkr | number | Sum of all CREDIT entries in the date window |
totalDebitsLkr | number | Sum of all DEBIT entries in the date window |
totalRefundsLkr | number | Sum of all REFUND entries (failed offramp reversals) in the date window |
netMovementLkr | number | credits − debits + refunds for the period |
creditCount | number | Number of credit entries |
debitCount | number | Number of debit entries |
refundCount | number | Number of refund entries |
openingBalanceLkr | number | null | Balance snapshot immediately before startDate; null if no startDate or no prior entries |
closingBalanceLkr | number | null | Balance snapshot of the last entry on or before endDate; null if no endDate |
Ledger Item Fields
| Field | Type | Description |
|---|
id | string (UUID) | Ledger entry ID |
type | string | CREDIT (admin top-up), DEBIT (offramp charge), or REFUND (failed offramp reversal) |
amountLkr | number | LKR amount of this entry |
balanceAfter | number | Float balance immediately after this entry was recorded |
aggregatorOfframpId | string | null | Linked offramp ID — set on DEBIT and REFUND entries |
bankRef | string | null | Bank transfer reference — set on CREDIT entries |
notes | string | null | Optional notes added at the time of the entry |
createdAt | string | ISO 8601 timestamp |
Offramp Transaction Report
Returns a paginated list of offramps with per-status summary statistics and a weighted average FX rate. Each row includes the partner user ID and bank details so the report is self-contained for export.
GET /v1/aggregator/report/offramp
Query Parameters
| Parameter | Type | Required | Description |
|---|
page | number | No | Page number, 1-indexed (default: 1) |
limit | number | No | Items per page, max 100 (default: 50) |
status | string | No | Filter items by status: PENDING, PROCESSING, COMPLETED, or FAILED |
startDate | string | No | Inclusive start date on created_at — ISO 8601 format |
endDate | string | No | Inclusive end date on created_at — ISO 8601 format |
sortBy | string | No | created_at (default), completed_at, or amount_lkr |
sortOrder | string | No | asc or desc (default: desc) |
The summary block always covers the full date window regardless of the status filter, so all per-status breakdowns remain visible even when the item list is filtered to a single status.
Example Request
curl "https://api.ceypay.io/v1/aggregator/report/offramp?startDate=2026-05-01&endDate=2026-05-31&status=COMPLETED" \
-H "x-api-key: ak_live_abc123" \
-H "x-timestamp: 1748430000000" \
-H "x-signature: your_signature_here"
Example Response
{
"pagination": {
"currentPage": 1,
"totalPages": 1,
"totalCount": 12,
"limit": 50,
"hasNext": false,
"hasPrev": false
},
"summary": {
"totalCount": 15,
"totalAmountLkr": 4425000.00,
"totalAmountUsdt": 15000.00,
"completedCount": 12,
"completedAmountLkr": 3540000.00,
"completedAmountUsdt": 12000.00,
"pendingCount": 1,
"pendingAmountLkr": 295500.00,
"processingCount": 1,
"processingAmountLkr": 295500.00,
"failedCount": 1,
"failedAmountLkr": 294000.00,
"averageRateUsdtLkr": 295.00000000
},
"offramps": [
{
"paymentId": "c1d2e3f4-a5b6-4c7d-8e9f-001122334455",
"externalRef": "withdrawal-9876543",
"status": "COMPLETED",
"amountUsdt": 1000.00,
"amountLkr": 295500.00,
"rateUsdtLkr": 295.50,
"bankRef": "BOC-TX-123456",
"failureReason": null,
"externalUserId": "usr_1234567890",
"accountNumber": "1234567890",
"bankName": "Bank of Ceylon",
"processedAt": "2026-05-28T10:02:00.000Z",
"completedAt": "2026-05-28T10:05:00.000Z",
"failedAt": null,
"createdAt": "2026-05-28T10:00:45.000Z"
}
]
}
Summary Fields
| Field | Type | Description |
|---|
totalCount | number | Total offramps in the date window (all statuses) |
totalAmountLkr | number | Sum of amountLkr across all statuses in the window |
totalAmountUsdt | number | Sum of amountUsdt across all statuses in the window |
completedCount | number | Number of COMPLETED offramps |
completedAmountLkr | number | LKR total for completed offramps |
completedAmountUsdt | number | USDT total for completed offramps |
pendingCount | number | Number of PENDING offramps |
pendingAmountLkr | number | LKR total for pending offramps |
processingCount | number | Number of PROCESSING offramps |
processingAmountLkr | number | LKR total for processing offramps |
failedCount | number | Number of FAILED offramps |
failedAmountLkr | number | LKR amount returned to float via refunds |
averageRateUsdtLkr | number | Weighted average USDT/LKR rate for COMPLETED offramps only |
Offramp Item Fields
| Field | Type | Description |
|---|
paymentId | string (UUID) | CeyPay offramp ID |
externalRef | string | Your idempotency key |
status | string | Current status (see Offramp Status) |
amountUsdt | number | USDT amount |
amountLkr | number | LKR amount |
rateUsdtLkr | number | Exchange rate applied |
bankRef | string | null | Bank transaction reference — set when COMPLETED |
failureReason | string | null | Failure description — set when FAILED |
externalUserId | string | Your internal user ID for the end-user |
accountNumber | string | Destination bank account number |
bankName | string | null | Destination bank name |
processedAt | string | null | ISO 8601 timestamp when processing began |
completedAt | string | null | ISO 8601 timestamp when transfer completed |
failedAt | string | null | ISO 8601 timestamp when transfer failed |
createdAt | string | ISO 8601 timestamp when offramp was created |