API Reference

Complete reference for the CoverPay REST API. All endpoints accept and return JSON. Every response includes a request_id for tracing and support.

Base URL

https://api.coverpayme.com

One host for both environments. Your key picks the environment: a cp_test_ key works in sandbox, a cp_live_ key in production. Sandbox and production data never mix.

Authentication

Authenticate every request with your secret key in the x-api-key header. Keep it on your server; it never goes in a browser or an app. API keys are prefixed by environment:

  • cp_test_... for sandbox
  • cp_live_... for production

All requests must include Content-Type: application/json.

bash
curl https://api.coverpayme.com/v1/bnpl/offers \
  -H "x-api-key: cp_live_a1b2c3d4e5f6..." \
  -H "Content-Type: application/json" \
  -d '{"amount": 15000, "currency": "USD"}'

Link is how a checkout starts and ends, the way Plaid Link works. Your server creates a link_token with your secret key, your page or app opens Link with it, and Link's onSuccess gives you a public_token that your server exchanges for the payment. The amount, order and customer live on the link token, so nothing about the payment comes from the browser. Your webhook stays the source of truth for whether it's paid.

Your server, with your secret key. Returns a short-lived, single-use link token.

FieldTypeDescription
amountinteger, requiredIn cents, from your own catalog or cart. Never from the client.
currencystringDefault USD.
orderIdstringYour order id. Comes back on the exchange and in webhooks.
customerIdstringYour id for the shopper.
customerEmailstringOptional; prefills the provider’s form.
merchantNamestringShown in Link.
providersstring[]Limit routing to these providers (for example ["zip", "affirm"]). Default: every provider you’ve enabled.
mode"choice" | "auto""choice" (default) shows the routed offers; "auto" picks the best one.
purpose"checkout" | "update" | "eligibility" | "terms"Default "checkout". Sets the lifetime: 30 minutes for checkout and update, 4 hours for eligibility and terms.
expiresInSecondsintegerOverrides the lifetime, 60 to 86,400.
allowedDomainsstring[]Pages allowed to open this token.
metadataobjectYours; returned with the payment. In sandbox, coverpay_test sets a test scenario (see Sandbox).
bash
curl https://api.coverpayme.com/v1/link/token/create \
  -H "x-api-key: cp_test_a1b2c3d4..." \
  -H "Content-Type: application/json" \
  -d '{"amount": 12000, "orderId": "order_123", "customerId": "cust_42"}'

# 200
# { "link_token": "lt_test_…", "expiration": "2026-10-09T18:30:00.000Z", "request_id": "…" }

Link (the SDK) calls this to learn how its checkout ended; you don't need to. Holding the link token is what authorizes it. It returns { status, session_id, public_token? }; the public_token appears once the provider has confirmed the payment, and only until it's exchanged.

POST /v1/link/public_token/exchange

Your server, with your secret key: trade the public_token from onSuccess for the payment. Don't trust a payment id that came from the client; trust this.

bash
curl https://api.coverpayme.com/v1/link/public_token/exchange \
  -H "x-api-key: cp_test_a1b2c3d4..." \
  -H "Content-Type: application/json" \
  -d '{"public_token": "public-sandbox-…"}'

# 200
# { "payment_id": "…", "status": "captured", "provider": "zip", "amount": 12000, "currency": "USD",
#   "order_id": "order_123", "customer_id": "cust_42", "completed_at": "…",
#   "already_exchanged": false, "plan": { … }, "request_id": "…" }
Response fieldDescription
payment_idThe payment. Use it to match webhooks and to look the payment up later.
statusThe payment’s status when you asked. The webhook is the source of truth for “paid”.
planOnly when CoverPay’s own pay-in-4 was used: the plan and its schedule.
already_exchangedtrue when you’ve exchanged this public token before (see below).
Safe to retry
A public token lasts 30 minutes. If your exchange timed out or its answer was lost, exchange the same token again: you get the same payment back with already_exchanged: true. No money moves on an exchange, so a repeat is harmless. Another business can never exchange your token.
ErrorHTTPWhen
INVALID_PUBLIC_TOKEN400public_token is missing or isn’t one.
INVALID_PUBLIC_TOKEN404Not found, not yours, or the checkout hasn’t completed.
EXPIRED_PUBLIC_TOKEN410More than 30 minutes old. Use the webhook or GET the payment instead.
INVALID_LINK_TOKEN401Opening Link with a link token that doesn’t exist.
EXPIRED_LINK_TOKEN410Opening Link after the link token expired. Create a new one.

BNPL Offers

Older path
Offers and Checkout Sessions take the amount in the request itself. For a checkout that a shopper can't change, use Link tokens: the amount is fixed on your server.

Query available BNPL offers for a given purchase amount. The smart router ranks providers by availability, APR, and approval likelihood.

POST/v1/bnpl/offers

Returns ranked BNPL offers from all available providers for the given amount.

Request body

FieldTypeRequiredDescription
amountintegerYesPurchase amount in cents (e.g. 15000 = $150.00)
currencystringYesISO 4217 currency code (currently only USD)
customerEmailstringNoPre-fill customer email for faster checkout

Example request

bash
curl -X POST https://api.coverpayme.com/v1/bnpl/offers \
  -H "x-api-key: cp_test_a1b2c3d4..." \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 15000,
    "currency": "USD",
    "customerEmail": "jane@example.com"
  }'

Example response

json
{
  "request_id": "req_8f3a1b2c4d5e",
  "offers": [
    {
      "provider": "klarna",
      "plan_type": "pay_in_4",
      "installments": 4,
      "per_installment": 3750,
      "total": 15000,
      "apr": 0,
      "currency": "USD",
      "min_amount": 3500,
      "max_amount": 150000,
      "score": 92.4
    },
    {
      "provider": "affirm",
      "plan_type": "monthly",
      "installments": 12,
      "per_installment": 1336,
      "total": 16032,
      "apr": 15.0,
      "currency": "USD",
      "min_amount": 5000,
      "max_amount": 1750000,
      "score": 87.1
    },
    {
      "provider": "afterpay",
      "plan_type": "pay_in_4",
      "installments": 4,
      "per_installment": 3750,
      "total": 15000,
      "apr": 0,
      "currency": "USD",
      "min_amount": 100,
      "max_amount": 200000,
      "score": 84.6
    },
    {
      "provider": "coverpay",
      "plan_type": "pay_in_4",
      "installments": 4,
      "per_installment": 3750,
      "total": 15000,
      "apr": 0,
      "currency": "USD",
      "min_amount": 1000,
      "max_amount": 50000,
      "score": 80.2
    }
  ]
}

Checkout Sessions

Create a checkout session to redirect the customer to a specific provider's payment flow.

POST/v1/bnpl/sessions

Creates a checkout session for the selected provider. Returns a redirect URL the customer should be sent to.

Request body

FieldTypeRequiredDescription
providerstringYesProvider slug: klarna, affirm, afterpay, paypal, sezzle, zip, coverpay
orderobjectYesOrder details (required minimal schema shown below; optional enrichment fields supported)
order.orderIdstringYesYour internal order ID
order.totalAmountintegerYesAmount in cents
order.currencystringNoISO 4217 currency code (defaults to USD if omitted)
order.lineItemsarrayNoOptional item-level detail for provider risk and reconciliation context
successUrlstringYesURL to redirect after successful payment
cancelUrlstringYesURL to redirect if customer cancels

Example request

bash
curl -X POST https://api.coverpayme.com/v1/bnpl/sessions \
  -H "x-api-key: cp_test_a1b2c3d4..." \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "klarna",
    "order": {
      "orderId": "order_9f8e7d6c",
      "totalAmount": 15000
    },
    "successUrl": "https://mystore.com/checkout/success",
    "cancelUrl": "https://mystore.com/checkout/cancel"
  }'

Example response

json
{
  "request_id": "req_2c4d6e8f0a1b",
  "sessionId": "sess_kl_7a8b9c0d1e2f",
  "provider": "klarna",
  "redirectUrl": "https://pay.klarna.com/checkout/7a8b9c0d1e2f",
  "expiresAt": "2026-01-30T12:30:00Z"
}
Sessions expire 15 minutes after creation. Once expired, the redirectUrl is invalid and you must create a new session. Expired sessions may emit checkout.abandoned with reason session_expired.
POST/v1/checkout/confirm

Confirms a client-side payment token server-side before order fulfillment.

Request body

FieldTypeRequiredDescription
paymentTokenstringYesOne-time token returned from SDK checkout success
orderIdstringNoMerchant order identifier for reconciliation

Example request

bash
curl -X POST https://api.coverpayme.com/v1/checkout/confirm \
  -H "x-api-key: cp_test_a1b2c3d4..." \
  -H "Content-Type: application/json" \
  -d '{
    "paymentToken": "cp_tok_kl_7a8b9c0d1e2f",
    "orderId": "order_9f8e7d6c"
  }'

Example response

json
{
  "request_id": "req_abc123",
  "confirmed": true,
  "paymentToken": "cp_tok_kl_7a8b9c0d1e2f",
  "orderId": "order_9f8e7d6c",
  "provider": "klarna",
  "plan": {
    "type": "pay_in_4",
    "installments": 4,
    "installmentAmount": 2500,
    "totalAmount": 10000,
    "apr": 0
  },
  "confirmedAt": "2026-01-29T14:35:00Z"
}

Eligibility

Verify that a customer is eligible for BNPL before showing checkout options. The eligibility pipeline checks age, residency, KYC status, sanctions, and terms acceptance.

POST/v1/checkout/verify-eligibility

Runs the full eligibility pipeline and returns pass/fail for each check.

Request body

FieldTypeRequiredDescription
userIdstringYesCoverPay user ID
amountintegerYesRequested amount in cents

Example request

bash
curl -X POST https://api.coverpayme.com/v1/checkout/verify-eligibility \
  -H "x-api-key: cp_test_a1b2c3d4..." \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "usr_9a8b7c6d5e4f",
    "amount": 15000
  }'

Example response

json
{
  "request_id": "req_3d5f7a9b1c3e",
  "eligible": true,
  "failureReasons": [],
  "checks": {
    "age": { "passed": true },
    "residency": { "passed": true },
    "kyc": { "passed": true, "status": "approved" },
    "sanctions": { "passed": true },
    "terms": { "passed": true, "version": "2026-01-15" }
  }
}

Failure reasons

When eligible is false, the failureReasons array contains one or more of: UNDER_18, DATE_OF_BIRTH_MISSING, NON_US_RESIDENT, KYC_NOT_STARTED, KYC_PENDING, KYC_REJECTED, KYC_EXPIRED, SANCTIONS_FLAGGED, TERMS_NOT_ACCEPTED, USER_NOT_FOUND.

POST/v1/checkout/accept-terms

Records the customer's acceptance of CoverPay legal documents. Must be accepted before a checkout session can be created.

Request body

FieldTypeRequiredDescription
userIdstringYesCoverPay user ID
documentsstring[]YesArray of accepted document types: user_agreement, privacy_policy, regulatory_disclosures, third_party_disclosures

Example request

bash
curl -X POST https://api.coverpayme.com/v1/checkout/accept-terms \
  -H "x-api-key: cp_test_a1b2c3d4..." \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "usr_9a8b7c6d5e4f",
    "documents": [
      "user_agreement",
      "privacy_policy",
      "regulatory_disclosures",
      "third_party_disclosures"
    ]
  }'

Example response

json
{
  "request_id": "req_4e6g8i0k2m4o",
  "accepted": true,
  "version": "2026-01-15",
  "acceptedAt": "2026-01-29T14:22:31Z"
}
GET/v1/checkout/terms-status/:userId

Returns the current terms acceptance status for a user.

Path parameters

FieldTypeRequiredDescription
userIdstringYesCoverPay user ID

Example request

bash
curl https://api.coverpayme.com/v1/checkout/terms-status/usr_9a8b7c6d5e4f \
  -H "x-api-key: cp_test_a1b2c3d4..."

Example response

json
{
  "request_id": "req_5f7h9j1l3n5p",
  "accepted": true,
  "version": "2026-01-15",
  "documents": [
    { "type": "user_agreement", "acceptedAt": "2026-01-29T14:22:31Z" },
    { "type": "privacy_policy", "acceptedAt": "2026-01-29T14:22:31Z" },
    { "type": "regulatory_disclosures", "acceptedAt": "2026-01-29T14:22:31Z" },
    { "type": "third_party_disclosures", "acceptedAt": "2026-01-29T14:22:31Z" }
  ]
}

Business

Manage your business account, settings, and API keys.

POST/v1/business/register

Register a new business account. Returns an access token and the created business object.

Access tokens are short-lived session credentials for dashboard flows. For long-lived server integrations, create and use static API keys.

Request body

FieldTypeRequiredDescription
emailstringYesBusiness email address
passwordstringYesMinimum 8 characters
businessNamestringYesLegal business name
websitestringNoBusiness website URL

Example request

bash
curl -X POST https://api.coverpayme.com/v1/business/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "dev@mystore.com",
    "password": "secureP@ss123",
    "businessName": "My Store Inc.",
    "website": "https://mystore.com"
  }'

Example response

json
{
  "request_id": "req_6a7b8c9d0e1f",
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "expiresIn": 86400,
  "business": {
    "id": "biz_3e4f5a6b7c8d",
    "name": "My Store Inc.",
    "email": "dev@mystore.com",
    "website": "https://mystore.com",
    "tier": "starter",
    "createdAt": "2026-01-29T15:00:00Z"
  }
}
POST/v1/business/login

Authenticate and receive an access token.

Access tokens expire after 24 hours. Use API keys for automated server-to-server calls.

Request body

FieldTypeRequiredDescription
emailstringYesBusiness email address
passwordstringYesAccount password

Example response

json
{
  "request_id": "req_7b8c9d0e1f2a",
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "expiresIn": 86400
}
GET/v1/business/me

Returns the currently authenticated user and their associated business.

Example response

json
{
  "request_id": "req_8c9d0e1f2a3b",
  "user": {
    "id": "usr_1a2b3c4d5e6f",
    "email": "dev@mystore.com",
    "role": "owner"
  },
  "business": {
    "id": "biz_3e4f5a6b7c8d",
    "name": "My Store Inc.",
    "website": "https://mystore.com",
    "tier": "growth",
    "providers": ["klarna", "affirm", "afterpay"],
    "createdAt": "2026-01-15T10:00:00Z"
  }
}
PUT/v1/business/settings

Update business settings including enabled providers, branding, and routing preferences.

Request body

FieldTypeRequiredDescription
businessNamestringNoUpdated business name
websitestringNoBusiness website URL
providersstring[]NoEnabled provider slugs
brandColorstringNoHex color for checkout branding

Example response

json
{
  "request_id": "req_9d0e1f2a3b4c",
  "business": {
    "id": "biz_3e4f5a6b7c8d",
    "name": "My Store Inc.",
    "website": "https://mystore.com",
    "providers": ["klarna", "affirm", "afterpay", "coverpay"],
    "brandColor": "#f59e0b",
    "updatedAt": "2026-01-29T16:15:00Z"
  }
}
GET/v1/business/api-keys

List all API keys for the authenticated business. Secret keys are masked after creation.

Example response

json
{
  "request_id": "req_0e1f2a3b4c5d",
  "keys": [
    {
      "id": "key_1a2b3c4d",
      "name": "Production Key",
      "prefix": "cp_live_a1b2...",
      "environment": "production",
      "createdAt": "2026-01-20T10:00:00Z",
      "lastUsedAt": "2026-01-29T14:30:00Z"
    },
    {
      "id": "key_5e6f7a8b",
      "name": "Test Key",
      "prefix": "cp_test_x9y8...",
      "environment": "sandbox",
      "createdAt": "2026-01-15T09:00:00Z",
      "lastUsedAt": "2026-01-29T12:00:00Z"
    }
  ]
}
POST/v1/business/api-keys

Create a new API key. The full secret is returned only once in the response.

Request body

FieldTypeRequiredDescription
namestringYesA label for this key
environmentstringYessandbox or production

Example response

json
{
  "request_id": "req_1f2a3b4c5d6e",
  "key": {
    "id": "key_9c0d1e2f",
    "name": "Mobile App Key",
    "secret": "cp_live_sk_7g8h9i0j1k2l3m4n5o6p7q8r9s0t",
    "environment": "production",
    "createdAt": "2026-01-29T16:30:00Z"
  }
}

Important: The secret field is only returned at creation time. Store it securely -- you will not be able to retrieve it again.

DELETE/v1/business/api-keys/:keyId

Permanently revoke an API key. This action cannot be undone. Any requests using the revoked key will receive a 401 response.

Example response

json
{
  "request_id": "req_2a3b4c5d6e7f",
  "deleted": true,
  "keyId": "key_1a2b3c4d"
}

Webhooks

Manage webhook endpoints to receive real-time event notifications. See the Webhooks Guide for payload schemas, signature verification, and best practices.

GET/v1/business/webhooks

List all webhook endpoints configured for your business.

Example response

json
{
  "request_id": "req_3b4c5d6e7f8a",
  "endpoints": [
    {
      "id": "wh_1a2b3c4d",
      "url": "https://mystore.com/webhooks/coverpay",
      "events": ["checkout.completed", "payment.success", "payment.failed"],
      "enabled": true,
      "signingSecret": "whsec_a1b2...d4e5",
      "createdAt": "2026-01-20T10:00:00Z"
    }
  ]
}
POST/v1/business/webhooks

Create a new webhook endpoint. The URL must use HTTPS.

Request body

FieldTypeRequiredDescription
urlstringYesHTTPS endpoint URL
eventsstring[]YesEvent types to subscribe to

Example response

json
{
  "request_id": "req_4c5d6e7f8a9b",
  "endpoint": {
    "id": "wh_5e6f7a8b",
    "url": "https://mystore.com/webhooks/coverpay",
    "events": ["checkout.completed", "payment.success"],
    "enabled": true,
    "signingSecret": "whsec_9x8w7v6u5t4s3r2q1p0o",
    "createdAt": "2026-01-29T17:00:00Z"
  }
}
PUT/v1/business/webhooks/:id

Update an existing webhook endpoint. You can change the URL, subscribed events, or enabled state.

Request body

FieldTypeRequiredDescription
urlstringNoUpdated HTTPS endpoint URL
eventsstring[]NoUpdated event types
enabledbooleanNoEnable or disable the endpoint

Example response

json
{
  "request_id": "req_5d6e7f8a9b0c",
  "endpoint": {
    "id": "wh_5e6f7a8b",
    "url": "https://mystore.com/webhooks/v2/coverpay",
    "events": ["checkout.completed", "payment.success", "refund.completed"],
    "enabled": true,
    "updatedAt": "2026-01-29T17:30:00Z"
  }
}
DELETE/v1/business/webhooks/:id

Permanently delete a webhook endpoint. Pending deliveries will be cancelled.

Example response

json
{
  "request_id": "req_6e7f8a9b0c1d",
  "deleted": true,
  "endpointId": "wh_5e6f7a8b"
}
POST/v1/business/webhooks/:id/test

Send a test event to the webhook endpoint to verify it is reachable and correctly processing payloads.

Request body

FieldTypeRequiredDescription
eventTypestringNoEvent type to simulate (defaults to checkout.completed)

Example response

json
{
  "request_id": "req_7f8a9b0c1d2e",
  "delivered": true,
  "statusCode": 200,
  "responseTimeMs": 142,
  "eventType": "checkout.completed"
}
GET/v1/business/webhooks/:id/deliveries

List recent delivery attempts for a webhook endpoint, including status codes and response times.

Example response

json
{
  "request_id": "req_8a9b0c1d2e3f",
  "deliveries": [
    {
      "id": "dlv_a1b2c3d4",
      "eventType": "payment.success",
      "statusCode": 200,
      "responseTimeMs": 89,
      "success": true,
      "attempt": 1,
      "deliveredAt": "2026-01-29T16:45:12Z"
    },
    {
      "id": "dlv_e5f6a7b8",
      "eventType": "checkout.completed",
      "statusCode": 500,
      "responseTimeMs": 2340,
      "success": false,
      "attempt": 3,
      "deliveredAt": "2026-01-29T15:30:00Z",
      "nextRetryAt": "2026-01-29T17:30:00Z"
    }
  ]
}
GET/v1/business/webhooks/events

List all available webhook event types you can subscribe to.

Example response

json
{
  "request_id": "req_9b0c1d2e3f4a",
  "events": [
    { "type": "checkout.completed", "description": "Customer completed checkout" },
    { "type": "checkout.abandoned", "description": "Customer abandoned checkout session" },
    { "type": "payment.success", "description": "Payment was successfully processed" },
    { "type": "payment.failed", "description": "Payment processing failed" },
    { "type": "loan.created", "description": "BNPL loan was originated" },
    { "type": "loan.completed", "description": "All installments paid, loan closed" },
    { "type": "eligibility.failed", "description": "Customer failed eligibility check" },
    { "type": "refund.initiated", "description": "Refund was initiated" },
    { "type": "refund.completed", "description": "Refund was successfully processed" }
  ]
}

Errors

CoverPay uses conventional HTTP status codes. All error responses include a machine-readableerror code and a human-readable message.

Error response format

json
{
  "request_id": "req_0c1d2e3f4a5b",
  "error": "invalid_amount",
  "message": "Amount must be a positive integer in cents.",
  "retryAfter": null
}

retryAfter is integer | null. It is populated on 429 rate_limited responses (seconds to wait) and otherwise null.

Error codes

HTTP StatusError CodeDescription
400invalid_requestThe request body is malformed or missing required fields.
400invalid_amountAmount must be a positive integer in cents.
400invalid_providerThe specified provider is not supported or not enabled.
401unauthorizedMissing or invalid API key.
401token_expiredThe access token has expired. Re-authenticate.
403forbiddenThe API key does not have permission for this action.
404not_foundThe requested resource does not exist.
409duplicateA resource with this identifier already exists.
409token_already_confirmedThe payment token has already been confirmed.
422eligibility_failedThe customer did not pass eligibility checks.
400invalid_tokenPayment token is invalid, expired, or already used.
429rate_limitedToo many requests. Check the Retry-After header.
500internal_errorAn unexpected error occurred on our end.
502provider_errorThe upstream BNPL provider returned an error.
503service_unavailableCoverPay is temporarily unavailable. Try again shortly.

Rate Limits

Rate limits are applied per API key and vary by subscription tier. When rate limited, the response includes a Retry-After header with the number of seconds to wait.

TierRequests / minuteRequests / dayBurst
Starter6010,00020
Growth20050,00050
Scale600200,000100
EnterpriseCustomCustomCustom

Rate limit headers are included in every response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (Unix timestamp, UTC, seconds since epoch).

Enterprise tier limits are custom and shown per account in Dashboard settings.