Developer documentation for the OpenPay payment processing API
The OpenPay Public API allows merchants to programmatically create payment links, retrieve transaction details, and integrate OpenPay directly into their applications.
All API requests must be authenticated using an API Key. You can generate API keys in the Merchant Dashboard under Settings > Developer API.
Include your API key in the x-api-key header of each request.
x-api-key: sk_live_...
https://api.openpay.bz/v1
Paylinks are secure, shareable URLs that allow customers to make payments.
Creates a new payment link.
Endpoint: POST /paylinks
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
amount |
number | Yes | Payment amount (e.g., 150.00) |
currency |
string | Yes | Currency code (e.g., "BZD", "USD") |
description |
string | No | Description of the payment |
customer_name |
string | No | Name of the customer |
customer_email |
string | No | Email of the customer for receipts |
return_url |
string | No | URL to redirect customer after successful payment |
Example Request:
curl -X POST https://api.openpay.bz/v1/paylinks \
-H "x-api-key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"amount": 150.00,
"currency": "BZD",
"description": "Invoice #1234",
"customer_name": "John Doe",
"customer_email": "john@example.com",
"return_url": "https://myshop.com/success"
}'
Success Response (201 Created):
{
"success": true,
"data": {
"id": "cafe62fc-11e5-4000-a11b-e95f0f5f54e7",
"url": "https://merchant.openpay.bz/payment/cafe62fc-11e5-4000-a11b-e95f0f5f54e7",
"qr_code": "https://api.openpay.bz/qr/cafe62fc-11e5-4000-a11b-e95f0f5f54e7.png",
"amount": 150.00,
"currency": "BZD",
"status": "active",
"created_at": "2026-02-11T09:30:43.473Z"
}
}
The qr_code field is a URL to a PNG image that encodes the paylink URL — useful for printing on receipts or displaying on a checkout screen.
Retrieves details of a specific paylink.
Endpoint: GET /paylinks/:id
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | The ID of the paylink to retrieve |
Example Request:
curl -X GET https://api.openpay.bz/v1/paylinks/cafe62fc-11e5-4000-a11b-e95f0f5f54e7 \
-H "x-api-key: sk_live_..."
Success Response (200 OK):
{
"success": true,
"data": {
"id": "cafe62fc-11e5-4000-a11b-e95f0f5f54e7",
"url": "https://merchant.openpay.bz/payment/cafe62fc-11e5-4000-a11b-e95f0f5f54e7",
"qr_code": "https://api.openpay.bz/qr/cafe62fc-11e5-4000-a11b-e95f0f5f54e7.png",
"amount": 150.00,
"currency": "BZD",
"status": "active",
"description": "Invoice #1234",
"customer_name": "John Doe",
"created_at": "2026-02-11T09:30:43.473Z"
}
}
Process payments directly through the API without creating a paylink first. This is ideal for integrating payment processing into your application (e.g., food ordering apps, e-commerce checkouts).
Processes a direct payment with card details.
Endpoint: POST /payments
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
amount |
number | Yes | Payment amount (e.g., 50.00) |
currency |
string | Yes | Currency code (e.g., "BZD", "USD") |
card_number |
string | Yes | Full card number (16 digits) |
card_expiry_month |
string | Yes | Card expiry month (e.g., "12") |
card_expiry_year |
string | Yes | Card expiry year (e.g., "2025") |
card_cvv |
string | Yes | Card CVV/CVC code (3-4 digits) |
card_holder_name |
string | Yes | Name on the card |
description |
string | No | Description of the payment |
customer_name |
string | No | Name of the customer |
customer_email |
string | No | Email of the customer for receipts |
Example Request:
curl -X POST https://api.openpay.bz/v1/payments \
-H "x-api-key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"amount": 50.00,
"currency": "BZD",
"card_number": "4541250000000001",
"card_expiry_month": "12",
"card_expiry_year": "2025",
"card_cvv": "123",
"card_holder_name": "John Doe",
"description": "Order #5678",
"customer_name": "John Doe",
"customer_email": "john@example.com"
}'
Success Response (200 OK):
{
"success": true,
"data": {
"transaction_id": "25501adf-48e0-41b7-bbbc-c831de450dd0",
"status": "completed",
"message": "Payment processed successfully",
"gateway_reference": "BPREF-609286",
"response_code": "APPROVED"
}
}
Verification Required Response (402 Payment Required):
When a new card is used for the first time, verification is required:
{
"success": false,
"error_code": "VERIFICATION_INITIATED",
"message": "Card verification required. A small random amount has been charged to your card. Please verify the amount to continue.",
"verification_required": true
}
Error Response (400 Bad Request):
{
"success": false,
"message": "Invalid card",
"response_code": "INVALID_CARD"
}
Rate-Limited / Abuse Block (429 Too Many Requests):
OpenPay's abuse-detection layer transparently rejects requests that look like card-enumeration or velocity attacks. The card, IP, or merchant is blocked for a cooldown window; legitimate retries can resume once the window passes. The message describes the specific block (e.g. too many declines on the same card, or too many cards from the same IP).
{
"success": false,
"message": "Too many declined attempts. Please try again later."
}
Test-mode tagging. When the merchant is in test status, every response branch above (200, 402, 400, 429) additionally carries "test_mode": true at the top level. See Test Mode for the simulator behavior.
Retrieve a paginated list of your transactions with optional filters. Useful for reconciliation, reporting, and monitoring payment activity.
Endpoint: GET /transactions
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
page |
number | No | Page number (default: 1) |
limit |
number | No | Results per page, max 100 (default: 20) |
status |
string | No | Filter by status: pending, completed, failed, refunded |
currency |
string | No | Filter by currency: BZD or USD |
start_date |
string | No | Filter from date (ISO 8601, e.g., 2026-04-01T00:00:00Z) |
end_date |
string | No | Filter to date (ISO 8601) |
paylink_id |
string | No | Filter by paylink UUID |
transaction_type |
string | No | Filter by type: payment, verification, refund, chargeback |
Example Request:
curl -X GET "https://api.openpay.bz/v1/transactions?status=completed&start_date=2026-04-01&limit=50" \
-H "x-api-key: sk_live_..."
Success Response (200 OK):
{
"success": true,
"data": [
{
"id": "abc-123-...",
"display_id": "TX1234567890",
"amount": 50.00,
"currency": "BZD",
"status": "completed",
"transaction_type": "payment",
"payment_processor": "plug_n_pay",
"card_type": "Visa",
"card_last_four": "1234",
"reference_number": "TX1234567890",
"created_at": "2026-04-15T14:30:00.000Z",
"updated_at": "2026-04-15T14:30:02.000Z",
"metadata": {
"customer_name": "John Doe",
"authorization_code": "AUTH123",
"gateway_reference": "2026041514300000001"
}
}
],
"pagination": {
"total": 142,
"page": 1,
"limit": 50,
"total_pages": 3
}
}
Retrieve details for a single transaction by ID.
Endpoint: GET /transactions/:id
Example Request:
curl -X GET "https://api.openpay.bz/v1/transactions/abc-123-..." \
-H "x-api-key: sk_live_..."
Success Response (200 OK):
{
"success": true,
"data": {
"id": "abc-123-...",
"amount": 50.00,
"currency": "BZD",
"status": "completed",
"payment_processor": "plug_n_pay",
"card_type": "Visa",
"card_last_four": "1234",
"verification_required": false,
"created_at": "2026-04-15T14:30:00.000Z",
"metadata": {
"customer_name": "John Doe",
"authorization_code": "AUTH123",
"gateway_reference": "2026041514300000001"
}
}
}
verification_required is true when the transaction is a card-verification micro-charge awaiting amount confirmation. It is false (or omitted) for completed payments.
For security purposes, new cards must be verified before they can be used for payments. The verification process involves charging a small random amount to the card, which the customer must confirm.
The verification amount depends on the merchant's PlugNPay processing currency:
The customer can submit either the exact amount charged or the approximate equivalent in the other currency (to account for bank FX conversions on international card statements).
402 status and VERIFICATION_INITIATED error codeManually initiates the card verification process.
Endpoint: POST /cards/verification/initiate
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
card_number |
string | Yes | Full card number (16 digits) |
card_expiry_month |
string | Yes | Card expiry month (e.g., "12") |
card_expiry_year |
string | Yes | Card expiry year (e.g., "2025") |
card_cvv |
string | Yes | Card CVV/CVC code (3-4 digits) |
card_holder_name |
string | Yes | Name on the card |
customer_name |
string | No | Name of the customer |
customer_email |
string | No | Email of the customer |
Example Request:
curl -X POST https://api.openpay.bz/v1/cards/verification/initiate \
-H "x-api-key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"card_number": "4541250000000001",
"card_expiry_month": "12",
"card_expiry_year": "2025",
"card_cvv": "123",
"card_holder_name": "John Doe",
"customer_name": "John Doe",
"customer_email": "john@example.com"
}'
Success Response (200 OK):
{
"success": true,
"message": "Verification initiated. A small random amount has been charged to your card."
}
Completes the card verification by submitting the verification amount.
Endpoint: POST /cards/verification/complete
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
card_number |
string | Yes | Full card number (16 digits) |
card_expiry_month |
string | Yes | Card expiry month (e.g., "12") |
card_expiry_year |
string | Yes | Card expiry year (e.g., "2025") |
amount |
number | Yes | The verification amount from the bank statement (e.g., 3.42 for BZD, 1.71 for USD) |
Example Request:
curl -X POST https://api.openpay.bz/v1/cards/verification/complete \
-H "x-api-key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"card_number": "4541250000000001",
"card_expiry_month": "12",
"card_expiry_year": "2025",
"amount": 3.42
}'
Success Response (200 OK):
{
"success": true,
"message": "Card verified successfully."
}
Error Response - Incorrect Amount (400 Bad Request):
{
"success": false,
"message": "Incorrect amount. Please try again.",
"remaining_attempts": 1
}
Error Response - Card Blocked (400 Bad Request):
After 2 failed verification attempts, the card is blocked:
{
"success": false,
"message": "Too many failed attempts. Card blocked.",
"remaining_attempts": 0
}
The API uses standard HTTP status codes to indicate the success or failure of requests.
| Status Code | Description |
|---|---|
200 |
OK - Request succeeded |
201 |
Created - Resource created successfully |
400 |
Bad Request - Invalid parameters or missing fields |
401 |
Unauthorized - Invalid or missing API Key |
402 |
Payment Required - Card verification required |
403 |
Forbidden - Access denied to the resource |
404 |
Not Found - Resource does not exist |
429 |
Too Many Requests - Abuse-detection block (card/IP/merchant velocity) |
500 |
Internal Server Error - Something went wrong on our end |
Error Response Body:
{
"success": false,
"message": "Description of the error"
}
Merchants with status test can use all API endpoints normally, but no real charges are processed. All gateway calls are simulated and transactions are tagged with test_mode: true in their metadata.
"test_mode": true at the top level when the merchant is in test mode.test to active).Use specific card number suffixes to trigger different gateway responses:
| Card number suffix | Simulated result |
|---|---|
Default (e.g., 4111111111111111) |
Approved |
*1000 (e.g., 4111111111111000) |
Insufficient Funds |
*2000 (e.g., 4111111111112000) |
Declined |
*3000 (e.g., 4111111111113000) |
CVV Mismatch |
*4000 (e.g., 4111111111114000) |
Expired Card |
*5000 (e.g., 4111111111115000) |
Processing Error |
{
"success": true,
"test_mode": true,
"data": {
"transaction_id": "abc-123-...",
"status": "completed",
"message": "Payment processed successfully",
"gateway_reference": "TEST-1713200000000",
"response_code": "APPROVED"
}
}
gateway_reference values prefixed with TEST-. These are not valid PlugNPay or BeaconPay references.test_mode flag is only present when the merchant is in test mode. Production responses do not include this field.test in the admin portal. To go live, the admin clicks "Activate Merchant" which wipes all test data and transitions to active.