Developers / API reference
The API, endpoint by endpoint.
Every endpoint with its parameters, errors and a request you can paste, the objects it returns, and the webhooks it sends. Generated from the same schemas the API validates with, so what is here is what the API accepts. New to it? Start with the developer guide.
Introduction
The API is JSON over HTTPS at https://api.payments.lk. Send bodies as JSON with Content-Type: application/json; every answer, errors included, is JSON. Test and live keys use the same address: the key decides the mode.
The whole API is also described as an OpenAPI 3.1 document, which the API serves itself, without a key, to any origin. Point Postman, an SDK generator or an AI agent at it. The MCP endpoint for AI agents, at /mcp, is not part of this reference; it has its own agent guide.
Keys and authentication
Send your key as a bearer token: Authorization: Bearer sk_test_.... Keys are in your dashboard under Developers. A missing, malformed, revoked or expired key answers 401; a key that may not do what you asked answers 403.
| Key | What it may do |
|---|---|
| sk_test_, sk_live_ | Secret key. Every endpoint here. Keep it on your server; never ship it in a web page or an app. |
| pk_test_, pk_live_ | Publishable key. Only POST /v1/checkouts; everything else answers 403. The API answers cross-origin requests only for Payments.lk's own sites, so JavaScript on your site cannot call it with this key either. Create checkouts on your server with the secret key. |
| ak_test_, ak_live_ | Agent key, for an AI agent at the MCP endpoint only. Every endpoint here answers 403. |
Test and live
Every object carries a mode, test or live, taken from the key that made it. A test key reads and writes test objects only, and a live key live objects only, so changing the key is the whole switch.
Test checkouts run on Payable’s sandbox with the card networks’ test cards, once you have submitted your application; no money moves. The test cards and the outcome each expiry date gives are on the developers page. Live keys take payments once your account is activated; until then a live checkout, payment link or card charge answers 409.
Idempotency
Every write marked Idempotency-Key required takes an Idempotency-Key header of 8 to 255 letters, digits, dashes, underscores, colons or dots. Make it from your own record, such as order-8891, so a retry after a timeout sends the same key.
A key is remembered for 24 hours, separately for test and live keys. Within that time the same key with the same body gets the first answer back, with the header Idempotent-Replayed: true, and nothing is made twice. The same key with a different body answers 409 idempotency_key_reused, and a retry while the first request is still running answers 409 idempotency_key_in_progress. A request that failed does not use up its key, so you can correct it and send it again. After 24 hours a key counts as new, and the same key with a test key and then a live key makes two objects, so never reuse a key for a different write.
Pagination
Lists answer { object: "list", data, hasMore, nextCursor }, newest first. Payments, refunds and events take limit (1 to 100, 25 when left out) and cursor: pass the nextCursor of one page as the cursor of the next, until hasMore is false. Payment links and saved cards come in one list of your 200 newest, with hasMore always false.
The Node.js and PHP libraries read every page for you: listAll, or all, on payments, refunds and events, in both.
Amounts
Every amount is an integer number of cents of LKR, in fields that end in Cents: Rs. 3,500 is 350000. There is no currency field to send and no other currency; answers carry currency: "LKR". A payment is from Rs. 10 (1000) to Rs. 1,000,000 (100000000). A number with a fraction is refused, never rounded.
Errors
An error answers with its HTTP status and a body in one shape: a stable code, a reason when there is a more precise one, a message you can show a person, a docUrl that explains it, and a traceId to quote to support. A refused field is named in fields. Match on code and reason, never on the sentence. Every code and reason, what it means and how to fix it, is on the errors page; each endpoint below lists the ones it can answer with.
{
"code": "VALIDATION_FAILED",
"message": "Some of the details provided are not valid.",
"docUrl": "https://payments.lk/developers/errors#validation_failed",
"traceId": "0f8e3c1a-5b2d-4c7e-9a61-2d4b8f0e7c35",
"fields": [
{
"path": "amountCents",
"message": "The smallest payment is Rs. 10."
}
]
}| Code and status | What it means |
|---|---|
| VALIDATION_FAILED400 | The request is not valid |
| UNAUTHENTICATED401 | No valid API key |
| FORBIDDEN403 | This key cannot do that |
| NOT_FOUND404 | No such object for this key |
| CONFLICT409 | Not allowed in the current state |
| RATE_LIMITED429 | Too many requests |
| NOT_SUPPORTED501 | Not available yet |
| SERVICE_UNAVAILABLE503 | Temporarily unavailable |
| INTERNAL500 | Something went wrong on our side |
Retry only what time can change: a 429, a 500, a 503 and idempotency_key_in_progress, each with the same Idempotency-Key. Anything else fails the same way until the request changes.
Rate limits
120 requests a minute from one address, counted separately for each endpoint. Every answer carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds until the window resets). Over the limit the answer is 429 RATE_LIMITED with Retry-After in seconds.
Versioning
There is no version header and there are no dated versions: every path starts with /v1. Answers can gain fields, so ignore any field you do not recognise. The OpenAPI document always describes the API as it runs.
Checkouts
A payment and the hosted page that takes it. Most integrations need only this.
Create a checkout
post/v1/checkouts
Creates a payment and the hosted page that takes it. Send amountCents and a description, or lineItems and the total is worked out from them; never both. Send the customer to url. The checkout expires 30 minutes after it is made. The mode comes from the key, never from the body: a sandbox key makes a sandbox checkout on Payable's sandbox. When the customer has paid you receive payment.succeeded; fulfil the order from that webhook, not from the browser's return to successUrl.
- Key
- A secret key or a publishable key (permission checkouts:create)
- Idempotency-Key
- Required
With an amount
curl https://api.payments.lk/v1/checkouts \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-8891" \
-d '{
"amountCents": 350000,
"description": "Two kottu and a milk tea",
"reference": "order-8891",
"customer": {
"name": "Nimali Perera",
"email": "[email protected]",
"phone": "0771234567"
},
"successUrl": "https://yourshop.lk/orders/8891",
"cancelUrl": "https://yourshop.lk/cart"
}'{
"object": "checkout",
"id": "chk_z6zf7gkx4fcwy3402kmwrm3w",
"mode": "test",
"status": "open",
"url": "https://payments.lk/checkout/chk_z6zf7gkx4fcwy3402kmwrm3w",
"expiresAt": "2026-09-18T09:44:05.000Z",
"payment": {
"object": "payment",
"id": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"mode": "test",
"status": "requires_payment_method",
"amountCents": 350000,
"currency": "LKR",
"description": "Two kottu and a milk tea",
"reference": "order-8891",
"customer": {
"name": "Nimali Perera",
"email": "[email protected]",
"phone": "0771234567"
},
"card": null,
"cardSave": "not_requested",
"feeCents": null,
"netCents": null,
"refundedCents": 0,
"failureMessage": null,
"paymentLinkId": null,
"savedCardId": null,
"invoiceId": null,
"checkoutId": "chk_z6zf7gkx4fcwy3402kmwrm3w",
"checkoutPageId": null,
"order": null,
"delivery": null,
"customFields": null,
"customerTaxId": null,
"termsAcceptedAt": null,
"createdAt": "2026-09-18T09:14:05.000Z",
"succeededAt": null
}
}With line items, in Sinhala
curl https://api.payments.lk/v1/checkouts \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-8892" \
-d '{
"lineItems": [
{
"name": "Chicken kottu",
"unitAmountCents": 145000,
"quantity": 2
},
{
"name": "Milk tea",
"description": "Hot, with sugar",
"unitAmountCents": 30000,
"quantity": 2
}
],
"reference": "order-8892",
"locale": "si",
"successUrl": "https://yourshop.lk/orders/8892",
"cancelUrl": "https://yourshop.lk/cart"
}'{
"object": "checkout",
"id": "chk_7h2mq8v4d1x9k3r6t0ya5wnp",
"mode": "test",
"status": "open",
"url": "https://payments.lk/si/checkout/chk_7h2mq8v4d1x9k3r6t0ya5wnp",
"expiresAt": "2026-09-18T09:44:05.000Z",
"payment": {
"object": "payment",
"id": "pay_3c9k2x7m1v8q4r6h0tdy5bnw",
"mode": "test",
"status": "requires_payment_method",
"amountCents": 350000,
"currency": "LKR",
"description": "Colombo Kottu House, 4 items",
"reference": "order-8892",
"customer": null,
"card": null,
"cardSave": "not_requested",
"feeCents": null,
"netCents": null,
"refundedCents": 0,
"failureMessage": null,
"paymentLinkId": null,
"savedCardId": null,
"invoiceId": null,
"checkoutId": "chk_7h2mq8v4d1x9k3r6t0ya5wnp",
"checkoutPageId": null,
"order": {
"lines": [
{
"id": "line-1",
"name": "Chicken kottu",
"quantity": 2,
"unitAmountCents": 145000,
"amountCents": 290000,
"suggested": false
},
{
"id": "line-2",
"name": "Milk tea",
"description": "Hot, with sugar",
"quantity": 2,
"unitAmountCents": 30000,
"amountCents": 60000,
"suggested": false
}
],
"subtotalCents": 350000,
"discount": null,
"shipping": null,
"tax": null,
"totalCents": 350000
},
"delivery": null,
"customFields": null,
"customerTaxId": null,
"termsAcceptedAt": null,
"createdAt": "2026-09-18T09:14:05.000Z",
"succeededAt": null
}
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| amountCentsinteger | The amount in LKR cents, from Rs. 10 (1000) to Rs. 1,000,000 (100000000). Required unless you send lineItems, and refused with them. Take it from your own records, never from the browser.1000 to 100000000 |
| descriptionstring | What the customer is paying for, shown on the checkout. 1 to 120 characters. Required with amountCents; with lineItems a summary such as your trading name and the number of items stands in when you leave it out.1 to 120 characters |
| lineItemsarray of object | What the customer is buying, 1 to 10 lines, shown on the checkout; the total is worked out from them and must come to Rs. 10 to Rs. 1,000,000. Send lineItems or amountCents, never both. |
| lineItems[].namestringrequired | The line's name, 1 to 80 characters.1 to 80 characters |
| lineItems[].descriptionstring | More about the line, up to 200 characters.at most 200 characters |
| lineItems[].unitAmountCentsintegerrequired | The price of one, in LKR cents, from Rs. 1 (100) to Rs. 1,000,000 (100000000).100 to 100000000 |
| lineItems[].quantityintegerrequired | How many, from 1 to 99. The customer cannot change it on the checkout.1 to 99 |
| localeone of: en, si, ta | The language the checkout opens in: en, si or ta. English when left out; url then carries /si or /ta. |
| referencestring | Your own reference, such as an order number. Up to 64 characters; it comes back on the payment and its webhooks.at most 64 characters |
| customerobject | The customer's details, if you have them. Otherwise the checkout page asks for them. |
| customer.namestringrequired | The customer's name, 2 to 80 characters.2 to 80 characters |
| customer.emailstringrequired | The customer's email address.3 to 254 characters |
| customer.phonestring | A Sri Lankan phone number, such as 0771234567 or +94771234567. Spaces and dashes are removed. |
| successUrlstring | Where the customer's browser goes after paying: https, http on localhost, or your app's own scheme such as myshop://paid. It proves nothing; confirm from the webhook.at most 2000 characters |
| cancelUrlstring | Where the customer's browser goes if they give up. The same rules as successUrl.at most 2000 characters |
| saveCardboolean | Offer the customer to keep the card on file for later charges. A live checkout refuses it until saved cards are switched on for your account.default false |
Returns 201 with a Checkout object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots. The line items add up to less or more than one payment may be.json_requiredinvalid_jsonidempotency_key_missingamount |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. A live key before the account is activated, an account that cannot take payments, saveCard on a live checkout before saved cards are switched on, or an amount that would take this month past the plan's monthly limit.idempotency_key_reusedidempotency_key_in_progressmerchant_not_livemerchant_unavailablesaved_cards_offmonthly_limit_reached |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retrieve a checkout
get/v1/checkouts/{id}
A checkout and its payment. Read it to confirm an order when your webhook has not arrived; the browser's return proves nothing.
- Key
- A secret key (permission payments:read)
curl https://api.payments.lk/v1/checkouts/chk_z6zf7gkx4fcwy3402kmwrm3w \
-H "Authorization: Bearer sk_test_..."{
"object": "checkout",
"id": "chk_z6zf7gkx4fcwy3402kmwrm3w",
"mode": "test",
"status": "completed",
"url": "https://payments.lk/checkout/chk_z6zf7gkx4fcwy3402kmwrm3w",
"expiresAt": "2026-09-18T09:44:05.000Z",
"payment": {
"object": "payment",
"id": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"mode": "test",
"status": "succeeded",
"amountCents": 350000,
"currency": "LKR",
"description": "Two kottu and a milk tea",
"reference": "order-8891",
"customer": {
"name": "Nimali Perera",
"email": "[email protected]",
"phone": "0771234567"
},
"card": {
"scheme": "VISA",
"last4": "4242"
},
"cardSave": "not_requested",
"feeCents": 8015,
"netCents": 341985,
"refundedCents": 0,
"failureMessage": null,
"paymentLinkId": null,
"savedCardId": null,
"invoiceId": null,
"checkoutId": "chk_z6zf7gkx4fcwy3402kmwrm3w",
"checkoutPageId": null,
"order": null,
"delivery": null,
"customFields": null,
"customerTaxId": null,
"termsAcceptedAt": null,
"createdAt": "2026-09-18T09:14:05.000Z",
"succeededAt": "2026-09-18T09:16:41.000Z"
}
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The checkout's id, chk_ followed by 24 characters. |
Returns 200 with a Checkout object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No checkout with this id for this key; mode_mismatch when it exists in the other mode.mode_mismatch |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Payments
Every payment, however it was made: a checkout, a payment link, a checkout page or a saved card.
List payments
get/v1/payments
Your payments in this key's mode, newest first. Filter by status and page with limit and cursor.
- Key
- A secret key (permission payments:read)
curl "https://api.payments.lk/v1/payments?limit=1&status=succeeded" \
-H "Authorization: Bearer sk_test_..."{
"object": "list",
"data": [
{
"object": "payment",
"id": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"mode": "test",
"status": "succeeded",
"amountCents": 350000,
"currency": "LKR",
"description": "Two kottu and a milk tea",
"reference": "order-8891",
"customer": {
"name": "Nimali Perera",
"email": "[email protected]",
"phone": "0771234567"
},
"card": {
"scheme": "VISA",
"last4": "4242"
},
"cardSave": "not_requested",
"feeCents": 8015,
"netCents": 341985,
"refundedCents": 0,
"failureMessage": null,
"paymentLinkId": null,
"savedCardId": null,
"invoiceId": null,
"checkoutId": "chk_z6zf7gkx4fcwy3402kmwrm3w",
"checkoutPageId": null,
"order": null,
"delivery": null,
"customFields": null,
"customerTaxId": null,
"termsAcceptedAt": null,
"createdAt": "2026-09-18T09:14:05.000Z",
"succeededAt": "2026-09-18T09:16:41.000Z"
}
],
"hasMore": true,
"nextCursor": "pay_x1n31wnp8nkbjkkhymtqd6zs"
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
| statusone of: requires_payment_method, processing, succeeded, failed, canceled, partially_refunded, refunded, disputed | Only payments in this status. |
Returns 200 with a list of Payment objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retrieve a payment
get/v1/payments/{id}
One payment.
- Key
- A secret key (permission payments:read)
curl https://api.payments.lk/v1/payments/pay_x1n31wnp8nkbjkkhymtqd6zs \
-H "Authorization: Bearer sk_test_..."{
"object": "payment",
"id": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"mode": "test",
"status": "succeeded",
"amountCents": 350000,
"currency": "LKR",
"description": "Two kottu and a milk tea",
"reference": "order-8891",
"customer": {
"name": "Nimali Perera",
"email": "[email protected]",
"phone": "0771234567"
},
"card": {
"scheme": "VISA",
"last4": "4242"
},
"cardSave": "not_requested",
"feeCents": 8015,
"netCents": 341985,
"refundedCents": 0,
"failureMessage": null,
"paymentLinkId": null,
"savedCardId": null,
"invoiceId": null,
"checkoutId": "chk_z6zf7gkx4fcwy3402kmwrm3w",
"checkoutPageId": null,
"order": null,
"delivery": null,
"customFields": null,
"customerTaxId": null,
"termsAcceptedAt": null,
"createdAt": "2026-09-18T09:14:05.000Z",
"succeededAt": "2026-09-18T09:16:41.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The payment's id, pay_ followed by 24 characters. |
Returns 200 with a Payment object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No payment with this id for this key; mode_mismatch when it exists in the other mode.mode_mismatch |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Refunds
Money back to the customer's card, in full or in part. The outcome arrives as a webhook.
Create a refund
post/v1/refunds
Refunds a payment in full or in part. The refund is recorded as pending and sent to Payable, which refunds only a settled payment, so a live refund goes out from the next bank working day. The outcome arrives as refund.succeeded or refund.failed. Refunds on one payment never add up to more than was paid.
- Key
- A secret key (permission refunds:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/refunds \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: refund-order-8891-tea" \
-d '{
"paymentId": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"amountCents": 50000,
"reason": "The milk tea was not available"
}'{
"object": "refund",
"id": "re_sbca5jeftvf7acqsx5rvdzk3",
"mode": "test",
"paymentId": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"status": "pending",
"amountCents": 50000,
"currency": "LKR",
"reason": "The milk tea was not available",
"failureMessage": null,
"createdAt": "2026-09-19T10:02:17.000Z",
"succeededAt": null
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| paymentIdstringrequired | The payment to refund. It must have succeeded. |
| amountCentsinteger | How much to refund, in cents, at least Rs. 1 (100). Leave it out to refund everything still refundable.100 to 9007199254740991 |
| reasonstring | Why, for your records. Up to 200 characters.at most 200 characters |
Returns 201 with a Refund object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots. The amount is more than is left to refund on the payment.json_requiredinvalid_jsonidempotency_key_missingamount |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No payment with this id for this key; mode_mismatch when it exists in the other mode.mode_mismatch |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. The payment has not succeeded, so there is nothing to refund.idempotency_key_reusedidempotency_key_in_progressnot_refundable |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List refunds
get/v1/refunds
Your refunds in this key's mode, newest first. Filter by payment and page with limit and cursor.
- Key
- A secret key (permission payments:read)
curl "https://api.payments.lk/v1/refunds?paymentId=pay_x1n31wnp8nkbjkkhymtqd6zs" \
-H "Authorization: Bearer sk_test_..."{
"object": "list",
"data": [
{
"object": "refund",
"id": "re_sbca5jeftvf7acqsx5rvdzk3",
"mode": "test",
"paymentId": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"status": "succeeded",
"amountCents": 50000,
"currency": "LKR",
"reason": "The milk tea was not available",
"failureMessage": null,
"createdAt": "2026-09-19T10:02:17.000Z",
"succeededAt": "2026-09-19T10:05:52.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
| paymentIdstring | Only refunds of this payment. |
Returns 200 with a list of Refund objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retrieve a refund
get/v1/refunds/{id}
One refund.
- Key
- A secret key (permission payments:read)
curl https://api.payments.lk/v1/refunds/re_sbca5jeftvf7acqsx5rvdzk3 \
-H "Authorization: Bearer sk_test_..."{
"object": "refund",
"id": "re_sbca5jeftvf7acqsx5rvdzk3",
"mode": "test",
"paymentId": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"status": "succeeded",
"amountCents": 50000,
"currency": "LKR",
"reason": "The milk tea was not available",
"failureMessage": null,
"createdAt": "2026-09-19T10:02:17.000Z",
"succeededAt": "2026-09-19T10:05:52.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The refund's id, re_ followed by 24 characters. |
Returns 200 with a Refund object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No refund with this id for this key; mode_mismatch when it exists in the other mode.mode_mismatch |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Payment links
A fixed amount link you can send anywhere, with no website of your own.
Create a payment link
post/v1/payment_links
A link for a fixed amount that you can send anywhere. Each payer who opens it gets a checkout and a payment of their own.
- Key
- A secret key (permission links:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/payment_links \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cake-link-2026-09-18" \
-d '{
"title": "Chocolate cake, 1 kg",
"description": "Collect from the shop on Saturday",
"amountCents": 450000
}'{
"object": "payment_link",
"id": "plink_6fn2qx9v9dk3ppj0n9dky5hz",
"mode": "test",
"title": "Chocolate cake, 1 kg",
"description": "Collect from the shop on Saturday",
"amountCents": 450000,
"url": "https://payments.lk/l/5oR6p1BQk5dFDfFpu7MZ5I",
"active": true,
"paidCount": 0,
"createdAt": "2026-09-18T11:20:00.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| titlestringrequired | What the link is for, 1 to 80 characters. Payers see it.1 to 80 characters |
| descriptionstring | More about it, up to 200 characters. Payers see it.at most 200 characters |
| amountCentsintegerrequired | The fixed amount in LKR cents, from Rs. 10 (1000) to Rs. 1,000,000 (100000000).1000 to 100000000 |
Returns 201 with a Payment link object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. A live key before the account is activated, or an account that cannot take payments.idempotency_key_reusedidempotency_key_in_progressmerchant_not_livemerchant_unavailable |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List payment links
get/v1/payment_links
Your 200 newest payment links in this key's mode, retired ones included, with how many payments each has taken. Not paginated.
- Key
- A secret key (permission payments:read)
curl https://api.payments.lk/v1/payment_links \
-H "Authorization: Bearer sk_test_..."{
"object": "list",
"data": [
{
"object": "payment_link",
"id": "plink_6fn2qx9v9dk3ppj0n9dky5hz",
"mode": "test",
"title": "Chocolate cake, 1 kg",
"description": "Collect from the shop on Saturday",
"amountCents": 450000,
"url": "https://payments.lk/l/5oR6p1BQk5dFDfFpu7MZ5I",
"active": true,
"paidCount": 3,
"createdAt": "2026-09-18T11:20:00.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Returns 200 with a list of Payment link objects.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retire a payment link
delete/v1/payment_links/{id}
Turns a link off: it takes no more payments. Payments already made are unaffected. Retiring a link that is already retired answers the same.
- Key
- A secret key (permission links:write)
- Idempotency-Key
- Not used
curl -X DELETE https://api.payments.lk/v1/payment_links/plink_6fn2qx9v9dk3ppj0n9dky5hz \
-H "Authorization: Bearer sk_test_..."{
"object": "payment_link",
"id": "plink_6fn2qx9v9dk3ppj0n9dky5hz",
"mode": "test",
"title": "Chocolate cake, 1 kg",
"description": "Collect from the shop on Saturday",
"amountCents": 450000,
"url": "https://payments.lk/l/5oR6p1BQk5dFDfFpu7MZ5I",
"active": false,
"paidCount": 0,
"createdAt": "2026-09-18T11:20:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The payment link's id, plink_ followed by 24 characters. |
Returns 200 with a Payment link object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No payment link with this id for this key; mode_mismatch when it exists in the other mode.mode_mismatch |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Saved cards
Cards customers kept on file at a checkout, charged later without them present.
List saved cards
get/v1/cards
The cards your customers kept on file in this key's mode that have not been deleted, newest first. Filter by the customer's email and page with limit and cursor.
- Key
- A secret key (permission cards:read)
curl "https://api.payments.lk/v1/cards?limit=1&customerEmail=ruwan%40example.lk" \
-H "Authorization: Bearer sk_test_..."{
"object": "list",
"data": [
{
"object": "saved_card",
"id": "card_g2a1rex14ff7qh2dc6j6qzmq",
"mode": "test",
"customerEmail": "[email protected]",
"scheme": "VISA",
"last4": "4242",
"expiry": {
"month": 11,
"year": 2029
},
"paymentId": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"consent": {
"agreedAt": "2026-09-02T07:30:41.000Z",
"locale": "en",
"wording": "checkoutPage.saveCard@1"
},
"active": true,
"createdAt": "2026-09-02T07:30:44.000Z",
"retiredAt": null
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
| customerEmailstring | Only the cards kept by this customer, matched on the address they gave at checkout.3 to 254 characters |
Returns 200 with a list of Saved card objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retrieve a saved card
get/v1/cards/{id}
One saved card, deleted ones included, so you can still say why a charge stopped. It carries the card's expiry month and the consent the customer gave when they kept it.
- Key
- A secret key (permission cards:read)
curl https://api.payments.lk/v1/cards/card_g2a1rex14ff7qh2dc6j6qzmq \
-H "Authorization: Bearer sk_test_..."{
"object": "saved_card",
"id": "card_g2a1rex14ff7qh2dc6j6qzmq",
"mode": "test",
"customerEmail": "[email protected]",
"scheme": "VISA",
"last4": "4242",
"expiry": {
"month": 11,
"year": 2029
},
"paymentId": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"consent": {
"agreedAt": "2026-09-02T07:30:41.000Z",
"locale": "en",
"wording": "checkoutPage.saveCard@1"
},
"active": true,
"createdAt": "2026-09-02T07:30:44.000Z",
"retiredAt": null
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The saved card's id, card_ followed by 24 characters. |
Returns 200 with a Saved card object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No saved card with this id for this key; mode_mismatch when it exists in the other mode.mode_mismatch |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Delete a saved card
delete/v1/cards/{id}
Deletes the card here and its token at the processor. Do it when the customer asks or cancels. Deleting a card that is already deleted answers the same card, so a retry is safe.
- Key
- A secret key (permission cards:charge)
- Idempotency-Key
- Not used
curl -X DELETE https://api.payments.lk/v1/cards/card_g2a1rex14ff7qh2dc6j6qzmq \
-H "Authorization: Bearer sk_test_..."{
"object": "saved_card",
"id": "card_g2a1rex14ff7qh2dc6j6qzmq",
"mode": "test",
"customerEmail": "[email protected]",
"scheme": "VISA",
"last4": "4242",
"expiry": {
"month": 11,
"year": 2029
},
"paymentId": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"consent": {
"agreedAt": "2026-09-02T07:30:41.000Z",
"locale": "en",
"wording": "checkoutPage.saveCard@1"
},
"active": false,
"createdAt": "2026-09-02T07:30:44.000Z",
"retiredAt": "2026-10-04T11:09:52.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The saved card's id, card_ followed by 24 characters. |
Returns 200 with a Saved card object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No saved card with this id for this key; mode_mismatch when it exists in the other mode.mode_mismatch |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Charge a saved card
post/v1/cards/{id}/charge
Takes a payment on a saved card without the customer present. The answer is the payment, usually processing: the outcome arrives as payment.succeeded or payment.failed. A charge the processor refuses at once comes back as failed. Charge only what the customer agreed to, with one Idempotency-Key per billing period.
- Key
- A secret key (permission cards:charge)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/cards/card_g2a1rex14ff7qh2dc6j6qzmq/charge \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sub-1042-2026-10" \
-d '{
"amountCents": 250000,
"description": "October coffee subscription",
"reference": "sub-1042-2026-10"
}'{
"object": "payment",
"id": "pay_41j10awqfww0v3p1gjndpbv4",
"mode": "test",
"status": "processing",
"amountCents": 250000,
"currency": "LKR",
"description": "October coffee subscription",
"reference": "sub-1042-2026-10",
"customer": {
"name": null,
"email": "[email protected]",
"phone": null
},
"card": {
"scheme": "VISA",
"last4": "4242"
},
"cardSave": "not_requested",
"feeCents": null,
"netCents": null,
"refundedCents": 0,
"failureMessage": null,
"paymentLinkId": null,
"savedCardId": "card_g2a1rex14ff7qh2dc6j6qzmq",
"invoiceId": null,
"checkoutId": null,
"checkoutPageId": null,
"order": null,
"delivery": null,
"customFields": null,
"customerTaxId": null,
"termsAcceptedAt": null,
"createdAt": "2026-10-01T06:00:03.000Z",
"succeededAt": null
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The saved card's id, card_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| amountCentsintegerrequired | The amount in LKR cents, from Rs. 10 (1000) to Rs. 1,000,000 (100000000). Charge only what the customer agreed to.1000 to 100000000 |
| descriptionstringrequired | What the charge is for, 1 to 120 characters.1 to 120 characters |
| referencestring | Your own reference, up to 64 characters.at most 64 characters |
Returns 201 with a Payment object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No saved card with this id for this key; mode_mismatch when it exists in the other mode.mode_mismatch |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. A live key before the account is activated, a card that cannot be charged again (ask the customer to save it at a new checkout), or an amount that would take this month past the plan's monthly limit.idempotency_key_reusedidempotency_key_in_progressmerchant_not_livecard_not_reusablemonthly_limit_reached |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Events
The events your webhooks receive, readable here too, in the same shape.
List events
get/v1/events
The public events in this key's mode, newest first, in exactly the shape a webhook delivers them. Use it to catch up on anything your endpoint missed. Filter by type and time; page with limit and cursor.
- Key
- A secret key (permission events:read)
curl "https://api.payments.lk/v1/events?limit=1&type=payment.succeeded" \
-H "Authorization: Bearer sk_test_..."{
"object": "list",
"data": [
{
"id": "evt_ev85ebwj9yqjq81vwf5qwz9m",
"object": "event",
"type": "payment.succeeded",
"mode": "test",
"created": "2026-09-18T09:16:41.000Z",
"data": {
"object": "payment",
"id": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"mode": "test",
"status": "succeeded",
"amountCents": 350000,
"currency": "LKR",
"description": "Two kottu and a milk tea",
"reference": "order-8891",
"customer": {
"name": "Nimali Perera",
"email": "[email protected]",
"phone": "0771234567"
},
"card": {
"scheme": "VISA",
"last4": "4242"
},
"cardSave": "not_requested",
"feeCents": 8015,
"netCents": 341985,
"refundedCents": 0,
"failureMessage": null,
"paymentLinkId": null,
"savedCardId": null,
"invoiceId": null,
"checkoutId": "chk_z6zf7gkx4fcwy3402kmwrm3w",
"checkoutPageId": null,
"order": null,
"delivery": null,
"customFields": null,
"customerTaxId": null,
"termsAcceptedAt": null,
"createdAt": "2026-09-18T09:14:05.000Z",
"succeededAt": "2026-09-18T09:16:41.000Z"
}
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
| typeone of: payment.succeeded, payment.failed, checkout.expired, card.saved, card.save_failed, card.expiring, billing.alert.triggered, subscription_schedule.created, subscription_schedule.completed, subscription_schedule.released, subscription_schedule.canceled, refund.succeeded, refund.failed, account.limit.approaching, account.limit.reached, account.limit.reset, customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, customer.subscription.trial_will_end, customer.subscription.paused, customer.subscription.resumed, invoice.created, invoice.finalized, invoice.sent, invoice.overdue, credit_note.created, entitlements.active_entitlement_summary.updated, invoice.paid, invoice.payment_failed, invoice.upcoming, invoice.voided, invoice.marked_uncollectible | Only events of this type. |
| sincestring, ISO 8601 time | Only events created at or after this moment, ISO 8601, such as 2026-09-18T00:00:00Z. |
Returns 200 with a list of Event objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retrieve an event
get/v1/events/{id}
One event, in the shape a webhook delivers it.
- Key
- A secret key (permission events:read)
curl https://api.payments.lk/v1/events/evt_ev85ebwj9yqjq81vwf5qwz9m \
-H "Authorization: Bearer sk_test_..."{
"id": "evt_ev85ebwj9yqjq81vwf5qwz9m",
"object": "event",
"type": "payment.succeeded",
"mode": "test",
"created": "2026-09-18T09:16:41.000Z",
"data": {
"object": "payment",
"id": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"mode": "test",
"status": "succeeded",
"amountCents": 350000,
"currency": "LKR",
"description": "Two kottu and a milk tea",
"reference": "order-8891",
"customer": {
"name": "Nimali Perera",
"email": "[email protected]",
"phone": "0771234567"
},
"card": {
"scheme": "VISA",
"last4": "4242"
},
"cardSave": "not_requested",
"feeCents": 8015,
"netCents": 341985,
"refundedCents": 0,
"failureMessage": null,
"paymentLinkId": null,
"savedCardId": null,
"invoiceId": null,
"checkoutId": "chk_z6zf7gkx4fcwy3402kmwrm3w",
"checkoutPageId": null,
"order": null,
"delivery": null,
"customFields": null,
"customerTaxId": null,
"termsAcceptedAt": null,
"createdAt": "2026-09-18T09:14:05.000Z",
"succeededAt": "2026-09-18T09:16:41.000Z"
}
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The event's id, evt_ followed by 24 characters. |
Returns 200 with an Event object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No public event with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Subscriptions
Customers, recurring prices, subscriptions and their invoices. We renew, charge the card on file, retry and email the customer; you react to the events.
Create a customer
post/v1/customers
Someone you bill. One customer per email in each mode: sending an email you already have answers that customer, with any new details filled in.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/customers \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: member-1042" \
-d '{
"email": "[email protected]",
"name": "Nimali Perera",
"phone": "0771234567",
"locale": "si"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "customer",
"id": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"mode": "test",
"email": "[email protected]",
"name": "Nimali Perera",
"phone": "+94771234567",
"locale": "si",
"defaultCardId": "card_g2a1rex14ff7qh2dc6j6qzmq",
"creditBalanceCents": 0,
"createdAt": "2026-10-01T04:12:30.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| emailstringrequired | Their email address. A customer with this email in this mode is answered, not made again.3 to 254 characters |
| namestring | Their name, 1 to 120 characters.1 to 120 characters |
| phonestring | A Sri Lankan mobile number, such as 0771234567.at most 24 characters |
| localeone of: en, si, ta | The language of their emails and payment pages: en (the default), si or ta. |
Returns 201 with a Customer object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running.idempotency_key_reusedidempotency_key_in_progress |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List customers
get/v1/customers
Your customers in this key's mode, newest first. Filter by email; page with limit and cursor.
- Key
- A secret key (permission billing:read)
curl "https://api.payments.lk/v1/customers?email=nimali%40example.com" \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "customer",
"id": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"mode": "test",
"email": "[email protected]",
"name": "Nimali Perera",
"phone": "+94771234567",
"locale": "si",
"defaultCardId": "card_g2a1rex14ff7qh2dc6j6qzmq",
"creditBalanceCents": 0,
"createdAt": "2026-10-01T04:12:30.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
| emailstring | Only the customer with this email.3 to 254 characters |
Returns 200 with a list of Customer objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retrieve a customer
get/v1/customers/{id}
One customer.
- Key
- A secret key (permission billing:read)
curl https://api.payments.lk/v1/customers/cus_q8w3n5v1x7c2m9r4t6ya0kzp \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "customer",
"id": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"mode": "test",
"email": "[email protected]",
"name": "Nimali Perera",
"phone": "+94771234567",
"locale": "si",
"defaultCardId": "card_g2a1rex14ff7qh2dc6j6qzmq",
"creditBalanceCents": 0,
"createdAt": "2026-10-01T04:12:30.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The customer's id, cus_ followed by 24 characters. |
Returns 200 with a Customer object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No customer with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Create a price
post/v1/prices
A recurring price: an amount every day, week, month or year, on a product you name here or made before. Prices are never edited; make a new one and archive the old in the dashboard.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
A monthly price
curl https://api.payments.lk/v1/prices \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: price-gym-monthly" \
-d '{
"product": {
"name": "Gym membership",
"description": "Unlimited visits, both branches"
},
"unitAmountCents": 450000,
"interval": "month"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "price",
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"mode": "test",
"product": {
"id": "prod_h2k9m4q7v1x8c3r6t0ynwz5p",
"name": "Gym membership",
"description": "Unlimited visits, both branches"
},
"unitAmountCents": 450000,
"currency": "LKR",
"interval": "month",
"intervalCount": 1,
"nickname": null,
"active": true,
"billingScheme": "per_unit",
"tiersMode": null,
"tiers": null,
"packageSize": null,
"usageType": "licensed",
"meterId": null,
"createdAt": "2026-10-01T04:00:00.000Z"
}Usage, tiered: messages sent
curl https://api.payments.lk/v1/prices \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: price-sms-usage" \
-d '{
"product": {
"name": "SMS gateway"
},
"interval": "month",
"billingScheme": "tiered",
"tiersMode": "graduated",
"tiers": [
{
"upTo": 1000,
"unitAmountCents": 150
},
{
"upTo": null,
"unitAmountCents": 100
}
],
"usageType": "metered",
"meterId": "mtr_a4c8v2x9q1m7k3r6t0yhwn5z"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "price",
"id": "price_u6s2v9x1c8q4m7k3r0tnhw5y",
"mode": "test",
"product": {
"id": "prod_h2k9m4q7v1x8c3r6t0ynwz5p",
"name": "SMS gateway",
"description": null
},
"unitAmountCents": 0,
"currency": "LKR",
"interval": "month",
"intervalCount": 1,
"nickname": null,
"active": true,
"billingScheme": "tiered",
"tiersMode": "graduated",
"tiers": [
{
"upTo": 1000,
"unitAmountCents": 150,
"flatAmountCents": 0
},
{
"upTo": null,
"unitAmountCents": 100,
"flatAmountCents": 0
}
],
"packageSize": null,
"usageType": "metered",
"meterId": "mtr_a4c8v2x9q1m7k3r6t0yhwn5z",
"createdAt": "2026-10-01T04:00:00.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| productIdstring | A product of yours already made. Send this or product, not both. |
| productobject | A product made with this price. Send this or productId, not both. |
| product.namestringrequired | The product's name, 1 to 120 characters.1 to 120 characters |
| product.descriptionstring | A line about it, up to 500 characters.at most 500 characters |
| unitAmountCentsinteger | The amount per unit (or per package), in LKR cents, up to Rs. 1,000,000 (100000000). A licensed price per unit is Rs. 10 (1000) at least; a metered or package price may be less. Leave it out for a tiered price.0 to 100000000 |
| intervalone of: day, week, month, yearrequired | How often it bills: day, week, month or year. |
| intervalCountinteger | How many intervals a period is, 1 to 12. Defaults to 1; a yearly price is every year.1 to 12; default 1 |
| nicknamestring | Your own name for it on invoices, up to 80 characters.at most 80 characters |
| billingSchemeone of: per_unit, package, tiered | per_unit (the default), package or tiered.default "per_unit" |
| tiersModeone of: graduated, volume | With tiered: graduated or volume. |
| tiersarray of object | With tiered: 2 to 10 tiers in order, each upTo above the one before, the last upTo null. |
| tiers[].upTointeger or nullrequired | The last unit the tier prices, or null for the last tier.at least 1 |
| tiers[].unitAmountCentsintegerrequired | Per unit in the tier, in cents, from 0.0 to 100000000 |
| tiers[].flatAmountCentsinteger | Added once for the tier, in cents. Defaults to 0.0 to 100000000; default 0 |
| packageSizeinteger | With package: how many units a package is, 2 to 1000000.2 to 1000000 |
| usageTypeone of: licensed, metered | licensed (the default: a quantity, billed in advance) or metered (a meter's usage, billed in arrears).default "licensed" |
| meterIdstring | With metered: the active meter whose usage it bills. |
Returns 201 with a Price object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots. A metered price names no active meter of yours, or the tiers cannot price anything.json_requiredinvalid_jsonidempotency_key_missingmeter_invalidprice_invalid |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No product with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running.idempotency_key_reusedidempotency_key_in_progress |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List prices
get/v1/prices
Your prices in this key's mode, newest first, with their products. Filter by active.
- Key
- A secret key (permission billing:read)
curl "https://api.payments.lk/v1/prices?active=true" \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "price",
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"mode": "test",
"product": {
"id": "prod_h2k9m4q7v1x8c3r6t0ynwz5p",
"name": "Gym membership",
"description": "Unlimited visits, both branches"
},
"unitAmountCents": 450000,
"currency": "LKR",
"interval": "month",
"intervalCount": 1,
"nickname": null,
"active": true,
"billingScheme": "per_unit",
"tiersMode": null,
"tiers": null,
"packageSize": null,
"usageType": "licensed",
"meterId": null,
"createdAt": "2026-10-01T04:00:00.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
| activeone of: true, false | true for prices taking new subscriptions, false for archived ones. |
Returns 200 with a list of Price objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retrieve a price
get/v1/prices/{id}
One price, with its product.
- Key
- A secret key (permission billing:read)
curl https://api.payments.lk/v1/prices/price_m3v8q1x6c9k2r5t7y0wn4hzp \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "price",
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"mode": "test",
"product": {
"id": "prod_h2k9m4q7v1x8c3r6t0ynwz5p",
"name": "Gym membership",
"description": "Unlimited visits, both branches"
},
"unitAmountCents": 450000,
"currency": "LKR",
"interval": "month",
"intervalCount": 1,
"nickname": null,
"active": true,
"billingScheme": "per_unit",
"tiersMode": null,
"tiers": null,
"packageSize": null,
"usageType": "licensed",
"meterId": null,
"createdAt": "2026-10-01T04:00:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The price's id, price_ followed by 24 characters. |
Returns 200 with a Price object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No price with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Create a subscription
post/v1/subscriptions
Starts a subscription on a price for a customer, by id or by details. It is incomplete, with its first invoice open, until that invoice is paid. With a card the customer saved with you (cardId, or the customer's default card) the invoice is charged within a minute. Without one, send the customer to checkoutUrl: they pay on Payable's page and agree to keep the card for the renewals. Unpaid after 23 hours, the subscription expires. From then on we renew each period, charge the card, retry a decline on days 1, 3, 7 and 14, email the customer, and send you the events. Live subscriptions need saved cards switched on.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/subscriptions \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: member-1042-gym" \
-d '{
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"priceId": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"reference": "member-1042",
"successUrl": "https://gym.example/welcome"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "subscription",
"id": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"status": "incomplete",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1,
"items": [
{
"id": "si_t4c9v2x7q1m6k8r3y0wnhz5p",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1
}
],
"currentPeriodStart": "2026-10-01T04:12:31.000Z",
"currentPeriodEnd": "2026-11-01T04:12:31.000Z",
"billingCycleAnchor": "2026-10-01T04:12:31.000Z",
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancelReason": null,
"cancellationDetails": {
"reason": null,
"feedback": null,
"comment": null
},
"trialStart": null,
"trialEnd": null,
"trialEndBehavior": "create_invoice",
"pauseCollection": null,
"discount": null,
"taxRateIds": [],
"subscriptionLinkId": null,
"collectionMethod": "charge_automatically",
"daysUntilDue": null,
"pendingUpdate": null,
"cardId": null,
"cardMissingSince": null,
"latestInvoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"checkoutUrl": "https://payments.lk/checkout/chk_r8v2x6c1q9m4k7t3y0wnzh5p",
"reference": "member-1042",
"createdAt": "2026-10-01T04:12:31.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| customerIdstring | Your customer, by id. Send this or customer, not both. |
| customerCreateCustomerRequest | Your customer's details: found by email, or made. Send this or customerId, not both. |
| priceIdstring | The active price to bill. Send this or items. |
| quantityinteger | How many units each period, 1 to 10000. Defaults to 1.1 to 10000; default 1 |
| itemsarray of object | Up to 10 active prices, each once and all on the same interval, such as a base plan and add-ons. Send this or priceId. |
| items[].priceIdstringrequired | The price. |
| items[].quantityinteger | How many units of it each period, 1 to 10000. Defaults to 1.1 to 10000; default 1 |
| cardIdstring | A card this customer kept with you: the first invoice is charged to it. Without it, the customer's default card, and without that, checkoutUrl. |
| referencestring | Your own reference, up to 120 characters.at most 120 characters |
| successUrlstring | Where the customer lands after the first payment at checkoutUrl: https, or your app's scheme.at most 2000 characters |
| cancelUrlstring | Where the customer lands if they leave checkoutUrl without paying.at most 2000 characters |
| trialDaysinteger | A free trial of this many days, 1 to 730: the subscription starts trialing, with no payment, and its first invoice is for nothing. Send this or trialEnd.1 to 730 |
| trialEndstring, ISO 8601 time | A free trial until this time, ISO 8601, within two years. Send this or trialDays. |
| trialEndBehaviorone of: create_invoice, pause, cancel | With no card on file when the trial ends: create_invoice (the default: the customer is sent the invoice to pay), pause, or cancel.default "create_invoice" |
| cancelAtstring, ISO 8601 time | Ends the subscription at this time, ISO 8601, within two years; the last period is billed only up to it. |
| couponIdstring | A coupon of yours discounting its invoices. |
| taxRateIdsarray of string | Up to 5 tax rates of yours applied to its invoices. |
| addInvoiceItemsarray of object | Up to 10 one-off lines on its first invoice, such as a joining fee. |
| addInvoiceItems[].descriptionstringrequired | The line as the customer reads it.1 to 200 characters |
| addInvoiceItems[].unitAmountCentsintegerrequired | Per unit, in cents; negative for a credit.-100000000 to 100000000 |
| addInvoiceItems[].quantityinteger | How many units, 1 to 10000. Defaults to 1.1 to 10000; default 1 |
| addInvoiceItems[].discountableboolean | Whether a coupon discounts it. Defaults to true.default true |
| collectionMethodone of: charge_automatically, send_invoice | charge_automatically (the default) or send_invoice: it starts active, and each invoice is emailed to the customer to pay by its due date instead of being charged.default "charge_automatically" |
| daysUntilDueinteger | For send_invoice: days from an invoice being sent to its due date, 0 to 365. Defaults to 30.0 to 365 |
| billingCycleAnchorone of: now, first_of_month or string, ISO 8601 time | Where its renewals fall: now (from the moment it starts), first_of_month (midnight on the 1st of each month in Sri Lanka; monthly and yearly prices only), or an ISO 8601 time after now and at most one period ahead. The part period up to it is its first, invoiced now and priced by the day (or the second, as your billing settings say); one worth less than Rs. 10 is added to the first renewal's invoice instead. Not with a trial, whose end anchors the renewals. Leave it out for your billing settings' default. |
Returns 201 with a Subscription object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots. trialEnd or cancelAt is in the past or more than two years away.json_requiredinvalid_jsonidempotency_key_missingbilling_date_invalid |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No customer with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. An archived price, a card that is not this customer's, a coupon or tax rate that cannot be used, live saved cards not switched on, the monthly limit, or an account that cannot take payments.idempotency_key_reusedidempotency_key_in_progressprice_unavailablecard_not_reusablecoupon_invalidtax_rate_invalidsaved_cards_offmonthly_limit_reachedmerchant_not_livemerchant_unavailable |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List subscriptions
get/v1/subscriptions
Your subscriptions in this key's mode, newest first. Filter by customer and status.
- Key
- A secret key (permission billing:read)
curl "https://api.payments.lk/v1/subscriptions?status=active" \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "subscription",
"id": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"status": "active",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1,
"items": [
{
"id": "si_t4c9v2x7q1m6k8r3y0wnhz5p",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1
}
],
"currentPeriodStart": "2026-10-01T04:12:31.000Z",
"currentPeriodEnd": "2026-11-01T04:12:31.000Z",
"billingCycleAnchor": "2026-10-01T04:12:31.000Z",
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancelReason": null,
"cancellationDetails": {
"reason": null,
"feedback": null,
"comment": null
},
"trialStart": null,
"trialEnd": null,
"trialEndBehavior": "create_invoice",
"pauseCollection": null,
"discount": null,
"taxRateIds": [],
"subscriptionLinkId": null,
"collectionMethod": "charge_automatically",
"daysUntilDue": null,
"pendingUpdate": null,
"cardId": "card_g2a1rex14ff7qh2dc6j6qzmq",
"cardMissingSince": null,
"latestInvoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"checkoutUrl": null,
"reference": "member-1042",
"createdAt": "2026-10-01T04:12:31.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
| customerIdstring | Only this customer's subscriptions. |
| statusone of: incomplete, incomplete_expired, trialing, active, past_due, unpaid, paused, canceled | Only subscriptions in this status. |
Returns 200 with a list of Subscription objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retrieve a subscription
get/v1/subscriptions/{id}
One subscription.
- Key
- A secret key (permission billing:read)
curl https://api.payments.lk/v1/subscriptions/sub_p6x1c8v3q9m2k7r4t0yhwz5n \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "subscription",
"id": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"status": "active",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1,
"items": [
{
"id": "si_t4c9v2x7q1m6k8r3y0wnhz5p",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1
}
],
"currentPeriodStart": "2026-10-01T04:12:31.000Z",
"currentPeriodEnd": "2026-11-01T04:12:31.000Z",
"billingCycleAnchor": "2026-10-01T04:12:31.000Z",
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancelReason": null,
"cancellationDetails": {
"reason": null,
"feedback": null,
"comment": null
},
"trialStart": null,
"trialEnd": null,
"trialEndBehavior": "create_invoice",
"pauseCollection": null,
"discount": null,
"taxRateIds": [],
"subscriptionLinkId": null,
"collectionMethod": "charge_automatically",
"daysUntilDue": null,
"pendingUpdate": null,
"cardId": "card_g2a1rex14ff7qh2dc6j6qzmq",
"cardMissingSince": null,
"latestInvoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"checkoutUrl": null,
"reference": "member-1042",
"createdAt": "2026-10-01T04:12:31.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The subscription's id, sub_ followed by 24 characters. |
Returns 200 with a Subscription object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No subscription with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Update a subscription
post/v1/subscriptions/{id}
Changes what a subscription bills (items, or priceId and quantity for one item) and how the part of the period already begun is billed (prorationBehavior), when it ends (cancelAtPeriodEnd, cancelAt), pauses or resumes collection (pauseCollection), ends or moves a trial (trialEnd), and sets the card renewals are charged to, the cancellation details, the coupon, the tax rates and your reference. Only the fields you send change. A change of interval starts a new period at once, invoiced now with the unused time credited. With paymentBehavior pending_if_incomplete a change that costs more waits in pendingUpdate until its invoice is paid. Preview a change with POST /v1/invoices/preview. customer.subscription.updated is sent.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
Move to another plan, invoiced now
curl https://api.payments.lk/v1/subscriptions/sub_p6x1c8v3q9m2k7r4t0yhwz5n \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: upgrade-member-1042-pool" \
-d '{
"priceId": "price_w7k2x9c4q1m8v3r6t0ynhz5p",
"prorationBehavior": "always_invoice",
"prorationDate": "2026-10-16T04:12:31.000Z"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "subscription",
"id": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"status": "active",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"price": {
"id": "price_w7k2x9c4q1m8v3r6t0ynhz5p",
"productName": "Gym and pool",
"unitAmountCents": 600000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1,
"items": [
{
"id": "si_t4c9v2x7q1m6k8r3y0wnhz5p",
"price": {
"id": "price_w7k2x9c4q1m8v3r6t0ynhz5p",
"productName": "Gym and pool",
"unitAmountCents": 600000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1
}
],
"currentPeriodStart": "2026-10-01T04:12:31.000Z",
"currentPeriodEnd": "2026-11-01T04:12:31.000Z",
"billingCycleAnchor": "2026-10-01T04:12:31.000Z",
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancelReason": null,
"cancellationDetails": {
"reason": null,
"feedback": null,
"comment": null
},
"trialStart": null,
"trialEnd": null,
"trialEndBehavior": "create_invoice",
"pauseCollection": null,
"discount": null,
"taxRateIds": [],
"subscriptionLinkId": null,
"collectionMethod": "charge_automatically",
"daysUntilDue": null,
"pendingUpdate": null,
"cardId": "card_g2a1rex14ff7qh2dc6j6qzmq",
"cardMissingSince": null,
"latestInvoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"checkoutUrl": null,
"reference": "member-1042",
"createdAt": "2026-10-01T04:12:31.000Z"
}Pause collection over the holidays
curl https://api.payments.lk/v1/subscriptions/sub_p6x1c8v3q9m2k7r4t0yhwz5n \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: pause-member-1042-holidays" \
-d '{
"pauseCollection": {
"behavior": "keep_as_draft",
"resumesAt": "2027-01-05T00:00:00.000Z"
}
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "subscription",
"id": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"status": "active",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1,
"items": [
{
"id": "si_t4c9v2x7q1m6k8r3y0wnhz5p",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1
}
],
"currentPeriodStart": "2026-10-01T04:12:31.000Z",
"currentPeriodEnd": "2026-11-01T04:12:31.000Z",
"billingCycleAnchor": "2026-10-01T04:12:31.000Z",
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancelReason": null,
"cancellationDetails": {
"reason": null,
"feedback": null,
"comment": null
},
"trialStart": null,
"trialEnd": null,
"trialEndBehavior": "create_invoice",
"pauseCollection": {
"behavior": "keep_as_draft",
"resumesAt": "2027-01-05T00:00:00.000Z"
},
"discount": null,
"taxRateIds": [],
"subscriptionLinkId": null,
"collectionMethod": "charge_automatically",
"daysUntilDue": null,
"pendingUpdate": null,
"cardId": "card_g2a1rex14ff7qh2dc6j6qzmq",
"cardMissingSince": null,
"latestInvoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"checkoutUrl": null,
"reference": "member-1042",
"createdAt": "2026-10-01T04:12:31.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The subscription's id, sub_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| itemsarray of object | Changes to its items, up to 10: an existing one by id (a new priceId, a new quantity, or deleted true), or a new one by priceId. |
| items[].idstring | The item to change, from the subscription's items. |
| items[].priceIdstring | Its new price, or the price of a new item. Active, and on the subscription's interval unless every item changes to a new one together. |
| items[].quantityinteger | Its new quantity, 1 to 10000.1 to 10000 |
| items[].deleted"true" | True removes the item. A subscription keeps at least one. |
| priceIdstring | For a subscription of one item: its new price. Send this and quantity, or items. |
| quantityinteger | For a subscription of one item: its new quantity, 1 to 10000.1 to 10000 |
| prorationBehaviorone of: create_prorations, always_invoice, none | How the part of the period already begun is billed: create_prorations (the default: a credit for the unused time and a charge for the rest of the period wait for the next invoice), always_invoice (the same, invoiced and charged now) or none. A change of interval starts a new period at once, invoiced now. |
| prorationDatestring, ISO 8601 time | Price the change as at this time, ISO 8601: inside the current period and not after now. Send the time a preview used so the charge matches it. |
| paymentBehaviorone of: allow_incomplete, pending_if_incomplete | allow_incomplete (the default: the change applies at once) or pending_if_incomplete (a change that costs more waits for its invoice to be paid; see pendingUpdate). |
| cancelAtPeriodEndboolean | True ends it when the period that is paid for runs out; false takes that back. |
| cancelAtstring, ISO 8601 time or null | Ends it at this time, ISO 8601, within two years; the last period is billed only up to it. Null takes the date back. |
| pauseCollectionobject or null | Pauses collection: invoices are still made but not charged, until resumesAt or until you send null. |
| pauseCollection.behaviorone of: keep_as_draft, mark_uncollectible, voidrequired | keep_as_draft, mark_uncollectible or void: what happens to each invoice made while paused. |
| pauseCollection.resumesAtstring, ISO 8601 time | When collection resumes by itself, ISO 8601, within two years. |
| trialEnd"now" or string, ISO 8601 time | Ends a trialing subscription's trial: "now", or a new time within two years. |
| trialEndBehaviorone of: create_invoice, pause, cancel | create_invoice, pause or cancel: what happens if the trial ends with no card on file. |
| cardIdstring | A card this customer kept with you, charged for the renewals from now on. |
| cancellationDetailsobject | Why it is being cancelled, kept on the subscription. |
| cancellationDetails.feedbackone of: customer_service, low_quality, missing_features, other, switched_service, too_complex, too_expensive, unused | customer_service, low_quality, missing_features, other, switched_service, too_complex, too_expensive or unused. |
| cancellationDetails.commentstring | A comment on why, up to 500 characters.at most 500 characters |
| referencestring or null | Your own reference, up to 120 characters, or null to clear it.at most 120 characters |
| couponIdstring or null | A coupon to discount its invoices from now, or null to take the discount off. |
| taxRateIdsarray of string | The tax rates for its invoices from now; an empty list stops taxing them. |
| collectionMethodone of: charge_automatically, send_invoice | How its invoices are collected from the next one: charge_automatically or send_invoice. |
| daysUntilDueinteger | For send_invoice: days from an invoice being sent to its due date, 0 to 365.0 to 365 |
Returns 200 with a Subscription object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots. A date is out of range, or the items would not go together: an unknown item, a price twice, two intervals, or none left.json_requiredinvalid_jsonidempotency_key_missingbilling_date_invaliditems_invalid |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No subscription with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. The subscription has ended, the change does not apply in its status (or a change already waits for its invoice), the card is not this customer's, a price is archived or in the other mode, or a coupon or tax rate cannot be used.idempotency_key_reusedidempotency_key_in_progresssubscription_endedsubscription_change_refusedcard_not_reusableprice_unavailablecoupon_invalidtax_rate_invalid |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Cancel a subscription
post/v1/subscriptions/{id}/cancel
Ends a subscription now (atPeriodEnd false, the default): its open invoices are voided and retries stop. Or at the end of the period that is paid for (atPeriodEnd true), which you can take back with resume until then. The customer is emailed when it ends.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/subscriptions/sub_p6x1c8v3q9m2k7r4t0yhwz5n/cancel \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cancel-member-1042" \
-d '{
"atPeriodEnd": false
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "subscription",
"id": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"status": "canceled",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1,
"items": [
{
"id": "si_t4c9v2x7q1m6k8r3y0wnhz5p",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1
}
],
"currentPeriodStart": "2026-10-01T04:12:31.000Z",
"currentPeriodEnd": "2026-11-01T04:12:31.000Z",
"billingCycleAnchor": "2026-10-01T04:12:31.000Z",
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": "2026-10-20T08:00:00.000Z",
"endedAt": "2026-10-20T08:00:00.000Z",
"cancelReason": "requested_by_merchant",
"cancellationDetails": {
"reason": "requested_by_merchant",
"feedback": "too_expensive",
"comment": "Moving to the branch closer to home."
},
"trialStart": null,
"trialEnd": null,
"trialEndBehavior": "create_invoice",
"pauseCollection": null,
"discount": null,
"taxRateIds": [],
"subscriptionLinkId": null,
"collectionMethod": "charge_automatically",
"daysUntilDue": null,
"pendingUpdate": null,
"cardId": "card_g2a1rex14ff7qh2dc6j6qzmq",
"cardMissingSince": null,
"latestInvoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"checkoutUrl": null,
"reference": "member-1042",
"createdAt": "2026-10-01T04:12:31.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The subscription's id, sub_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| atPeriodEndboolean | False (the default) ends it now. True lets the period that is paid for run out first.default false |
| feedbackone of: customer_service, low_quality, missing_features, other, switched_service, too_complex, too_expensive, unused | Why, in Stripe's words: customer_service, low_quality, missing_features, other, switched_service, too_complex, too_expensive or unused. |
| commentstring | A comment on why, up to 500 characters.at most 500 characters |
Returns 200 with a Subscription object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No subscription with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. The subscription has already ended.idempotency_key_reusedidempotency_key_in_progresssubscription_ended |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Resume a subscription
post/v1/subscriptions/{id}/resume
Withdraws a cancellation set for the period's end or for a date, while it is still running. A paused subscription (its trial ended with no card) starts again with a new period from now, billed at once to the card now on file, and customer.subscription.resumed is sent. A subscription with neither is answered as it is.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl -X POST https://api.payments.lk/v1/subscriptions/sub_p6x1c8v3q9m2k7r4t0yhwz5n/resume \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: resume-member-1042"The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "subscription",
"id": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"status": "active",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1,
"items": [
{
"id": "si_t4c9v2x7q1m6k8r3y0wnhz5p",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1
}
],
"currentPeriodStart": "2026-10-01T04:12:31.000Z",
"currentPeriodEnd": "2026-11-01T04:12:31.000Z",
"billingCycleAnchor": "2026-10-01T04:12:31.000Z",
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancelReason": null,
"cancellationDetails": {
"reason": null,
"feedback": null,
"comment": null
},
"trialStart": null,
"trialEnd": null,
"trialEndBehavior": "create_invoice",
"pauseCollection": null,
"discount": null,
"taxRateIds": [],
"subscriptionLinkId": null,
"collectionMethod": "charge_automatically",
"daysUntilDue": null,
"pendingUpdate": null,
"cardId": "card_g2a1rex14ff7qh2dc6j6qzmq",
"cardMissingSince": null,
"latestInvoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"checkoutUrl": null,
"reference": "member-1042",
"createdAt": "2026-10-01T04:12:31.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The subscription's id, sub_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Returns 200 with a Subscription object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No subscription with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. The subscription has already ended, or it is paused and there is no card on file.idempotency_key_reusedidempotency_key_in_progresssubscription_endedsubscription_change_refused |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List invoices
get/v1/invoices
Your invoices in this key's mode, newest first. Filter by subscription, customer and status.
- Key
- A secret key (permission billing:read)
curl "https://api.payments.lk/v1/invoices?subscriptionId=sub_p6x1c8v3q9m2k7r4t0yhwz5n" \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "invoice",
"id": "in_x7c2v9q4m1k6r8t3y0hwnzp5",
"mode": "test",
"number": "INV-000002",
"status": "open",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_cycle",
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"lines": [
{
"description": "Gym membership, every month",
"quantity": 1,
"unitAmountCents": 450000,
"amountCents": 450000,
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"proration": false
}
],
"subtotalCents": 450000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 450000,
"creditAppliedCents": 0,
"amountDueCents": 450000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 1,
"nextAttemptAt": "2026-11-02T03:30:00.000Z",
"lastFailure": "The processor refused the charge.",
"paidAt": null,
"hostedInvoiceUrl": "https://payments.lk/invoice/b3Nq8Zt2Xv6Lm1Kc9Rw4Hy7Pd5Gs0Jf2Ae4Uo7Ik9Mx",
"createdAt": "2026-11-01T04:13:00.000Z"
},
{
"object": "invoice",
"id": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"mode": "test",
"number": "INV-000001",
"status": "paid",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_create",
"periodStart": "2026-10-01T04:12:31.000Z",
"periodEnd": "2026-11-01T04:12:31.000Z",
"lines": [
{
"description": "Gym membership, every month",
"quantity": 1,
"unitAmountCents": 450000,
"amountCents": 450000,
"periodStart": "2026-10-01T04:12:31.000Z",
"periodEnd": "2026-11-01T04:12:31.000Z",
"proration": false
}
],
"subtotalCents": 450000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 450000,
"creditAppliedCents": 0,
"amountDueCents": 450000,
"amountPaidCents": 450000,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 0,
"nextAttemptAt": null,
"lastFailure": null,
"paidAt": "2026-10-01T04:15:02.000Z",
"hostedInvoiceUrl": "https://payments.lk/invoice/q7Vd2x9Kc4mZt1Wn8Rb5Hy3Lp6Gs0Jf2Ae4Uo7Ik9Nx",
"createdAt": "2026-10-01T04:12:31.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
| subscriptionIdstring | Only this subscription's invoices. |
| customerIdstring | Only this customer's invoices. |
| statusone of: draft, open, paid, void, uncollectible | Only invoices in this status. |
Returns 200 with a list of Invoice objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Create an invoice
post/v1/invoices
A standalone invoice for a customer, not tied to a subscription: a draft whose lines are the customer's pending invoice items (make them with POST /v1/invoice_items first, or add them to the draft with its invoiceId). Finalise it with POST /v1/invoices/{id}/finalize: send_invoice (the default) numbers it and emails the customer its page, to pay by the due date; charge_automatically charges the card on file.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/invoices \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: term-3-nimali" \
-d '{
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"collectionMethod": "send_invoice",
"daysUntilDue": 30,
"memo": "Third term, grade 10"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "invoice",
"id": "in_s5v1x8c3q9m2k7r4t0yhwn6z",
"mode": "test",
"number": null,
"status": "draft",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": null,
"billingReason": "manual",
"periodStart": null,
"periodEnd": null,
"lines": [
{
"description": "Grade 10 maths, third term",
"quantity": 1,
"unitAmountCents": 1800000,
"amountCents": 1800000,
"periodStart": null,
"periodEnd": null,
"proration": false
}
],
"subtotalCents": 1800000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 1800000,
"creditAppliedCents": 0,
"amountDueCents": 1800000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "send_invoice",
"dueDate": null,
"memo": "Third term, grade 10",
"currency": "LKR",
"attemptCount": 0,
"nextAttemptAt": null,
"lastFailure": null,
"paidAt": null,
"hostedInvoiceUrl": null,
"createdAt": "2026-10-01T08:00:00.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| customerIdstringrequired | The customer, one of yours in this key's mode. Their pending invoice items not for a subscription become its lines. |
| collectionMethodone of: charge_automatically, send_invoice | send_invoice (the default: emailed when finalised, to pay by its due date) or charge_automatically (the card on file is charged when finalised).default "send_invoice" |
| daysUntilDueinteger | For send_invoice: days from sending to the due date, 0 to 365. Defaults to 30.0 to 365 |
| memostring | A note printed on it, up to 500 characters.at most 500 characters |
Returns 201 with an Invoice object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No customer with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running.idempotency_key_reusedidempotency_key_in_progress |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retrieve an invoice
get/v1/invoices/{id}
One invoice, with its lines and where its collection stands.
- Key
- A secret key (permission billing:read)
curl https://api.payments.lk/v1/invoices/in_c4v9x2q7m1k8r3t6y0wnzhp5 \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "invoice",
"id": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"mode": "test",
"number": "INV-000001",
"status": "paid",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_create",
"periodStart": "2026-10-01T04:12:31.000Z",
"periodEnd": "2026-11-01T04:12:31.000Z",
"lines": [
{
"description": "Gym membership, every month",
"quantity": 1,
"unitAmountCents": 450000,
"amountCents": 450000,
"periodStart": "2026-10-01T04:12:31.000Z",
"periodEnd": "2026-11-01T04:12:31.000Z",
"proration": false
}
],
"subtotalCents": 450000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 450000,
"creditAppliedCents": 0,
"amountDueCents": 450000,
"amountPaidCents": 450000,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 0,
"nextAttemptAt": null,
"lastFailure": null,
"paidAt": "2026-10-01T04:15:02.000Z",
"hostedInvoiceUrl": "https://payments.lk/invoice/q7Vd2x9Kc4mZt1Wn8Rb5Hy3Lp6Gs0Jf2Ae4Uo7Ik9Nx",
"createdAt": "2026-10-01T04:12:31.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The invoice's id, in_ followed by 24 characters. |
Returns 200 with an Invoice object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No invoice with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Preview the next invoice
get/v1/invoices/upcoming
The invoice a subscription's next renewal will make, as it stands now: its period and its lines. Nothing is written, and the preview has no id of its own. Not found for a subscription that will not renew.
- Key
- A secret key (permission billing:read)
curl "https://api.payments.lk/v1/invoices/upcoming?subscriptionId=sub_p6x1c8v3q9m2k7r4t0yhwz5n" \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "invoice",
"id": "upcoming_sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"number": null,
"status": "draft",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_cycle",
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"lines": [
{
"description": "Gym membership, every month",
"quantity": 1,
"unitAmountCents": 450000,
"amountCents": 450000,
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"proration": false
}
],
"subtotalCents": 450000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 450000,
"creditAppliedCents": 0,
"amountDueCents": 450000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 0,
"nextAttemptAt": null,
"lastFailure": null,
"paidAt": null,
"hostedInvoiceUrl": null,
"createdAt": "2026-10-29T04:13:00.000Z"
}Query parameters
| Name | Description |
|---|---|
| subscriptionIdstringrequired | The subscription whose next invoice to preview. |
Returns 200 with an Invoice object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No subscription with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Preview a change
post/v1/invoices/preview
The invoice a change to a subscription would make, without making it: with always_invoice (or a change of interval) the invoice made at once, with its proration lines; otherwise the next renewal with the new items and the prorations it would carry. Send the same prorationDate with the update so the amounts match. Nothing is written, and the preview has no id of its own. A read: no Idempotency-Key.
- Key
- A secret key (permission billing:read)
- Idempotency-Key
- Not used
curl https://api.payments.lk/v1/invoices/preview \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"priceId": "price_w7k2x9c4q1m8v3r6t0ynhz5p",
"prorationBehavior": "always_invoice",
"prorationDate": "2026-10-16T04:12:31.000Z"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "invoice",
"id": "upcoming_sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"number": null,
"status": "draft",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_update",
"periodStart": "2026-10-16T04:12:31.000Z",
"periodEnd": "2026-11-01T04:12:31.000Z",
"lines": [
{
"description": "Unused time on Gym membership from 16 October",
"quantity": 1,
"unitAmountCents": -239063,
"amountCents": -239063,
"periodStart": "2026-10-16T04:12:31.000Z",
"periodEnd": "2026-11-01T04:12:31.000Z",
"proration": true
},
{
"description": "Remaining time on Gym and pool from 16 October",
"quantity": 1,
"unitAmountCents": 318750,
"amountCents": 318750,
"periodStart": "2026-10-16T04:12:31.000Z",
"periodEnd": "2026-11-01T04:12:31.000Z",
"proration": true
}
],
"subtotalCents": 79687,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 79687,
"creditAppliedCents": 0,
"amountDueCents": 79687,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 0,
"nextAttemptAt": null,
"lastFailure": null,
"paidAt": null,
"hostedInvoiceUrl": null,
"createdAt": "2026-10-16T04:12:31.000Z"
}Body
| Field | Description |
|---|---|
| subscriptionIdstringrequired | The subscription the change is for. |
| itemsarray of object | The item changes to preview, as the update takes them. |
| items[].idstring | The item to change, from the subscription's items. |
| items[].priceIdstring | Its new price, or the price of a new item. |
| items[].quantityinteger | Its new quantity, 1 to 10000.1 to 10000 |
| items[].deleted"true" | True removes the item. |
| priceIdstring | For a subscription of one item: its new price. |
| quantityinteger | For a subscription of one item: its new quantity, 1 to 10000.1 to 10000 |
| prorationBehaviorone of: create_prorations, always_invoice, none | create_prorations (the default), always_invoice or none, as the update will send it. |
| prorationDatestring, ISO 8601 time | Price the change as at this time, ISO 8601; defaults to now. Send the same time with the update. |
Returns 200 with an Invoice object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The proration date is outside the period, or the items would not go together.json_requiredinvalid_jsonbilling_date_invaliditems_invalid |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No subscription with this id for this key. |
| 409CONFLICT | The subscription has ended, or a price is archived or in the other mode.subscription_endedprice_unavailable |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Send an invoice again
post/v1/invoices/{id}/send
Emails an open invoice collected by sending it to the customer again, with its page to pay on. invoice.sent is sent. Reminders go out by themselves three days before the due date and when it passes.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl -X POST https://api.payments.lk/v1/invoices/in_s5v1x8c3q9m2k7r4t0yhwn6z/send \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: term-3-nimali-resend"The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "invoice",
"id": "in_s5v1x8c3q9m2k7r4t0yhwn6z",
"mode": "test",
"number": "INV-000014",
"status": "open",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": null,
"billingReason": "manual",
"periodStart": null,
"periodEnd": null,
"lines": [
{
"description": "Grade 10 maths, third term",
"quantity": 1,
"unitAmountCents": 1800000,
"amountCents": 1800000,
"periodStart": null,
"periodEnd": null,
"proration": false
}
],
"subtotalCents": 1800000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 1800000,
"creditAppliedCents": 0,
"amountDueCents": 1800000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "send_invoice",
"dueDate": "2026-10-31T18:29:59.000Z",
"memo": "Third term, grade 10",
"currency": "LKR",
"attemptCount": 0,
"nextAttemptAt": null,
"lastFailure": null,
"paidAt": null,
"hostedInvoiceUrl": "https://payments.lk/invoice/r2Vd9x4Kc7mZt3Wn1Rb8Hy6Lp5Gs0Jf4Ae2Uo9Ik7Nx",
"createdAt": "2026-10-01T08:00:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The invoice's id, in_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Returns 200 with an Invoice object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No invoice with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. The invoice is not open, or it is charged to the card on file rather than sent.idempotency_key_reusedidempotency_key_in_progressinvoice_not_open |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Create a credit note
post/v1/credit_notes
Takes an amount off an invoice, as Stripe's credit notes. On an open invoice it lowers what is due (all of it pays the invoice). On a paid one it is refunded to the card the invoice was paid with (refund true), or added to the customer's credit balance for their next invoices. Numbered CN-000001 in your own sequence; credit_note.created is sent.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/credit_notes \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cn-october-closure" \
-d '{
"invoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"amountCents": 150000,
"reason": "order_change",
"memo": "Closed for a week in October"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "credit_note",
"id": "cn_r7v2x9c4q1m8k3t6y0wnhz5p",
"mode": "test",
"invoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"number": "CN-000001",
"amountCents": 150000,
"reason": "order_change",
"memo": "Closed for a week in October",
"amountDueReducedCents": 0,
"refundedCents": 0,
"creditedToBalanceCents": 150000,
"refundId": null,
"createdAt": "2026-10-20T08:00:00.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| invoiceIdstringrequired | The invoice, open or paid. |
| amountCentsintegerrequired | How much, in cents: at most what is due on an open invoice, or what was paid and not yet credited on a paid one.1 to 100000000 |
| reasonone of: duplicate, fraudulent, order_change, product_unsatisfactory | duplicate, fraudulent, order_change or product_unsatisfactory. |
| memostring | A note, up to 500 characters.at most 500 characters |
| refundboolean | On a paid invoice: true refunds the amount to the card it was paid with; false (the default) adds it to the customer's credit balance for their next invoices.default false |
Returns 201 with a CreditNote object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots. More than the invoice still has to give.json_requiredinvalid_jsonidempotency_key_missingamount |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No invoice with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. The invoice is neither open nor paid, or it was not paid by card here and a refund was asked for.idempotency_key_reusedidempotency_key_in_progressinvoice_not_opennot_refundable |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List credit notes
get/v1/credit_notes
Your credit notes in this key's mode, newest first. Filter by invoice.
- Key
- A secret key (permission billing:read)
curl "https://api.payments.lk/v1/credit_notes?invoiceId=in_c4v9x2q7m1k8r3t6y0wnzhp5" \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "credit_note",
"id": "cn_r7v2x9c4q1m8k3t6y0wnhz5p",
"mode": "test",
"invoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"number": "CN-000001",
"amountCents": 150000,
"reason": "order_change",
"memo": "Closed for a week in October",
"amountDueReducedCents": 0,
"refundedCents": 0,
"creditedToBalanceCents": 150000,
"refundId": null,
"createdAt": "2026-10-20T08:00:00.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
| invoiceIdstring | Only this invoice's credit notes. |
Returns 200 with a list of CreditNote objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retrieve a credit note
get/v1/credit_notes/{id}
One credit note.
- Key
- A secret key (permission billing:read)
curl https://api.payments.lk/v1/credit_notes/cn_r7v2x9c4q1m8k3t6y0wnhz5p \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "credit_note",
"id": "cn_r7v2x9c4q1m8k3t6y0wnhz5p",
"mode": "test",
"invoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"number": "CN-000001",
"amountCents": 150000,
"reason": "order_change",
"memo": "Closed for a week in October",
"amountDueReducedCents": 0,
"refundedCents": 0,
"creditedToBalanceCents": 150000,
"refundId": null,
"createdAt": "2026-10-20T08:00:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The credit note's id, cn_ followed by 24 characters. |
Returns 200 with a CreditNote object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No credit note with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Charge an invoice now
post/v1/invoices/{id}/pay
Charges an open invoice to the card on file (the subscription's, or the customer's default) on the worker's next pass, within a minute, whatever the retry schedule says. The outcome arrives as invoice.paid or invoice.payment_failed. A customer without a card pays on the invoice's hostedInvoiceUrl instead.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl -X POST https://api.payments.lk/v1/invoices/in_x7c2v9q4m1k6r8t3y0hwnzp5/pay \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: pay-inv-000002"The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "invoice",
"id": "in_x7c2v9q4m1k6r8t3y0hwnzp5",
"mode": "test",
"number": "INV-000002",
"status": "open",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_cycle",
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"lines": [
{
"description": "Gym membership, every month",
"quantity": 1,
"unitAmountCents": 450000,
"amountCents": 450000,
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"proration": false
}
],
"subtotalCents": 450000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 450000,
"creditAppliedCents": 0,
"amountDueCents": 450000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 1,
"nextAttemptAt": "2026-11-01T09:40:00.000Z",
"lastFailure": "The processor refused the charge.",
"paidAt": null,
"hostedInvoiceUrl": "https://payments.lk/invoice/b3Nq8Zt2Xv6Lm1Kc9Rw4Hy7Pd5Gs0Jf2Ae4Uo7Ik9Mx",
"createdAt": "2026-11-01T04:13:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The invoice's id, in_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Returns 200 with an Invoice object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No invoice with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. The invoice is not open, a charge of it is still with the processor, or there is no card to charge.idempotency_key_reusedidempotency_key_in_progressinvoice_not_openinvoice_payment_pendingno_card_on_file |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Finalise a draft now
post/v1/invoices/{id}/finalize
Ends a renewal's draft window early: the invoice is numbered, gets its hosted page, and is charged to the card on file on the worker's next pass (an unpaid subscription's invoice is left for the customer to pay).
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl -X POST https://api.payments.lk/v1/invoices/in_x7c2v9q4m1k6r8t3y0hwnzp5/finalize \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: finalize-inv-draft"The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "invoice",
"id": "in_x7c2v9q4m1k6r8t3y0hwnzp5",
"mode": "test",
"number": "INV-000002",
"status": "open",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_cycle",
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"lines": [
{
"description": "Gym membership, every month",
"quantity": 1,
"unitAmountCents": 450000,
"amountCents": 450000,
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"proration": false
}
],
"subtotalCents": 450000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 450000,
"creditAppliedCents": 0,
"amountDueCents": 450000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 0,
"nextAttemptAt": "2026-11-01T04:20:00.000Z",
"lastFailure": null,
"paidAt": null,
"hostedInvoiceUrl": "https://payments.lk/invoice/b3Nq8Zt2Xv6Lm1Kc9Rw4Hy7Pd5Gs0Jf2Ae4Uo7Ik9Mx",
"createdAt": "2026-11-01T04:13:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The invoice's id, in_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Returns 200 with an Invoice object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No invoice with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. The invoice is not a draft.idempotency_key_reusedidempotency_key_in_progressinvoice_not_open |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Void an invoice
post/v1/invoices/{id}/void
Cancels a draft or open invoice: nothing is owed on it and nothing more is tried. A past due or unpaid subscription with nothing else open is active again. Voiding a subscription's first invoice before it is paid ends the subscription as incomplete_expired.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl -X POST https://api.payments.lk/v1/invoices/in_x7c2v9q4m1k6r8t3y0hwnzp5/void \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: void-inv-000002"The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "invoice",
"id": "in_x7c2v9q4m1k6r8t3y0hwnzp5",
"mode": "test",
"number": "INV-000002",
"status": "void",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_cycle",
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"lines": [
{
"description": "Gym membership, every month",
"quantity": 1,
"unitAmountCents": 450000,
"amountCents": 450000,
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"proration": false
}
],
"subtotalCents": 450000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 450000,
"creditAppliedCents": 0,
"amountDueCents": 450000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 1,
"nextAttemptAt": null,
"lastFailure": "The processor refused the charge.",
"paidAt": null,
"hostedInvoiceUrl": "https://payments.lk/invoice/b3Nq8Zt2Xv6Lm1Kc9Rw4Hy7Pd5Gs0Jf2Ae4Uo7Ik9Mx",
"createdAt": "2026-11-01T04:13:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The invoice's id, in_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Returns 200 with an Invoice object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No invoice with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. The invoice is paid, void or uncollectible already, or a charge of it is still with the processor.idempotency_key_reusedidempotency_key_in_progressinvoice_not_openinvoice_payment_pending |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Write an invoice off
post/v1/invoices/{id}/mark_uncollectible
Marks an open invoice uncollectible: it was owed, and will never be tried again. A past due or unpaid subscription with nothing else open is active again.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl -X POST https://api.payments.lk/v1/invoices/in_x7c2v9q4m1k6r8t3y0hwnzp5/mark_uncollectible \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: writeoff-inv-000002"The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "invoice",
"id": "in_x7c2v9q4m1k6r8t3y0hwnzp5",
"mode": "test",
"number": "INV-000002",
"status": "uncollectible",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_cycle",
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"lines": [
{
"description": "Gym membership, every month",
"quantity": 1,
"unitAmountCents": 450000,
"amountCents": 450000,
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"proration": false
}
],
"subtotalCents": 450000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 450000,
"creditAppliedCents": 0,
"amountDueCents": 450000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 1,
"nextAttemptAt": null,
"lastFailure": "The processor refused the charge.",
"paidAt": null,
"hostedInvoiceUrl": "https://payments.lk/invoice/b3Nq8Zt2Xv6Lm1Kc9Rw4Hy7Pd5Gs0Jf2Ae4Uo7Ik9Mx",
"createdAt": "2026-11-01T04:13:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The invoice's id, in_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Returns 200 with an Invoice object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No invoice with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. The invoice is not open, or a charge of it is still with the processor.idempotency_key_reusedidempotency_key_in_progressinvoice_not_openinvoice_payment_pending |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Create a coupon
post/v1/coupons
A discount for subscriptions: percent or amount off, for their next invoice (once), some months (repeating) or for as long as they run (forever). Put it on a subscription with couponId.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/coupons \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: coupon-new-year-2027" \
-d '{
"name": "New year, 20% off for 3 months",
"percentOffBps": 2000,
"duration": "repeating",
"durationInMonths": 3,
"maxRedemptions": 100,
"redeemBy": "2027-01-31T18:30:00.000Z"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "coupon",
"id": "cpn_n5v2x8c1q7m3k9r4t0yhwz6p",
"mode": "test",
"name": "New year, 20% off for 3 months",
"percentOffBps": 2000,
"amountOffCents": null,
"duration": "repeating",
"durationInMonths": 3,
"maxRedemptions": 100,
"timesRedeemed": 0,
"redeemBy": "2027-01-31T18:30:00.000Z",
"active": true,
"code": "NEWYEAR20",
"createdAt": "2026-09-25T08:00:00.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| namestringrequired | Its name, 1 to 80 characters.1 to 80 characters |
| percentOffBpsinteger | Basis points off, 1 to 10000 (2000 is 20%). Send this or amountOffCents.1 to 10000 |
| amountOffCentsinteger | Cents off each invoice it discounts, from 100. Send this or percentOffBps.100 to 100000000 |
| durationone of: once, repeating, foreverrequired | once, repeating or forever. |
| durationInMonthsinteger | For repeating: how many months, 1 to 36.1 to 36 |
| maxRedemptionsinteger | How many subscriptions may take it.1 to 1000000 |
| redeemBystring, ISO 8601 time | The last time a subscription may take it, ISO 8601. |
| codestring | A code customers type on a subscription link, or in the customer portal where you allow it, to take this coupon: 3 to 24 letters, digits, dashes or underscores, kept in upper case, unique in the mode. |
Returns 201 with a Coupon object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots. redeemBy has passed.json_requiredinvalid_jsonidempotency_key_missingbilling_date_invalid |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running.idempotency_key_reusedidempotency_key_in_progress |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List coupons
get/v1/coupons
Your coupons in this key's mode, newest first.
- Key
- A secret key (permission billing:read)
curl "https://api.payments.lk/v1/coupons?active=true" \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "coupon",
"id": "cpn_n5v2x8c1q7m3k9r4t0yhwz6p",
"mode": "test",
"name": "New year, 20% off for 3 months",
"percentOffBps": 2000,
"amountOffCents": null,
"duration": "repeating",
"durationInMonths": 3,
"maxRedemptions": 100,
"timesRedeemed": 1,
"redeemBy": "2027-01-31T18:30:00.000Z",
"active": true,
"code": "NEWYEAR20",
"createdAt": "2026-09-25T08:00:00.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
| activeone of: true, false | true for coupons taking new subscriptions, false for archived ones. |
Returns 200 with a list of Coupon objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retrieve a coupon
get/v1/coupons/{id}
One coupon, with how many subscriptions have taken it.
- Key
- A secret key (permission billing:read)
curl https://api.payments.lk/v1/coupons/cpn_n5v2x8c1q7m3k9r4t0yhwz6p \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "coupon",
"id": "cpn_n5v2x8c1q7m3k9r4t0yhwz6p",
"mode": "test",
"name": "New year, 20% off for 3 months",
"percentOffBps": 2000,
"amountOffCents": null,
"duration": "repeating",
"durationInMonths": 3,
"maxRedemptions": 100,
"timesRedeemed": 1,
"redeemBy": "2027-01-31T18:30:00.000Z",
"active": true,
"code": "NEWYEAR20",
"createdAt": "2026-09-25T08:00:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The coupon's id, cpn_ followed by 24 characters. |
Returns 200 with a Coupon object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No coupon with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Archive a coupon
delete/v1/coupons/{id}
Stops new subscriptions taking it. Subscriptions that have it keep their discount, as Stripe's do.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Not used
curl -X DELETE https://api.payments.lk/v1/coupons/cpn_n5v2x8c1q7m3k9r4t0yhwz6p \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "coupon",
"id": "cpn_n5v2x8c1q7m3k9r4t0yhwz6p",
"mode": "test",
"name": "New year, 20% off for 3 months",
"percentOffBps": 2000,
"amountOffCents": null,
"duration": "repeating",
"durationInMonths": 3,
"maxRedemptions": 100,
"timesRedeemed": 1,
"redeemBy": "2027-01-31T18:30:00.000Z",
"active": false,
"code": "NEWYEAR20",
"createdAt": "2026-09-25T08:00:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The coupon's id, cpn_ followed by 24 characters. |
Returns 200 with a Coupon object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No coupon with this id for this key; mode_mismatch when it exists in the other mode.mode_mismatch |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Create a tax rate
post/v1/tax_rates
A tax you charge on invoices, such as VAT 18% or SSCL 2.5%, added on top or included in your prices. Put it on a subscription with taxRateIds.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/tax_rates \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: tax-vat-18" \
-d '{
"displayName": "VAT",
"description": "Value added tax",
"percentageBps": 1800,
"inclusive": false
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "tax_rate",
"id": "txr_v1x8c3q6m2k9r5t7y0wnhz4p",
"mode": "test",
"displayName": "VAT",
"description": "Value added tax",
"percentageBps": 1800,
"inclusive": false,
"active": true,
"createdAt": "2026-09-25T08:00:00.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| displayNamestringrequired | Its name on invoices, 1 to 40 characters, such as VAT.1 to 40 characters |
| descriptionstring | A line about it, up to 120 characters.at most 120 characters |
| percentageBpsintegerrequired | The rate in basis points, 0 to 5000: 1800 is 18%.0 to 5000 |
| inclusiveboolean | True when your prices already include it. Defaults to false: added on top.default false |
Returns 201 with a TaxRate object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running.idempotency_key_reusedidempotency_key_in_progress |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List tax rates
get/v1/tax_rates
Your tax rates in this key's mode, newest first.
- Key
- A secret key (permission billing:read)
curl "https://api.payments.lk/v1/tax_rates?active=true" \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "tax_rate",
"id": "txr_v1x8c3q6m2k9r5t7y0wnhz4p",
"mode": "test",
"displayName": "VAT",
"description": "Value added tax",
"percentageBps": 1800,
"inclusive": false,
"active": true,
"createdAt": "2026-09-25T08:00:00.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
| activeone of: true, false | true for rates taking new subscriptions, false for archived ones. |
Returns 200 with a list of TaxRate objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retrieve a tax rate
get/v1/tax_rates/{id}
One tax rate.
- Key
- A secret key (permission billing:read)
curl https://api.payments.lk/v1/tax_rates/txr_v1x8c3q6m2k9r5t7y0wnhz4p \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "tax_rate",
"id": "txr_v1x8c3q6m2k9r5t7y0wnhz4p",
"mode": "test",
"displayName": "VAT",
"description": "Value added tax",
"percentageBps": 1800,
"inclusive": false,
"active": true,
"createdAt": "2026-09-25T08:00:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The tax rate's id, txr_ followed by 24 characters. |
Returns 200 with a TaxRate object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No tax rate with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Update a tax rate
post/v1/tax_rates/{id}
Its name or description, or active false to archive it: no new subscription takes it, those that have it keep it. Its percentage never changes; for a new rate, create a new tax rate.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/tax_rates/txr_v1x8c3q6m2k9r5t7y0wnhz4p \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: archive-vat-18" \
-d '{
"active": false
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "tax_rate",
"id": "txr_v1x8c3q6m2k9r5t7y0wnhz4p",
"mode": "test",
"displayName": "VAT",
"description": "Value added tax",
"percentageBps": 1800,
"inclusive": false,
"active": false,
"createdAt": "2026-09-25T08:00:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The tax rate's id, txr_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| displayNamestring | A new name.1 to 40 characters |
| descriptionstring or null | A new line about it, or null.at most 120 characters |
| activeboolean | False archives it: no new subscription takes it. |
Returns 200 with a TaxRate object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No tax rate with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running.idempotency_key_reusedidempotency_key_in_progress |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Add an invoice item
post/v1/invoice_items
A one-off line, such as a setup fee or an add-on (a negative amount is a credit): it waits for the customer's next invoice, or the named subscription's, and joins a draft at once when you name one during its draft window.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/invoice_items \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: joining-fee-member-1042" \
-d '{
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"description": "Joining fee",
"unitAmountCents": 250000,
"discountable": false
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "invoice_item",
"id": "ii_f3v7x2c9q1m8k4r6t0yhzw5n",
"mode": "test",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"invoiceId": null,
"description": "Joining fee",
"quantity": 1,
"unitAmountCents": 250000,
"amountCents": 250000,
"proration": false,
"discountable": false,
"createdAt": "2026-10-01T04:12:31.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| descriptionstringrequired | The line as the customer reads it, 1 to 200 characters.1 to 200 characters |
| unitAmountCentsintegerrequired | Per unit, in cents; negative for a credit.-100000000 to 100000000 |
| quantityinteger | How many units, 1 to 10000. Defaults to 1.1 to 10000; default 1 |
| discountableboolean | Whether a coupon discounts it. Defaults to true.default true |
| customerIdstringrequired | The customer it bills. |
| subscriptionIdstring | Only this subscription's next invoice takes it. Without it, the customer's next invoice does. |
| invoiceIdstring | A draft invoice to add it to now, during its draft window. |
Returns 201 with an InvoiceItem object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No customer, subscription or invoice with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. The invoice is not a draft, or the subscription has ended.idempotency_key_reusedidempotency_key_in_progressinvoice_not_opensubscription_ended |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List invoice items
get/v1/invoice_items
Your invoice items in this key's mode, newest first. Filter by customer, and by whether an invoice has taken them.
- Key
- A secret key (permission billing:read)
curl "https://api.payments.lk/v1/invoice_items?customerId=cus_q8w3n5v1x7c2m9r4t6ya0kzp&pending=true" \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "invoice_item",
"id": "ii_f3v7x2c9q1m8k4r6t0yhzw5n",
"mode": "test",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"invoiceId": null,
"description": "Joining fee",
"quantity": 1,
"unitAmountCents": 250000,
"amountCents": 250000,
"proration": false,
"discountable": false,
"createdAt": "2026-10-01T04:12:31.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
| customerIdstring | Only this customer's items. |
| pendingone of: true, false | true for items no invoice has taken yet, false for the ones taken. |
Returns 200 with a list of InvoiceItem objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Delete an invoice item
delete/v1/invoice_items/{id}
Deletes an item no invoice has taken yet. One an invoice took stays as that invoice's record.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Not used
curl -X DELETE https://api.payments.lk/v1/invoice_items/ii_f3v7x2c9q1m8k4r6t0yhzw5n \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "invoice_item",
"id": "ii_f3v7x2c9q1m8k4r6t0yhzw5n",
"mode": "test",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"invoiceId": null,
"description": "Joining fee",
"quantity": 1,
"unitAmountCents": 250000,
"amountCents": 250000,
"proration": false,
"discountable": false,
"createdAt": "2026-10-01T04:12:31.000Z",
"deleted": true
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The invoice item's id, ii_ followed by 24 characters. |
Returns 200 with a DeletedInvoiceItem object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No invoice item with this id for this key; mode_mismatch when it exists in the other mode.mode_mismatch |
| 409CONFLICT | An invoice has taken it.invoice_not_open |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Adjust a customer's credit
post/v1/customers/{id}/balance_transactions
Adds credit to the customer's balance (positive) or takes it away (negative, never below zero). Credit is taken off their next invoices before the card is charged.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/customers/cus_q8w3n5v1x7c2m9r4t6ya0kzp/balance_transactions \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: credit-pool-week" \
-d '{
"amountCents": 150000,
"description": "Goodwill credit for the closed pool week"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "customer_balance_transaction",
"id": "cbtx_b4x9v2c7q3m1k8r5t0nwhz6y",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"amountCents": 150000,
"endingBalanceCents": 150000,
"type": "adjustment",
"invoiceId": null,
"description": "Goodwill credit for the closed pool week",
"createdAt": "2026-10-12T09:00:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The customer's id, cus_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| amountCentsintegerrequired | Positive adds credit to the customer's balance; negative takes it away, never below zero.-100000000 to 100000000 |
| descriptionstring | Why, up to 200 characters.at most 200 characters |
Returns 201 with a CustomerBalanceTransaction object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots. It would take the balance below zero.json_requiredinvalid_jsonidempotency_key_missingamount |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No customer with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running.idempotency_key_reusedidempotency_key_in_progress |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List a customer's credit changes
get/v1/customers/{id}/balance_transactions
Every change to the customer's credit balance, newest first, each with the balance after it.
- Key
- A secret key (permission billing:read)
curl https://api.payments.lk/v1/customers/cus_q8w3n5v1x7c2m9r4t6ya0kzp/balance_transactions \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "customer_balance_transaction",
"id": "cbtx_b4x9v2c7q3m1k8r5t0nwhz6y",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"amountCents": 150000,
"endingBalanceCents": 150000,
"type": "adjustment",
"invoiceId": null,
"description": "Goodwill credit for the closed pool week",
"createdAt": "2026-10-12T09:00:00.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The customer's id, cus_ followed by 24 characters. |
Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
Returns 200 with a list of CustomerBalanceTransaction objects.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No customer with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Move a subscription's time forward
post/v1/test_clocks/advance
Sandbox only. Runs one subscription's clock forward by up to a year, so you can watch the upcoming notice, the renewal, the charge, a retry and a cancellation in minutes instead of months. The worker catches up within a minute of each call.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/test_clocks/advance \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: clock-member-1042-1" \
-d '{
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"seconds": 2678400
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "test_clock",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"frozenTime": "2026-11-01T04:20:00.000Z",
"offsetSeconds": 2678400
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| subscriptionIdstringrequired | A sandbox subscription. |
| secondsintegerrequired | How far to move its clock forward: 60 seconds to a year.60 to 31622400 |
Returns 200 with a TestClock object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No subscription with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. A live key where real money moves, or a subscription that has ended.idempotency_key_reusedidempotency_key_in_progresstest_clock_unavailablesubscription_ended |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Create a subscription schedule
post/v1/subscription_schedules
Phases a subscription moves through, each its prices for a number of billing periods: a discounted first year and then the full price, or installments. The subscription starts on startDate (now by default), charged as any new subscription is: the card on file, or the first invoice's hostedInvoiceUrl. At the renewal where a phase ends it moves to the next phase's prices, with no proration. After the last phase it carries on (endBehavior release) or ends (cancel). Installments: one phase of the installment price with iterations set to the number of payments, and endBehavior cancel.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/subscription_schedules \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: diploma-nimali-installments" \
-d '{
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"startDate": "now",
"endBehavior": "cancel",
"phases": [
{
"items": [
{
"priceId": "price_i7c3v9x2q8m1k6r4t0ynwh5z",
"quantity": 1
}
],
"iterations": 12
}
],
"reference": "Diploma, 12 installments"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "subscription_schedule",
"id": "sched_c6v2x9q1m8k4r7t3y0hwnz5p",
"mode": "test",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"status": "active",
"endBehavior": "cancel",
"startDate": "2026-10-01T04:12:31.000Z",
"phases": [
{
"items": [
{
"priceId": "price_i7c3v9x2q8m1k6r4t0ynwh5z",
"quantity": 1
}
],
"iterations": 12,
"trialDays": null,
"couponId": null
}
],
"currentPhase": 0,
"currentPhaseEndsAt": "2027-10-01T04:12:31.000Z",
"reference": "Diploma, 12 installments",
"completedAt": null,
"releasedAt": null,
"canceledAt": null,
"createdAt": "2026-10-01T04:12:31.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| customerIdstringrequired | The customer, one of yours in this key's mode. |
| startDate"now" or string, ISO 8601 time | "now" (the default) starts the subscription at once, charged as any new subscription is; a time within two years starts it then.default "now" |
| endBehaviorone of: release, cancel | After the last phase: release (the default: the subscription carries on at the last phase's prices) or cancel (it ends: installments).default "release" |
| phasesarray of objectrequired | 1 to 10 phases, in order. |
| phases[].itemsarray of objectrequired | The phase's prices, 1 to 10, each once, all on one interval. |
| phases[].items[].priceIdstringrequired | An active price of yours. |
| phases[].items[].quantityinteger | How many units, 1 to 10000. Defaults to 1.1 to 10000; default 1 |
| phases[].iterationsinteger | How many billing periods the phase lasts, 1 to 120. Every phase but the last has it; give the last one too for endBehavior cancel.1 to 120 |
| phases[].trialDaysinteger | A free trial at the start of the first phase, 1 to 730 days.1 to 730 |
| phases[].couponIdstring | A coupon of yours put on the subscription when the phase starts. |
| referencestring | Your own reference, up to 120 characters.at most 120 characters |
Returns 201 with a SubscriptionSchedule object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots. A phase's prices bill on different intervals, or the start date is out of range.json_requiredinvalid_jsonidempotency_key_missingitems_invalidbilling_date_invalid |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No customer with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. A price is archived or in the other mode, or a coupon cannot be used.idempotency_key_reusedidempotency_key_in_progressprice_unavailablecoupon_invalid |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List subscription schedules
get/v1/subscription_schedules
Your subscription schedules in this key's mode, newest first. Filter by customer and status.
- Key
- A secret key (permission billing:read)
curl "https://api.payments.lk/v1/subscription_schedules?status=active" \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "subscription_schedule",
"id": "sched_c6v2x9q1m8k4r7t3y0hwnz5p",
"mode": "test",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"status": "active",
"endBehavior": "cancel",
"startDate": "2026-10-01T04:12:31.000Z",
"phases": [
{
"items": [
{
"priceId": "price_i7c3v9x2q8m1k6r4t0ynwh5z",
"quantity": 1
}
],
"iterations": 12,
"trialDays": null,
"couponId": null
}
],
"currentPhase": 0,
"currentPhaseEndsAt": "2027-10-01T04:12:31.000Z",
"reference": "Diploma, 12 installments",
"completedAt": null,
"releasedAt": null,
"canceledAt": null,
"createdAt": "2026-10-01T04:12:31.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
| customerIdstring | Only this customer's schedules. |
| statusone of: not_started, active, completed, released, canceled | Only schedules with this status. |
Returns 200 with a list of SubscriptionSchedule objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retrieve a subscription schedule
get/v1/subscription_schedules/{id}
One schedule, with the phase now running and when it ends.
- Key
- A secret key (permission billing:read)
curl https://api.payments.lk/v1/subscription_schedules/sched_c6v2x9q1m8k4r7t3y0hwnz5p \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "subscription_schedule",
"id": "sched_c6v2x9q1m8k4r7t3y0hwnz5p",
"mode": "test",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"status": "active",
"endBehavior": "cancel",
"startDate": "2026-10-01T04:12:31.000Z",
"phases": [
{
"items": [
{
"priceId": "price_i7c3v9x2q8m1k6r4t0ynwh5z",
"quantity": 1
}
],
"iterations": 12,
"trialDays": null,
"couponId": null
}
],
"currentPhase": 0,
"currentPhaseEndsAt": "2027-10-01T04:12:31.000Z",
"reference": "Diploma, 12 installments",
"completedAt": null,
"releasedAt": null,
"canceledAt": null,
"createdAt": "2026-10-01T04:12:31.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The subscription schedule's id, sched_ followed by 24 characters. |
Returns 200 with a SubscriptionSchedule object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No subscription schedule with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Cancel a subscription schedule
post/v1/subscription_schedules/{id}/cancel
A schedule not started never starts; one running cancels its subscription now, as a cancellation from the API does.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl -X POST https://api.payments.lk/v1/subscription_schedules/sched_c6v2x9q1m8k4r7t3y0hwnz5p/cancel \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: diploma-nimali-cancel"The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "subscription_schedule",
"id": "sched_c6v2x9q1m8k4r7t3y0hwnz5p",
"mode": "test",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"status": "canceled",
"endBehavior": "cancel",
"startDate": "2026-10-01T04:12:31.000Z",
"phases": [
{
"items": [
{
"priceId": "price_i7c3v9x2q8m1k6r4t0ynwh5z",
"quantity": 1
}
],
"iterations": 12,
"trialDays": null,
"couponId": null
}
],
"currentPhase": 0,
"currentPhaseEndsAt": null,
"reference": "Diploma, 12 installments",
"completedAt": null,
"releasedAt": null,
"canceledAt": "2027-02-01T04:12:31.000Z",
"createdAt": "2026-10-01T04:12:31.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The subscription schedule's id, sched_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Returns 200 with a SubscriptionSchedule object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No subscription schedule with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. The schedule has already finished.idempotency_key_reusedidempotency_key_in_progresssubscription_change_refused |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Release a subscription schedule
post/v1/subscription_schedules/{id}/release
Lets go of the subscription, which carries on at the prices it has now, with no more phases. A schedule not started never starts.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl -X POST https://api.payments.lk/v1/subscription_schedules/sched_c6v2x9q1m8k4r7t3y0hwnz5p/release \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: diploma-nimali-release"The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "subscription_schedule",
"id": "sched_c6v2x9q1m8k4r7t3y0hwnz5p",
"mode": "test",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"status": "released",
"endBehavior": "cancel",
"startDate": "2026-10-01T04:12:31.000Z",
"phases": [
{
"items": [
{
"priceId": "price_i7c3v9x2q8m1k6r4t0ynwh5z",
"quantity": 1
}
],
"iterations": 12,
"trialDays": null,
"couponId": null
}
],
"currentPhase": 0,
"currentPhaseEndsAt": null,
"reference": "Diploma, 12 installments",
"completedAt": null,
"releasedAt": "2027-02-01T04:12:31.000Z",
"canceledAt": null,
"createdAt": "2026-10-01T04:12:31.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The subscription schedule's id, sched_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Returns 200 with a SubscriptionSchedule object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No subscription schedule with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. The schedule has already finished.idempotency_key_reusedidempotency_key_in_progresssubscription_change_refused |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Create a feature
post/v1/entitlements/features
A feature customers get with a product, as Stripe's entitlements: give it to products with POST /v1/products/{id}/features, and check a customer's features in your app by lookup key. entitlements.active_entitlement_summary.updated tells you when a customer's features change.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/entitlements/features \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: feature-pool" \
-d '{
"name": "Swimming pool",
"lookupKey": "pool"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "entitlements.feature",
"id": "feat_p3v8x1c6q9m2k7r4t0ynhw5z",
"mode": "test",
"name": "Swimming pool",
"lookupKey": "pool",
"active": true,
"createdAt": "2026-09-25T08:00:00.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| namestringrequired | Its name, 1 to 80 characters.1 to 80 characters |
| lookupKeystringrequired | The key your app checks: lower case letters, digits, dots, dashes and underscores, up to 80, unique in the mode. |
Returns 201 with a Feature object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. Another feature in this mode already has that lookup key.idempotency_key_reusedidempotency_key_in_progressfeature_exists |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List features
get/v1/entitlements/features
Your features in this key's mode, newest first.
- Key
- A secret key (permission billing:read)
curl https://api.payments.lk/v1/entitlements/features \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "entitlements.feature",
"id": "feat_p3v8x1c6q9m2k7r4t0ynhw5z",
"mode": "test",
"name": "Swimming pool",
"lookupKey": "pool",
"active": true,
"createdAt": "2026-09-25T08:00:00.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
Returns 200 with a list of Feature objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List a customer's features
get/v1/entitlements/active_entitlements
The features a customer has now: those of the products on their active, trialing and past due subscriptions, each once. Check one by lookup key before letting them use it.
- Key
- A secret key (permission billing:read)
curl "https://api.payments.lk/v1/entitlements/active_entitlements?customerId=cus_q8w3n5v1x7c2m9r4t6ya0kzp" \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "entitlements.active_entitlement",
"featureId": "feat_g2v7x9c5q1m8k3r6t0hwnz4y",
"lookupKey": "gym"
},
{
"object": "entitlements.active_entitlement",
"featureId": "feat_p3v8x1c6q9m2k7r4t0ynhw5z",
"lookupKey": "pool"
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| customerIdstringrequired | The customer. |
Returns 200 with a list of ActiveEntitlement objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No customer with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Give a product a feature
post/v1/products/{id}/features
Every running subscription to the product now gives its customer the feature; their summaries are sent. A feature already on the product is answered as it is.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/products/prod_h2k9m4q7v1x8c3r6t0ynwz5p/features \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: gym-pool-feature" \
-d '{
"featureId": "feat_p3v8x1c6q9m2k7r4t0ynhw5z"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "product_feature",
"id": "pfeat_q4v9x2c7m1k8r3t6y0wnhz5p",
"productId": "prod_h2k9m4q7v1x8c3r6t0ynwz5p",
"featureId": "feat_p3v8x1c6q9m2k7r4t0ynhw5z",
"lookupKey": "pool",
"createdAt": "2026-09-25T08:05:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The product's id, prod_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| featureIdstringrequired | The feature to give with the product. |
Returns 200 with a ProductFeature object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No product with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running.idempotency_key_reusedidempotency_key_in_progress |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List a product's features
get/v1/products/{id}/features
The features given with a product, oldest first.
- Key
- A secret key (permission billing:read)
curl https://api.payments.lk/v1/products/prod_h2k9m4q7v1x8c3r6t0ynwz5p/features \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "product_feature",
"id": "pfeat_q4v9x2c7m1k8r3t6y0wnhz5p",
"productId": "prod_h2k9m4q7v1x8c3r6t0ynwz5p",
"featureId": "feat_p3v8x1c6q9m2k7r4t0ynhw5z",
"lookupKey": "pool",
"createdAt": "2026-09-25T08:05:00.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The product's id, prod_ followed by 24 characters. |
Returns 200 with a list of ProductFeature objects.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No product with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Take a feature off a product
delete/v1/products/{id}/features/{featureId}
Customers with the product lose the feature, unless another of their products gives it; their summaries are sent.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Not used
curl -X DELETE https://api.payments.lk/v1/products/prod_h2k9m4q7v1x8c3r6t0ynwz5p/features/feat_p3v8x1c6q9m2k7r4t0ynhw5z \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "product_feature",
"productId": "prod_h2k9m4q7v1x8c3r6t0ynwz5p",
"featureId": "feat_p3v8x1c6q9m2k7r4t0ynhw5z",
"deleted": true
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The product's id, prod_ followed by 24 characters. |
| featureIdstringrequired | The feature's id, feat_ followed by 24 characters. |
Returns 200 with a DeletedProductFeature object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No product feature with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Create a meter
post/v1/meters
What you count for usage based billing: API calls, messages, kilowatt hours. Report each use with POST /v1/meter_events under its eventName; a metered price on the meter bills a customer's usage at the end of each period, by the price's billing scheme.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/meters \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: meter-sms" \
-d '{
"name": "SMS sent",
"eventName": "sms.sent",
"aggregation": "sum"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "meter",
"id": "mtr_a4c8v2x9q1m7k3r6t0yhwn5z",
"mode": "test",
"name": "SMS sent",
"eventName": "sms.sent",
"aggregation": "sum",
"active": true,
"createdAt": "2026-10-01T04:00:00.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| namestringrequired | Its name, 1 to 80 characters, as invoice lines show the usage.1 to 80 characters |
| eventNamestringrequired | The name your meter events will carry: lower case letters, digits, dots, dashes and underscores, up to 100, unique in the mode. |
| aggregationone of: sum, count, last | sum (the default), count or last.default "sum" |
Returns 201 with a Meter object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. Another meter in this mode already counts that eventName.idempotency_key_reusedidempotency_key_in_progressmeter_exists |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List meters
get/v1/meters
Your meters in this key's mode, newest first.
- Key
- A secret key (permission billing:read)
curl https://api.payments.lk/v1/meters \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "meter",
"id": "mtr_a4c8v2x9q1m7k3r6t0yhwn5z",
"mode": "test",
"name": "SMS sent",
"eventName": "sms.sent",
"aggregation": "sum",
"active": true,
"createdAt": "2026-10-01T04:00:00.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
Returns 200 with a list of Meter objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retrieve a meter
get/v1/meters/{id}
One meter.
- Key
- A secret key (permission billing:read)
curl https://api.payments.lk/v1/meters/mtr_a4c8v2x9q1m7k3r6t0yhwn5z \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "meter",
"id": "mtr_a4c8v2x9q1m7k3r6t0yhwn5z",
"mode": "test",
"name": "SMS sent",
"eventName": "sms.sent",
"aggregation": "sum",
"active": true,
"createdAt": "2026-10-01T04:00:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The meter's id, mtr_ followed by 24 characters. |
Returns 200 with a Meter object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No meter with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Update a meter
post/v1/meters/{id}
Renames a meter, or turns it off (active false: its events are refused, and its prices bill what it counted) or back on.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/meters/mtr_a4c8v2x9q1m7k3r6t0yhwn5z \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: meter-sms-rename" \
-d '{
"name": "Messages sent"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "meter",
"id": "mtr_a4c8v2x9q1m7k3r6t0yhwn5z",
"mode": "test",
"name": "Messages sent",
"eventName": "sms.sent",
"aggregation": "sum",
"active": true,
"createdAt": "2026-10-01T04:00:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The meter's id, mtr_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| namestring | Its new name.1 to 80 characters |
| activeboolean | False turns it off: its events are refused. True turns it back on. |
Returns 200 with a Meter object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No meter with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running.idempotency_key_reusedidempotency_key_in_progress |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Summarise a customer's usage
get/v1/meters/{id}/event_summaries
A customer's usage on the meter between two times, as the meter adds it up: what a metered price would bill for that span.
- Key
- A secret key (permission billing:read)
curl "https://api.payments.lk/v1/meters/mtr_a4c8v2x9q1m7k3r6t0yhwn5z/event_summaries?customerId=cus_q8w3n5v1x7c2m9r4t6ya0kzp&start=2026-10-01T04%3A12%3A31.000Z&end=2026-11-01T04%3A12%3A31.000Z" \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "meter_event_summary",
"meterId": "mtr_a4c8v2x9q1m7k3r6t0yhwn5z",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"start": "2026-10-01T04:12:31.000Z",
"end": "2026-11-01T04:12:31.000Z",
"aggregatedValue": 12480
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The meter's id, mtr_ followed by 24 characters. |
Query parameters
| Name | Description |
|---|---|
| customerIdstringrequired | The customer. |
| startstring, ISO 8601 timerequired | From this time, ISO 8601. |
| endstring, ISO 8601 timerequired | Up to (not including) this time, ISO 8601, after start. |
Returns 200 with a MeterEventSummary object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No meter with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Report usage
post/v1/meter_events
One use of a customer's, counted by the active meter whose eventName it carries, dated when it happened (within the last 35 days). Send your own identifier so a retry never counts twice: the same identifier answers with the event already kept. Usage alerts the customer's usage now reaches fire as billing.alert.triggered.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/meter_events \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: usage-msg-88412" \
-d '{
"eventName": "sms.sent",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"value": 3,
"timestamp": "2026-10-18T06:40:12.000Z",
"identifier": "msg-88412"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "meter_event",
"id": "mev_e2x7v1c9q4m8k3r6t0ynhw5p",
"mode": "test",
"meterId": "mtr_a4c8v2x9q1m7k3r6t0yhwn5z",
"eventName": "sms.sent",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"value": 3,
"timestamp": "2026-10-18T06:40:12.000Z",
"identifier": "msg-88412",
"createdAt": "2026-10-18T06:40:12.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| eventNamestringrequired | The eventName of an active meter of yours in this mode. |
| customerIdstringrequired | The customer whose usage it is. |
| valueinteger | How much, a whole number from 0. Defaults to 1.0 to 1000000000; default 1 |
| timestampstring, ISO 8601 time | When it happened, ISO 8601: within the last 35 days, and not ahead of now. Defaults to now. |
| identifierstring | Your own id for it, up to 100 characters: sending it again returns the event already kept.at most 100 characters |
Returns 201 with a MeterEvent object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots. No active meter counts that eventName, or the timestamp is out of range.json_requiredinvalid_jsonidempotency_key_missingmeter_invalidbilling_date_invalid |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No customer with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running.idempotency_key_reusedidempotency_key_in_progress |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Create a usage alert
post/v1/usage_alerts
Sends billing.alert.triggered once a customer's usage on the meter in their current period reaches the threshold, once per customer: for one customer, or for every customer.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/usage_alerts \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: alert-sms-10k" \
-d '{
"meterId": "mtr_a4c8v2x9q1m7k3r6t0yhwn5z",
"title": "10,000 messages",
"threshold": 10000
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "usage_alert",
"id": "ual_t3v9x1c7q2m8k4r6y0wnhz5p",
"mode": "test",
"meterId": "mtr_a4c8v2x9q1m7k3r6t0yhwn5z",
"customerId": null,
"title": "10,000 messages",
"threshold": 10000,
"active": true,
"createdAt": "2026-10-01T04:05:00.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| meterIdstringrequired | The meter to watch. |
| customerIdstring | One customer to watch. Leave it out to watch every customer. |
| titlestringrequired | Its name, 1 to 80 characters, sent with the event.1 to 80 characters |
| thresholdintegerrequired | The usage in a customer's current period that fires it, from 1.1 to 1000000000 |
Returns 201 with an UsageAlert object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots. The meter is not one of yours in this mode.json_requiredinvalid_jsonidempotency_key_missingmeter_invalid |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No customer with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running.idempotency_key_reusedidempotency_key_in_progress |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List usage alerts
get/v1/usage_alerts
Your usage alerts in this key's mode, newest first. Filter by meter.
- Key
- A secret key (permission billing:read)
curl "https://api.payments.lk/v1/usage_alerts?meterId=mtr_a4c8v2x9q1m7k3r6t0yhwn5z" \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "usage_alert",
"id": "ual_t3v9x1c7q2m8k4r6y0wnhz5p",
"mode": "test",
"meterId": "mtr_a4c8v2x9q1m7k3r6t0yhwn5z",
"customerId": null,
"title": "10,000 messages",
"threshold": 10000,
"active": true,
"createdAt": "2026-10-01T04:05:00.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
| meterIdstring | Only the alerts watching this meter. |
Returns 200 with a list of UsageAlert objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Archive a usage alert
post/v1/usage_alerts/{id}/archive
Stops the alert firing for anyone new. Those it fired for stay recorded.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl -X POST https://api.payments.lk/v1/usage_alerts/ual_t3v9x1c7q2m8k4r6y0wnhz5p/archive \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: alert-sms-10k-off"The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "usage_alert",
"id": "ual_t3v9x1c7q2m8k4r6y0wnhz5p",
"mode": "test",
"meterId": "mtr_a4c8v2x9q1m7k3r6t0yhwn5z",
"customerId": null,
"title": "10,000 messages",
"threshold": 10000,
"active": false,
"createdAt": "2026-10-01T04:05:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The usage alert's id, ual_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Returns 200 with an UsageAlert object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No usage alert with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running.idempotency_key_reusedidempotency_key_in_progress |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Create a subscription link
post/v1/subscription_links
A public link to subscribe, with no website of your own: the customer chooses one of up to six prices (monthly and yearly side by side), gives their email, and pays the first period at a checkout that keeps the card. With trialDays nothing is paid; the card is checked with Rs. 10, refunded at once, and charged when the trial ends. With allowPromotionCodes the page takes a code matched to your coupons' codes. A link never charges a card already on file and never changes an existing customer's details. Subscriptions made through it carry subscriptionLinkId.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/subscription_links \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: link-membership-2026" \
-d '{
"name": "Membership, monthly or yearly",
"priceIds": [
"price_m3v8q1x6c9k2r5t7y0wn4hzp",
"price_y5c1v8x3q9m2k7r4t0hwnz6p"
],
"trialDays": 7,
"allowPromotionCodes": true,
"successUrl": "https://gym.example/welcome"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "subscription_link",
"id": "slk_g4v8x2c9q1m7k3r6t0ynwh5z",
"mode": "test",
"url": "https://payments.lk/s/4fQ9kZ2mR7xW1pL8vN3bT6",
"name": "Membership, monthly or yearly",
"prices": [
{
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"nickname": null,
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1,
"active": true
},
{
"id": "price_y5c1v8x3q9m2k7r4t0hwnz6p",
"productName": "Gym membership",
"nickname": "Yearly, two months free",
"unitAmountCents": 4500000,
"interval": "year",
"intervalCount": 1,
"active": true
}
],
"trialDays": 7,
"allowPromotionCodes": true,
"successUrl": "https://gym.example/welcome",
"active": true,
"createdAt": "2026-09-25T08:30:00.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| namestringrequired | Its name, for you, 1 to 80 characters.1 to 80 characters |
| priceIdsarray of stringrequired | One to six active prices of yours, each once, in the order the page shows them. Mix monthly and yearly to let customers choose. |
| trialDaysinteger | A free trial of 1 to 730 days for everyone who subscribes through it. Their card is checked with Rs. 10, refunded at once, and charged when the trial ends.1 to 730 |
| allowPromotionCodesboolean | True shows a field for a code, matched to your coupons' codes. Defaults to false.default false |
| successUrlstring | Where customers land after subscribing: https, or your app's scheme. Without it, the link's own thank you page.at most 2000 characters |
Returns 201 with a SubscriptionLink object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running. A price is archived or in the other mode.idempotency_key_reusedidempotency_key_in_progressprice_unavailable |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
List subscription links
get/v1/subscription_links
Your subscription links in this key's mode, newest first.
- Key
- A secret key (permission billing:read)
curl "https://api.payments.lk/v1/subscription_links?active=true" \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "list",
"data": [
{
"object": "subscription_link",
"id": "slk_g4v8x2c9q1m7k3r6t0ynwh5z",
"mode": "test",
"url": "https://payments.lk/s/4fQ9kZ2mR7xW1pL8vN3bT6",
"name": "Membership, monthly or yearly",
"prices": [
{
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"nickname": null,
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1,
"active": true
},
{
"id": "price_y5c1v8x3q9m2k7r4t0hwnz6p",
"productName": "Gym membership",
"nickname": "Yearly, two months free",
"unitAmountCents": 4500000,
"interval": "year",
"intervalCount": 1,
"active": true
}
],
"trialDays": 7,
"allowPromotionCodes": true,
"successUrl": "https://gym.example/welcome",
"active": true,
"createdAt": "2026-09-25T08:30:00.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Query parameters
| Name | Description |
|---|---|
| limitinteger | How many to return, 1 to 100. Defaults to 25.1 to 100; default 25 |
| cursorstring | The nextCursor of the page before. Leave it out for the first page. |
| activeone of: true, false | true for links that work, false for ones turned off. |
Returns 200 with a list of SubscriptionLink objects.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A query parameter is not valid; fields names each one. |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Retrieve a subscription link
get/v1/subscription_links/{id}
One subscription link.
- Key
- A secret key (permission billing:read)
curl https://api.payments.lk/v1/subscription_links/slk_g4v8x2c9q1m7k3r6t0ynwh5z \
-H "Authorization: Bearer sk_test_..."The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "subscription_link",
"id": "slk_g4v8x2c9q1m7k3r6t0ynwh5z",
"mode": "test",
"url": "https://payments.lk/s/4fQ9kZ2mR7xW1pL8vN3bT6",
"name": "Membership, monthly or yearly",
"prices": [
{
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"nickname": null,
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1,
"active": true
},
{
"id": "price_y5c1v8x3q9m2k7r4t0hwnz6p",
"productName": "Gym membership",
"nickname": "Yearly, two months free",
"unitAmountCents": 4500000,
"interval": "year",
"intervalCount": 1,
"active": true
}
],
"trialDays": 7,
"allowPromotionCodes": true,
"successUrl": "https://gym.example/welcome",
"active": true,
"createdAt": "2026-09-25T08:30:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The subscription link's id, slk_ followed by 24 characters. |
Returns 200 with a SubscriptionLink object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No subscription link with this id for this key. |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Update a subscription link
post/v1/subscription_links/{id}
Changes its name, trial, codes or landing page, or turns it off (active false) so its page says the link does not work, or back on. Its prices stay as they were: make a new link for other prices.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/subscription_links/slk_g4v8x2c9q1m7k3r6t0ynwh5z \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: link-membership-off" \
-d '{
"active": false
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "subscription_link",
"id": "slk_g4v8x2c9q1m7k3r6t0ynwh5z",
"mode": "test",
"url": "https://payments.lk/s/4fQ9kZ2mR7xW1pL8vN3bT6",
"name": "Membership, monthly or yearly",
"prices": [
{
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"nickname": null,
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1,
"active": true
},
{
"id": "price_y5c1v8x3q9m2k7r4t0hwnz6p",
"productName": "Gym membership",
"nickname": "Yearly, two months free",
"unitAmountCents": 4500000,
"interval": "year",
"intervalCount": 1,
"active": true
}
],
"trialDays": 7,
"allowPromotionCodes": true,
"successUrl": "https://gym.example/welcome",
"active": false,
"createdAt": "2026-09-25T08:30:00.000Z"
}Path parameters
| Name | Description |
|---|---|
| idstringrequired | The subscription link's id, slk_ followed by 24 characters. |
Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| namestring | Its new name.1 to 80 characters |
| activeboolean | False turns the link off; true turns it back on. |
| trialDaysinteger or null | A new free trial length, or null to take the trial away.1 to 730 |
| allowPromotionCodesboolean | Whether the page takes a code. |
| successUrlstring or null | A new landing page, or null for the link's own.at most 2000 characters |
Returns 200 with a SubscriptionLink object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No subscription link with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running.idempotency_key_reusedidempotency_key_in_progress |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Open the customer portal
post/v1/billing_portal/sessions
A customer's way into the customer portal, where they see their subscriptions and invoices, change their card, and, as your billing settings allow, switch plan or cancel. Send the customer to url straight away: it works for an hour. Make one each time a signed in customer asks to manage their subscription. A new card is taken by a Rs. 10 check that is refunded at once.
- Key
- A secret key (permission billing:write)
- Idempotency-Key
- Required
curl https://api.payments.lk/v1/billing_portal/sessions \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: portal-member-1042-20261016" \
-d '{
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"returnUrl": "https://gym.example/account"
}'The Node and PHP libraries do not have subscription methods yet. Call it over HTTP with your secret key, as shown; the libraries' types already describe the objects and the events.
{
"object": "billing_portal.session",
"id": "bps_k3v8x1c6q9m2r5t7y0wn4hzp",
"mode": "test",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"url": "https://payments.lk/billing/Xq7d2V9kC4mZt1Wn8Rb5Hy3Lp6Gs0Jf2Ae4Uo7Ik9Nx",
"returnUrl": "https://gym.example/account",
"expiresAt": "2026-10-16T05:12:31.000Z",
"createdAt": "2026-10-16T04:12:31.000Z"
}Headers
| Name | Description |
|---|---|
| Idempotency-Keystringrequired | Makes the write safe to retry. A key is remembered for 24 hours, separately for test and live keys: within that time the same key with the same body gets the first answer back instead of making a second one, and after it the key counts as new. Use a new key for every new write. 8 to 255 letters, digits, dashes, underscores, colons or dots. |
Body
| Field | Description |
|---|---|
| customerIdstringrequired | The customer, one of yours in this key's mode. |
| returnUrlstring | Where the portal's back link takes the customer: https, or your app's scheme.at most 2000 characters |
Returns 201 with a BillingPortalSession object.
Errors
| Status | When |
|---|---|
| 400VALIDATION_FAILED | A field is missing or not valid, fields names each one; or the body is not JSON. The Idempotency-Key header is missing, or is not 8 to 255 letters, digits, dashes, underscores, colons or dots.json_requiredinvalid_jsonidempotency_key_missing |
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 404NOT_FOUND | No customer with this id for this key. |
| 409CONFLICT | The Idempotency-Key was already used with a different request, or the first request with it is still running.idempotency_key_reusedidempotency_key_in_progress |
| 413VALIDATION_FAILED | The body is larger than the API reads.body_too_large |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Account
The merchant account behind the key.
Retrieve your account
get/v1/account
The merchant behind the key: status, plan, this month's usage against the plan's monthly limit in the key's mode, and whether live payments are open. A payment that would take the month past the limit is refused with monthly_limit_reached; usage says how close the month is.
- Key
- A secret key (permission account:read)
curl https://api.payments.lk/v1/account \
-H "Authorization: Bearer sk_test_..."{
"id": "mer_yb1xzmzqdza69g0p3hpeaew6",
"tradingName": "Colombo Kottu House",
"status": "activated",
"applicationStatus": "approved",
"clearance": {
"tier": "starter",
"monthlyCapCents": 10000000
},
"pricingPlan": "standard",
"plan": "starter",
"usage": {
"month": "2026-09",
"mode": "test",
"plan": "starter",
"capCents": 10000000,
"settledCents": 6340000,
"reservedCents": 0,
"percent": 63,
"resetsAt": "2026-09-30T18:30:00.000Z",
"refusedCount": 0,
"refusedCents": 0
},
"locale": "en",
"liveEnabled": true,
"phase1Position": null
}Returns 200 with an Account object.
Errors
| Status | When |
|---|---|
| 401UNAUTHENTICATED | The key is missing, malformed, or not a live key: revoked, expired or never issued.missing_api_keymalformed_api_keyunknown_api_key |
| 403FORBIDDEN | This kind of key cannot do this: a publishable key may only create checkouts, and an agent key opens no REST endpoint.key_not_permitted |
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Reference
This document.
This document
get/v1/openapi.json
The OpenAPI 3.1 description of this API, generated from the same schemas the API validates with. No key needed; any origin may read it.
- Key
- None. This document is public.
curl https://api.payments.lk/v1/openapi.jsonNo key and no library needed: any HTTP client, or an OpenAPI tool pointed at the URL.
{
"openapi": "3.1.0",
"info": {
"title": "Payments.lk API",
"version": "v1"
}
}Returns 200 with this OpenAPI document.
Errors
| Status | When |
|---|---|
| 429RATE_LIMITED | More than 120 requests a minute from one address to this endpoint. Wait for Retry-After seconds. |
| 500INTERNAL | Something failed on our side. Retry, with the same Idempotency-Key for a write. |
Objects
What the endpoints and webhooks return, field by field.
Checkout
A payment and the hosted page that takes it.
Fields
| Field | Description |
|---|---|
| object"checkout"required | Always "checkout". |
| idstringrequired | The checkout's id, chk_ followed by 24 characters. |
| modeone of: test, liverequired | test for a sandbox key, live for a live key. The key decides it; a key never reads the other mode's rows. |
| statusone of: open, processing, completed, expired, canceledrequired | open until the customer pays or it expires; processing while the processor decides; then completed, expired or canceled. An open checkout past expiresAt reads as expired. |
| urlstringrequired | The hosted checkout page, in the checkout's language. Send the customer here. |
| expiresAtstring, ISO 8601 timerequired | When the checkout stops taking a payment, 30 minutes after it was made. |
| paymentPaymentrequired | The payment the checkout takes. |
Payment
One payment, however it was made: a checkout, a payment link, a checkout page or a saved card.
Fields
| Field | Description |
|---|---|
| object"payment"required | Always "payment". |
| idstringrequired | The payment's id, pay_ followed by 24 characters. |
| modeone of: test, liverequired | test for a sandbox key, live for a live key. The key decides it; a key never reads the other mode's rows. |
| statusone of: requires_payment_method, processing, succeeded, failed, canceled, partially_refunded, refunded, disputedrequired | requires_payment_method until the customer pays; processing while the processor decides; then succeeded or failed. canceled when its checkout expired unpaid. partially_refunded and refunded once refunds succeed. disputed is reserved for chargebacks, which are not reported yet. |
| amountCentsintegerrequired | The amount in LKR cents. Rs. 3,500 is 350000. |
| currency"LKR"required | Always "LKR". |
| descriptionstringrequired | What the customer is paying for, as you sent it. |
| referencestring or nullrequired | Your own reference, such as an order number, or null. |
| customerobject or nullrequired | The customer's details, sent with the checkout or typed on the checkout page. Null when there are none. |
| customer.namestring or nullrequired | The customer's name, or null. |
| customer.emailstring or nullrequired | The customer's email address, lower case, or null. |
| customer.phonestring or nullrequired | The customer's phone number, or null. |
| cardobject or nullrequired | The card's scheme and last four digits, once the processor reports them. Null before that. |
| card.schemestring or nullrequired | The card scheme as the processor names it, such as VISA or MASTERCARD, or null. |
| card.last4string or nullrequired | The last four digits of the card number, or null. |
| cardSaveone of: not_requested, requested, saved, failedrequired | Whether the card was kept on file. not_requested: the checkout did not offer to keep it, or the customer did not agree. requested: keeping it was offered or agreed and the payment has not succeeded, so nothing is kept (and nothing will be, if it fails). saved: the card was kept, and card.saved names it. failed: the payment succeeded but the card was not kept, because the processor did not tokenise it or its answer carried no token; card.save_failed was sent, and the customer must add the card again. |
| feeCentsinteger or nullrequired | The Payments.lk fee in cents, set when the payment succeeds. Null until then. |
| netCentsinteger or nullrequired | amountCents less feeCents, set when the payment succeeds. Null until then. |
| refundedCentsintegerrequired | How much has been refunded so far, in cents. |
| failureMessagestring or nullrequired | Why the payment failed, in a sentence you can show the customer. Null otherwise. |
| paymentLinkIdstring or nullrequired | The payment link it was paid through, or null. |
| savedCardIdstring or nullrequired | The saved card it was charged to, when it was charged without the customer present. Null otherwise. |
| invoiceIdstring or nullrequired | The invoice this payment pays: a subscription's first payment, a renewal charged to the card on file, or a payment through a pay link. Null otherwise. |
| checkoutIdstring or nullrequired | The checkout it belongs to, or null. A saved card charge has none. |
| checkoutPageIdstring or nullrequired | The checkout page it was made on, or null. |
| orderobject or nullrequired | What the customer ordered, on a checkout page or through a checkout's lineItems, priced by us. Null for every other payment. |
| order.linesarray of objectrequired | The lines of the order. |
| order.lines[].idstringrequired | The line's id: the product's id on a checkout page, or line-1 to line-10 for lineItems in the order sent. |
| order.lines[].namestringrequired | The product's name. |
| order.lines[].descriptionstring | The line's description, when it has one. |
| order.lines[].quantityintegerrequired | How many the customer ordered. |
| order.lines[].unitAmountCentsintegerrequired | The price of one, in cents. |
| order.lines[].amountCentsintegerrequired | The line's total, in cents. |
| order.lines[].suggestedbooleanrequired | True when this is the product the page suggested and the customer added. |
| order.subtotalCentsintegerrequired | The lines' total before discount, delivery and added tax, in cents. |
| order.discountobject or nullrequired | The promotion code used and what it took off, or null. |
| order.discount.codestringrequired | The promotion code. |
| order.discount.centsintegerrequired | What it took off, in cents. |
| order.shippingobject or nullrequired | The delivery option chosen, or null. |
| order.shipping.idstringrequired | The delivery option's id on the checkout page. |
| order.shipping.labelstringrequired | The delivery option's name. |
| order.shipping.centsintegerrequired | What delivery cost, in cents. |
| order.taxobject or nullrequired | The tax on the order, or null when the page charges none. |
| order.tax.modeone of: included, addedrequired | included when prices already hold the tax, added when it was added on top. |
| order.tax.rateBpsintegerrequired | The tax rate in basis points: 1800 is 18%. |
| order.tax.centsintegerrequired | The tax, in cents. |
| order.totalCentsintegerrequired | What the order came to, in cents. |
| deliveryobject or nullrequired | Where to deliver, from a checkout page that asks. Null otherwise. |
| delivery.sameAsBillingbooleanrequired | True when the customer said to deliver to their billing address. |
| delivery.namestring or nullrequired | Who to deliver to, or null. |
| delivery.streetstring or nullrequired | The street address, or null. |
| delivery.citystring or nullrequired | The town or city, or null. |
| delivery.postcodestring or nullrequired | The postal code, or null. |
| customFieldsarray of object or nullrequired | Answers to the questions a checkout page asked, or null. |
| customFields[].keystringrequired | The question's key on the checkout page. |
| customFields[].labelstringrequired | The question as the customer saw it. |
| customFields[].valuestringrequired | The customer's answer. |
| customerTaxIdstring or nullrequired | The customer's tax number, when a checkout page asked for it. Null otherwise. |
| termsAcceptedAtstring, ISO 8601 time or nullrequired | When the customer accepted the checkout page's terms, or null. |
| createdAtstring, ISO 8601 timerequired | When it was created, ISO 8601 in UTC. |
| succeededAtstring, ISO 8601 time or nullrequired | When the payment succeeded, or null. |
Refund
Money going back to the customer's card, in full or in part.
Fields
| Field | Description |
|---|---|
| object"refund"required | Always "refund". |
| idstringrequired | The refund's id, re_ followed by 24 characters. |
| modeone of: test, liverequired | test for a sandbox key, live for a live key. The key decides it; a key never reads the other mode's rows. |
| paymentIdstringrequired | The payment it refunds. |
| statusone of: pending, processing, succeeded, failedrequired | pending when recorded, processing once sent to the processor, then succeeded or failed. The refund.succeeded or refund.failed webhook tells you which. |
| amountCentsintegerrequired | The amount refunded, in cents. |
| currency"LKR"required | Always "LKR". |
| reasonstring or nullrequired | The reason you gave, or null. |
| failureMessagestring or nullrequired | Why the refund failed, or null. |
| createdAtstring, ISO 8601 timerequired | When it was created, ISO 8601 in UTC. |
| succeededAtstring, ISO 8601 time or nullrequired | When the refund succeeded, or null. |
Payment link
A fixed amount link you can send anywhere. Each payer gets a payment of their own.
Fields
| Field | Description |
|---|---|
| object"payment_link"required | Always "payment_link". |
| idstringrequired | The link's id, plink_ followed by 24 characters. |
| modeone of: test, liverequired | test for a sandbox key, live for a live key. The key decides it; a key never reads the other mode's rows. |
| titlestringrequired | What the link is for, shown to the payer. |
| descriptionstring or nullrequired | More about it, shown to the payer, or null. |
| amountCentsintegerrequired | The fixed amount every payer pays, in cents. |
| urlstringrequired | The link to send. Each payer who opens it gets a checkout of their own. |
| activebooleanrequired | False once the link has been retired. A retired link takes no payments. |
| paidCountintegerrequired | How many payments the link has taken. The answers to create and retire always say 0; read the list for the count. |
| createdAtstring, ISO 8601 timerequired | When it was created, ISO 8601 in UTC. |
Saved card
A card a customer chose to keep on file at a checkout.
Fields
| Field | Description |
|---|---|
| object"saved_card"required | Always "saved_card". |
| idstringrequired | The saved card's id, card_ followed by 24 characters. It is not a card number and works only with your key. |
| modeone of: test, liverequired | test for a sandbox key, live for a live key. The key decides it; a key never reads the other mode's rows. |
| customerEmailstring or nullrequired | The email address of the customer who kept the card, or null. |
| schemestring or nullrequired | The card scheme, such as VISA, or null. |
| last4string or nullrequired | The last four digits of the card number, or null. |
| expiryobject or nullrequired | The month the card expires, as the processor reported it, or null. A charge after it will be declined, so ask the customer for a new card before then. |
| expiry.monthintegerrequired | The month, 1 to 12.1 to 12 |
| expiry.yearintegerrequired | The four digit year. |
| paymentIdstring or nullrequired | The payment the customer made when they kept the card, or null for a card saved before this was recorded. |
| consentobject or nullrequired | What the customer agreed to when they kept the card, or null for a card saved before this was recorded. Keep it: it is your record of why you may charge them. |
| consent.agreedAtstring, ISO 8601 timerequired | When the customer agreed, ISO 8601. |
| consent.localeone of: en, si, tarequired | The language the customer was reading: en, si or ta. |
| consent.wordingstringrequired | Which wording the customer read, such as checkoutPage.saveCard@1. The sentence itself is on our checkout page in that language, and a change to the words mints a new id rather than rewriting this one. |
| activebooleanrequired | False once the card has been deleted. |
| createdAtstring, ISO 8601 timerequired | When it was created, ISO 8601 in UTC. |
| retiredAtstring, ISO 8601 time or nullrequired | When the card was deleted, ISO 8601, or null while it is still usable. |
Account
The merchant account behind the key.
Fields
| Field | Description |
|---|---|
| idstringrequired | Your merchant id, mer_ followed by 24 characters. |
| tradingNamestring or nullrequired | The name customers see, or null before you have given one. |
| statusone of: prospect, applying, submitted, in_review, needs_info, approved, queued, activated, rejected, suspended, closedrequired | Where your account is: prospect and applying while you fill in the application, submitted, in_review and needs_info while it is reviewed, approved, queued and activated after; rejected, suspended or closed when it cannot take payments. Live payments need activated. |
| applicationStatusone of: draft, submitted, in_review, needs_info, approved, rejected or nullrequired | Your application's status, or null before you have started one. |
| clearanceobjectrequired | Kept for integrations written before plans. Read plan and usage instead. |
| clearance.tierone of: starter, verified, enterpriserequired | starter for the Starter plan, verified for Growth, enterprise for Enterprise. |
| clearance.monthlyCapCentsinteger or nullrequired | Your plan's monthly limit in cents, or null for no limit. The same figure as usage.capCents. |
| pricingPlanstringrequired | The pricing plan your fees are worked out on. |
| planone of: starter, growth, enterpriserequired | starter, growth or enterprise. Starter and Growth take card payments up to a monthly limit, the ceiling of their pricing bracket; Enterprise has none. Upgrade from the dashboard's Plans page. |
| usageobjectrequired | This month's card payments in the key's mode against your plan's monthly limit. Test mode counts separately, against the same limit. |
| usage.monthstringrequired | The calendar month in Sri Lanka, such as 2026-09. |
| usage.modeone of: live, testrequired | live or test: the mode of the key that asked. |
| usage.planone of: starter, growth, enterpriserequired | The plan this month is counted against. An upgrade changes it at once. |
| usage.capCentsinteger or nullrequired | The monthly limit in cents, or null for no limit. |
| usage.settledCentsintegerrequired | Payments that succeeded this month, in cents, gross: refunds do not free up room. |
| usage.reservedCentsintegerrequired | Payments with the processor now, in cents. They count toward the limit until they settle or fail. |
| usage.percentintegerrequired | Whole percent of the limit used by settled payments, rounded down. 0 with no limit. |
| usage.resetsAtstringrequired | When the limit resets: midnight on the 1st in Sri Lanka, as ISO 8601 in UTC. |
| usage.refusedCountintegerrequired | Payments refused this month because they would have passed the limit. |
| usage.refusedCentsintegerrequired | What those refused payments came to, in cents. |
| localeone of: en, si, tarequired | The language of your dashboard and emails: en, si or ta. |
| liveEnabledbooleanrequired | True once live keys can take payments. |
| phase1Positioninteger or nullrequired | Your place in the launch queue if you were approved beyond the launch cap, or null. |
Event
Something that happened on your account, in the same shape the webhook delivers it.
Fields
| Field | Description |
|---|---|
| idstringrequired | The event's id, evt_ followed by 24 characters. Deliveries of the same event carry the same id, so use it to ignore a repeat. |
| object"event"required | Always "event". |
| typeone of: payment.succeeded, payment.failed, checkout.expired, card.saved, card.save_failed, card.expiring, billing.alert.triggered, subscription_schedule.created, subscription_schedule.completed, subscription_schedule.released, subscription_schedule.canceled, refund.succeeded, refund.failed, account.limit.approaching, account.limit.reached, account.limit.reset, customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, customer.subscription.trial_will_end, customer.subscription.paused, customer.subscription.resumed, invoice.created, invoice.finalized, invoice.sent, invoice.overdue, credit_note.created, entitlements.active_entitlement_summary.updated, invoice.paid, invoice.payment_failed, invoice.upcoming, invoice.voided, invoice.marked_uncollectiblerequired | What happened. |
| modeone of: test, liverequired | test for a sandbox key, live for a live key. The key decides it; a key never reads the other mode's rows. |
| createdstring, ISO 8601 timerequired | When the event happened, ISO 8601 in UTC. |
| dataobjectrequired | The object the event is about. Its shape depends on type; see each event under Webhooks. |
Refund with its payment
Fields
| Field | Description |
|---|---|
| object"refund"required | Always "refund". |
| idstringrequired | The refund's id, re_ followed by 24 characters. |
| modeone of: test, liverequired | test for a sandbox key, live for a live key. The key decides it; a key never reads the other mode's rows. |
| paymentIdstringrequired | The payment it refunds. |
| statusone of: pending, processing, succeeded, failedrequired | pending when recorded, processing once sent to the processor, then succeeded or failed. The refund.succeeded or refund.failed webhook tells you which. |
| amountCentsintegerrequired | The amount refunded, in cents. |
| currency"LKR"required | Always "LKR". |
| reasonstring or nullrequired | The reason you gave, or null. |
| failureMessagestring or nullrequired | Why the refund failed, or null. |
| createdAtstring, ISO 8601 timerequired | When it was created, ISO 8601 in UTC. |
| succeededAtstring, ISO 8601 time or nullrequired | When the refund succeeded, or null. |
| paymentPaymentrequired | The payment the refund is for, as it stands after the refund. |
Expired checkout
Fields
| Field | Description |
|---|---|
| object"checkout"required | Always "checkout". |
| idstringrequired | The checkout's id. |
| paymentIdstringrequired | The id of the payment it would have taken, now canceled. |
Error
Every error, whatever the status.
Fields
| Field | Description |
|---|---|
| codeone of: VALIDATION_FAILED, UNAUTHENTICATED, FORBIDDEN, NOT_FOUND, CONFLICT, RATE_LIMITED, NOT_SUPPORTED, SERVICE_UNAVAILABLE, INTERNALrequired | A stable code for the kind of error. Match on this and on reason, never on the message. |
| reasonstring | The exact cause in snake_case, when there is a more precise one than the code, such as merchant_not_live. |
| messagestringrequired | A sentence you can show a person. It never carries internal detail. |
| docUrlstring, URLrequired | Where this error is explained, on https://payments.lk/developers/errors, anchored at its reason or its code. |
| traceIdstringrequired | Quote this when you contact support; it finds the request in our logs. |
| fieldsarray of object | For VALIDATION_FAILED: each field that was refused and why. |
| fields[].pathstringrequired | The field, as a dotted path such as customer.email. |
| fields[].messagestringrequired | Why it was refused. |
Webhooks
Add an endpoint in your dashboard under Developers: its URL, test or live, and the events it should receive. Its signing secret, starting whsec_, is shown once. There is no API for managing endpoints yet. Each event is sent as a POST of JSON, one event per request, to every active endpoint of the same mode that takes its type. On an endpoint’s page you can change its URL and events, turn it off, rotate its signing secret, and send it a test.ping event, which carries no payment and goes to that endpoint alone; each delivery shows the exact body sent and every attempt, and can be sent again.
{
"id": "evt_...",
"object": "event",
"type": "payment.succeeded",
"mode": "test",
"created": "2026-09-18T09:16:41.000Z",
"data": { ... }
}| Header | Value |
|---|---|
| Content-Type | application/json |
| User-Agent | Payments.lk-Webhooks/1.0 |
| Payments-Signature | t=<unix seconds>,v1=<signature>, with a second v1 while a rotated secret is kept. See Verifying signatures. |
| Payments-Event-Id | The event's id, the same as id in the body. |
Answer with any 2xx within 10 seconds and the delivery is done. Anything else, or no answer, is tried again after 1 minute, then 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours after each further failure; when the last try fails too, the delivery is marked failed. In production an endpoint must be an https URL on port 443 at a public address, an account holds up to ten endpoints in each mode, and redirects are not followed. A delivery can repeat and events can arrive out of order, so keep the event id and ignore one you have handled. To catch up on anything missed, read GET /v1/events, which lists the same events in the same shape.
Verifying signatures
The Payments-Signature header holds t, the time of signing in unix seconds, and v1, the hex HMAC-SHA256 of the text t + "." + body under the endpoint’s signing secret, where body is the raw request body exactly as received. To verify: compute the same HMAC, compare it with v1 in constant time, and refuse the delivery if t is more than 300 seconds from your clock. Verify before you parse the JSON; a parsed and re-serialised body will not match. After you rotate a signing secret and keep the old one for a while, the header carries a v1 for each secret until then, the new one last: accept the delivery if any of them matches, as the code below does.
import express from "express";
import { PaymentsLk } from "@payments-lk/node";
const lk = new PaymentsLk(process.env.PAYMENTS_LK_SECRET_KEY);
const app = express();
// express.raw keeps the exact bytes: the signature is over them, not over parsed JSON.
app.post("/webhooks/payments-lk", express.raw({ type: "application/json" }), async (req, res) => {
let event;
try {
event = lk.webhooks.constructEvent(req.body, req.headers["payments-signature"], process.env.PAYMENTS_LK_WEBHOOK_SECRET);
} catch {
return res.sendStatus(400); // not signed with your secret, or too old
}
if (await events.seen(event.id)) return res.sendStatus(200); // a delivery can repeat
if (event.type === "payment.succeeded") await orders.markPaid(event.data.reference, event.data.amountCents);
res.sendStatus(200);
});Events
Each event’s data is an object described under Objects. Every example is checked in our tests against the code that sends it.
payment.succeeded
The card was authorised and the payment succeeded. data is the payment, with its fee. Fulfil the order from this event.
{
"id": "evt_ev85ebwj9yqjq81vwf5qwz9m",
"object": "event",
"type": "payment.succeeded",
"mode": "test",
"created": "2026-09-18T09:16:41.000Z",
"data": {
"object": "payment",
"id": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"mode": "test",
"status": "succeeded",
"amountCents": 350000,
"currency": "LKR",
"description": "Two kottu and a milk tea",
"reference": "order-8891",
"customer": {
"name": "Nimali Perera",
"email": "[email protected]",
"phone": "0771234567"
},
"card": {
"scheme": "VISA",
"last4": "4242"
},
"cardSave": "not_requested",
"feeCents": 8015,
"netCents": 341985,
"refundedCents": 0,
"failureMessage": null,
"paymentLinkId": null,
"savedCardId": null,
"invoiceId": null,
"checkoutId": "chk_z6zf7gkx4fcwy3402kmwrm3w",
"checkoutPageId": null,
"order": null,
"delivery": null,
"customFields": null,
"customerTaxId": null,
"termsAcceptedAt": null,
"createdAt": "2026-09-18T09:14:05.000Z",
"succeededAt": "2026-09-18T09:16:41.000Z"
}
}payment.failed
The card was declined or the payment was not completed. data is the payment, with failureMessage.
{
"id": "evt_wxb2ycr0s580brjpes23aw67",
"object": "event",
"type": "payment.failed",
"mode": "test",
"created": "2026-09-17T18:42:30.000Z",
"data": {
"object": "payment",
"id": "pay_hj31hwy2mavtcbrfb2b2zz3n",
"mode": "test",
"status": "failed",
"amountCents": 520000,
"currency": "LKR",
"description": "Lamprais for four",
"reference": "order-8874",
"customer": {
"name": "Nimali Perera",
"email": "[email protected]",
"phone": "0771234567"
},
"card": {
"scheme": "MASTERCARD",
"last4": "5100"
},
"cardSave": "not_requested",
"feeCents": null,
"netCents": null,
"refundedCents": 0,
"failureMessage": "The card was declined.",
"paymentLinkId": null,
"savedCardId": null,
"invoiceId": null,
"checkoutId": "chk_4ne3cpz1r2a8g5vq7xk0m9dt",
"checkoutPageId": null,
"order": null,
"delivery": null,
"customFields": null,
"customerTaxId": null,
"termsAcceptedAt": null,
"createdAt": "2026-09-17T18:40:12.000Z",
"succeededAt": null
}
}checkout.expired
Nobody paid the checkout before it expired. data names the checkout and its payment, which is now canceled.
{
"id": "evt_r0my8wv91rgsx5fjb848aqpp",
"object": "event",
"type": "checkout.expired",
"mode": "test",
"created": "2026-09-18T10:31:00.000Z",
"data": {
"object": "checkout",
"id": "chk_wnqa1fax67k19rt7td9cc3j2",
"paymentId": "pay_xsrps207y12k0c1y5f85jbvz"
}
}card.saved
The customer kept their card on file. data is the saved card; keep its id to charge it later.
{
"id": "evt_w2fx9rtd0sa7fx2kt2vhbh9a",
"object": "event",
"type": "card.saved",
"mode": "test",
"created": "2026-09-02T07:30:44.000Z",
"data": {
"object": "saved_card",
"id": "card_g2a1rex14ff7qh2dc6j6qzmq",
"mode": "test",
"customerEmail": "[email protected]",
"scheme": "VISA",
"last4": "4242",
"expiry": {
"month": 11,
"year": 2029
},
"paymentId": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"consent": {
"agreedAt": "2026-09-02T07:30:41.000Z",
"locale": "en",
"wording": "checkoutPage.saveCard@1"
},
"active": true,
"createdAt": "2026-09-02T07:30:44.000Z",
"retiredAt": null
}
}card.save_failed
A payment that was to keep the customer's card succeeded, but the card was not kept: Payable did not tokenise it, or its answer carried no token. data is the payment, whose cardSave is failed; no card.saved follows. Ask the customer to add the card again. A subscription it was for, left with no card, shows cardMissingSince, and its renewals are emailed to the customer to pay, with a link to add a card.
{
"id": "evt_c8f3v1x6q9m2k5r7t0ynhw4z",
"object": "event",
"type": "card.save_failed",
"mode": "test",
"created": "2026-09-18T09:16:41.000Z",
"data": {
"object": "payment",
"id": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"mode": "test",
"status": "succeeded",
"amountCents": 350000,
"currency": "LKR",
"description": "Two kottu and a milk tea",
"reference": "order-8891",
"customer": {
"name": "Nimali Perera",
"email": "[email protected]",
"phone": "0771234567"
},
"card": {
"scheme": "VISA",
"last4": "4242"
},
"cardSave": "failed",
"feeCents": 8015,
"netCents": 341985,
"refundedCents": 0,
"failureMessage": null,
"paymentLinkId": null,
"savedCardId": null,
"invoiceId": null,
"checkoutId": "chk_z6zf7gkx4fcwy3402kmwrm3w",
"checkoutPageId": null,
"order": null,
"delivery": null,
"customFields": null,
"customerTaxId": null,
"termsAcceptedAt": null,
"createdAt": "2026-09-18T09:14:05.000Z",
"succeededAt": "2026-09-18T09:16:41.000Z"
}
}card.expiring
A saved card that a running subscription is charged to expires at the end of this month. data is the saved card; the customer is emailed a link to the customer portal to change it.
{
"id": "evt_c7x2v9k1q4m8e3r6t0ynhz5w",
"object": "event",
"type": "card.expiring",
"mode": "test",
"created": "2026-11-15T09:30:00.000Z",
"data": {
"object": "saved_card",
"id": "card_g2a1rex14ff7qh2dc6j6qzmq",
"mode": "test",
"customerEmail": "[email protected]",
"scheme": "VISA",
"last4": "4242",
"expiry": {
"month": 11,
"year": 2029
},
"paymentId": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"consent": {
"agreedAt": "2026-09-02T07:30:41.000Z",
"locale": "en",
"wording": "checkoutPage.saveCard@1"
},
"active": true,
"createdAt": "2026-09-02T07:30:44.000Z",
"retiredAt": null
}
}billing.alert.triggered
A customer's usage on a meter in their current period reached a usage alert's threshold. data is the alert, the customer and the usage; it is sent once per alert and customer.
{
"id": "evt_b8a2v7x1c9q4m3k6r0tnhw5z",
"object": "event",
"type": "billing.alert.triggered",
"mode": "test",
"created": "2026-11-15T09:30:00.000Z",
"data": {
"object": "usage_alert_trigger",
"alertId": "ual_t3v9x1c7q2m8k4r6y0wnhz5p",
"title": "10,000 API calls",
"meterId": "mtr_a4c8v2x9q1m7k3r6t0yhwn5z",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"threshold": 10000,
"value": 10012,
"triggeredAt": "2026-10-18T06:40:12.000Z"
}
}subscription_schedule.created
A subscription schedule was made. data is the schedule; its subscription starts on startDate.
{
"id": "evt_d1s8v3x7c2q9m4k6r0tnhw5y",
"object": "event",
"type": "subscription_schedule.created",
"mode": "test",
"created": "2026-11-15T09:30:00.000Z",
"data": {
"object": "subscription_schedule",
"id": "sched_c6v2x9q1m8k4r7t3y0hwnz5p",
"mode": "test",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"status": "active",
"endBehavior": "cancel",
"startDate": "2026-10-01T04:12:31.000Z",
"phases": [
{
"items": [
{
"priceId": "price_i7c3v9x2q8m1k6r4t0ynwh5z",
"quantity": 1
}
],
"iterations": 12,
"trialDays": null,
"couponId": null
}
],
"currentPhase": 0,
"currentPhaseEndsAt": "2027-10-01T04:12:31.000Z",
"reference": "Diploma, 12 installments",
"completedAt": null,
"releasedAt": null,
"canceledAt": null,
"createdAt": "2026-10-01T04:12:31.000Z"
}
}subscription_schedule.completed
A schedule's last phase ended and, as its endBehavior is cancel, so did its subscription. data is the schedule.
{
"id": "evt_d2s9v4x8c3q1m5k7r0ynhw6z",
"object": "event",
"type": "subscription_schedule.completed",
"mode": "test",
"created": "2026-11-15T09:30:00.000Z",
"data": {
"object": "subscription_schedule",
"id": "sched_c6v2x9q1m8k4r7t3y0hwnz5p",
"mode": "test",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"status": "completed",
"endBehavior": "cancel",
"startDate": "2026-10-01T04:12:31.000Z",
"phases": [
{
"items": [
{
"priceId": "price_i7c3v9x2q8m1k6r4t0ynwh5z",
"quantity": 1
}
],
"iterations": 12,
"trialDays": null,
"couponId": null
}
],
"currentPhase": 0,
"currentPhaseEndsAt": null,
"reference": "Diploma, 12 installments",
"completedAt": "2027-10-01T04:12:31.000Z",
"releasedAt": null,
"canceledAt": null,
"createdAt": "2026-10-01T04:12:31.000Z"
}
}subscription_schedule.released
A schedule let go of its subscription, which carries on as it is: after its last phase with endBehavior release, or released by you. data is the schedule.
{
"id": "evt_d3s1v5x9c4q2m6k8r0hwnz7y",
"object": "event",
"type": "subscription_schedule.released",
"mode": "test",
"created": "2026-11-15T09:30:00.000Z",
"data": {
"object": "subscription_schedule",
"id": "sched_c6v2x9q1m8k4r7t3y0hwnz5p",
"mode": "test",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"status": "released",
"endBehavior": "release",
"startDate": "2026-10-01T04:12:31.000Z",
"phases": [
{
"items": [
{
"priceId": "price_i7c3v9x2q8m1k6r4t0ynwh5z",
"quantity": 1
}
],
"iterations": 12,
"trialDays": null,
"couponId": null
}
],
"currentPhase": 0,
"currentPhaseEndsAt": null,
"reference": "Diploma, 12 installments",
"completedAt": null,
"releasedAt": "2027-10-01T04:12:31.000Z",
"canceledAt": null,
"createdAt": "2026-10-01T04:12:31.000Z"
}
}subscription_schedule.canceled
A schedule was canceled, by you or because its subscription ended some other way. data is the schedule.
{
"id": "evt_d4s2v6x1c5q3m7k9r0wnhy8z",
"object": "event",
"type": "subscription_schedule.canceled",
"mode": "test",
"created": "2026-11-15T09:30:00.000Z",
"data": {
"object": "subscription_schedule",
"id": "sched_c6v2x9q1m8k4r7t3y0hwnz5p",
"mode": "test",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"status": "canceled",
"endBehavior": "cancel",
"startDate": "2026-10-01T04:12:31.000Z",
"phases": [
{
"items": [
{
"priceId": "price_i7c3v9x2q8m1k6r4t0ynwh5z",
"quantity": 1
}
],
"iterations": 12,
"trialDays": null,
"couponId": null
}
],
"currentPhase": 0,
"currentPhaseEndsAt": null,
"reference": "Diploma, 12 installments",
"completedAt": null,
"releasedAt": null,
"canceledAt": "2027-02-01T04:12:31.000Z",
"createdAt": "2026-10-01T04:12:31.000Z"
}
}refund.succeeded
The refund went through. data is the refund, with the payment as it stands after it.
{
"id": "evt_np6d7c5k65jryr0jdgmavejf",
"object": "event",
"type": "refund.succeeded",
"mode": "test",
"created": "2026-09-19T10:05:52.000Z",
"data": {
"object": "refund",
"id": "re_sbca5jeftvf7acqsx5rvdzk3",
"mode": "test",
"paymentId": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"status": "succeeded",
"amountCents": 50000,
"currency": "LKR",
"reason": "The milk tea was not available",
"failureMessage": null,
"createdAt": "2026-09-19T10:02:17.000Z",
"succeededAt": "2026-09-19T10:05:52.000Z",
"payment": {
"object": "payment",
"id": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"mode": "test",
"status": "partially_refunded",
"amountCents": 350000,
"currency": "LKR",
"description": "Two kottu and a milk tea",
"reference": "order-8891",
"customer": {
"name": "Nimali Perera",
"email": "[email protected]",
"phone": "0771234567"
},
"card": {
"scheme": "VISA",
"last4": "4242"
},
"cardSave": "not_requested",
"feeCents": 8015,
"netCents": 341985,
"refundedCents": 50000,
"failureMessage": null,
"paymentLinkId": null,
"savedCardId": null,
"invoiceId": null,
"checkoutId": null,
"checkoutPageId": null,
"order": null,
"delivery": null,
"customFields": null,
"customerTaxId": null,
"termsAcceptedAt": null,
"createdAt": "2026-09-18T09:14:05.000Z",
"succeededAt": "2026-09-18T09:16:41.000Z"
}
}
}refund.failed
The refund could not be made. data is the refund, with failureMessage, and the payment it was for.
{
"id": "evt_v2z2cg8qe8w2mcqxbs7s7btd",
"object": "event",
"type": "refund.failed",
"mode": "test",
"created": "2026-09-19T10:05:52.000Z",
"data": {
"object": "refund",
"id": "re_sbca5jeftvf7acqsx5rvdzk3",
"mode": "test",
"paymentId": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"status": "failed",
"amountCents": 50000,
"currency": "LKR",
"reason": "The milk tea was not available",
"failureMessage": "The refund could not be processed.",
"createdAt": "2026-09-19T10:02:17.000Z",
"succeededAt": null,
"payment": {
"object": "payment",
"id": "pay_x1n31wnp8nkbjkkhymtqd6zs",
"mode": "test",
"status": "succeeded",
"amountCents": 350000,
"currency": "LKR",
"description": "Two kottu and a milk tea",
"reference": "order-8891",
"customer": {
"name": "Nimali Perera",
"email": "[email protected]",
"phone": "0771234567"
},
"card": {
"scheme": "VISA",
"last4": "4242"
},
"cardSave": "not_requested",
"feeCents": 8015,
"netCents": 341985,
"refundedCents": 0,
"failureMessage": null,
"paymentLinkId": null,
"savedCardId": null,
"invoiceId": null,
"checkoutId": null,
"checkoutPageId": null,
"order": null,
"delivery": null,
"customFields": null,
"customerTaxId": null,
"termsAcceptedAt": null,
"createdAt": "2026-09-18T09:14:05.000Z",
"succeededAt": "2026-09-18T09:16:41.000Z"
}
}
}account.limit.approaching
This month's live card payments reached 60, 70, 80 or 90 percent of your plan's monthly limit; threshold says which. Sent once a month per threshold. data is the month's usage, with projectedAt when the pace says when the limit will be reached.
{
"id": "evt_k7m2q9x4v1c8r3t6y0wz5hnd",
"object": "event",
"type": "account.limit.approaching",
"mode": "live",
"created": "2026-09-19T11:40:00.000Z",
"data": {
"object": "account_usage",
"month": "2026-09",
"plan": "starter",
"capCents": 10000000,
"settledCents": 8120000,
"percent": 81,
"threshold": 80,
"refusedCount": 0,
"refusedCents": 0,
"resetsAt": "2026-09-30T18:30:00.000Z",
"projectedAt": "2026-09-24T09:12:00.000Z"
}
}account.limit.reached
Your plan's monthly limit now stands in the way of live card payments: the month reached it, or a payment that would pass it was refused. Payments that would pass the limit are refused until you upgrade or the month resets on the 1st. data is the month's usage.
{
"id": "evt_p3v8n1x6c9m2q5r7t0yk4hwz",
"object": "event",
"type": "account.limit.reached",
"mode": "live",
"created": "2026-09-24T09:15:00.000Z",
"data": {
"object": "account_usage",
"month": "2026-09",
"plan": "starter",
"capCents": 10000000,
"settledCents": 9750000,
"percent": 97,
"threshold": 100,
"refusedCount": 1,
"refusedCents": 450000,
"resetsAt": "2026-09-30T18:30:00.000Z",
"projectedAt": null
}
}account.limit.reset
A new month began after one that reached the limit. data is the finished month's usage, with how many payments the limit refused and what they came to.
{
"id": "evt_z9c4m7q2x8v1n5r3t6yh0kwp",
"object": "event",
"type": "account.limit.reset",
"mode": "live",
"created": "2026-09-30T18:31:00.000Z",
"data": {
"object": "account_usage",
"month": "2026-09",
"plan": "starter",
"capCents": 10000000,
"settledCents": 9750000,
"percent": 97,
"threshold": 100,
"refusedCount": 3,
"refusedCents": 1280000,
"resetsAt": "2026-09-30T18:30:00.000Z",
"projectedAt": null
}
}customer.subscription.created
A subscription was made. It is incomplete until its first invoice is paid. data is the subscription.
{
"id": "evt_s1c8v2x7q4m9k3r6t0yhwn5z",
"object": "event",
"type": "customer.subscription.created",
"mode": "test",
"created": "2026-10-01T04:12:31.000Z",
"data": {
"object": "subscription",
"id": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"status": "incomplete",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1,
"items": [
{
"id": "si_t4c9v2x7q1m6k8r3y0wnhz5p",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1
}
],
"currentPeriodStart": "2026-10-01T04:12:31.000Z",
"currentPeriodEnd": "2026-11-01T04:12:31.000Z",
"billingCycleAnchor": "2026-10-01T04:12:31.000Z",
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancelReason": null,
"cancellationDetails": {
"reason": null,
"feedback": null,
"comment": null
},
"trialStart": null,
"trialEnd": null,
"trialEndBehavior": "create_invoice",
"pauseCollection": null,
"discount": null,
"taxRateIds": [],
"subscriptionLinkId": null,
"collectionMethod": "charge_automatically",
"daysUntilDue": null,
"pendingUpdate": null,
"cardId": null,
"cardMissingSince": null,
"latestInvoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"checkoutUrl": "https://payments.lk/checkout/chk_r8v2x6c1q9m4k7t3y0wnzh5p",
"reference": "member-1042",
"createdAt": "2026-10-01T04:12:31.000Z"
}
}customer.subscription.updated
A subscription changed: it turned active, past_due or unpaid, moved to its next period, was set to cancel at period end, or expired unpaid. data is the subscription as it stands.
{
"id": "evt_s2v9x3c8q1m6k4r7t0ynhw5z",
"object": "event",
"type": "customer.subscription.updated",
"mode": "test",
"created": "2026-10-01T04:15:02.000Z",
"data": {
"object": "subscription",
"id": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"status": "active",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1,
"items": [
{
"id": "si_t4c9v2x7q1m6k8r3y0wnhz5p",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1
}
],
"currentPeriodStart": "2026-10-01T04:12:31.000Z",
"currentPeriodEnd": "2026-11-01T04:12:31.000Z",
"billingCycleAnchor": "2026-10-01T04:12:31.000Z",
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancelReason": null,
"cancellationDetails": {
"reason": null,
"feedback": null,
"comment": null
},
"trialStart": null,
"trialEnd": null,
"trialEndBehavior": "create_invoice",
"pauseCollection": null,
"discount": null,
"taxRateIds": [],
"subscriptionLinkId": null,
"collectionMethod": "charge_automatically",
"daysUntilDue": null,
"pendingUpdate": null,
"cardId": "card_g2a1rex14ff7qh2dc6j6qzmq",
"cardMissingSince": null,
"latestInvoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"checkoutUrl": null,
"reference": "member-1042",
"createdAt": "2026-10-01T04:12:31.000Z"
}
}customer.subscription.deleted
A subscription ended: canceled now, at the end of its period, or for non-payment after the last retry and the grace days. data is the subscription. Revoke access.
{
"id": "evt_s3x1v6c9q2m8k5r4t0zyhw7n",
"object": "event",
"type": "customer.subscription.deleted",
"mode": "test",
"created": "2026-10-20T08:00:00.000Z",
"data": {
"object": "subscription",
"id": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"status": "canceled",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1,
"items": [
{
"id": "si_t4c9v2x7q1m6k8r3y0wnhz5p",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1
}
],
"currentPeriodStart": "2026-10-01T04:12:31.000Z",
"currentPeriodEnd": "2026-11-01T04:12:31.000Z",
"billingCycleAnchor": "2026-10-01T04:12:31.000Z",
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": "2026-10-20T08:00:00.000Z",
"endedAt": "2026-10-20T08:00:00.000Z",
"cancelReason": "requested_by_merchant",
"cancellationDetails": {
"reason": "requested_by_merchant",
"feedback": "too_expensive",
"comment": "Moving to the branch closer to home."
},
"trialStart": null,
"trialEnd": null,
"trialEndBehavior": "create_invoice",
"pauseCollection": null,
"discount": null,
"taxRateIds": [],
"subscriptionLinkId": null,
"collectionMethod": "charge_automatically",
"daysUntilDue": null,
"pendingUpdate": null,
"cardId": "card_g2a1rex14ff7qh2dc6j6qzmq",
"cardMissingSince": null,
"latestInvoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"checkoutUrl": null,
"reference": "member-1042",
"createdAt": "2026-10-01T04:12:31.000Z"
}
}customer.subscription.trial_will_end
A subscription's trial ends in three days. The customer has been emailed what happens next. data is the subscription.
{
"id": "evt_s4t8v1x2c9q3m7k5r0yhwn6z",
"object": "event",
"type": "customer.subscription.trial_will_end",
"mode": "test",
"created": "2026-11-15T09:30:00.000Z",
"data": {
"object": "subscription",
"id": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"status": "trialing",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1,
"items": [
{
"id": "si_t4c9v2x7q1m6k8r3y0wnhz5p",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1
}
],
"currentPeriodStart": "2026-10-01T04:12:31.000Z",
"currentPeriodEnd": "2026-10-15T04:12:31.000Z",
"billingCycleAnchor": "2026-10-01T04:12:31.000Z",
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancelReason": null,
"cancellationDetails": {
"reason": null,
"feedback": null,
"comment": null
},
"trialStart": "2026-10-01T04:12:31.000Z",
"trialEnd": "2026-10-15T04:12:31.000Z",
"trialEndBehavior": "create_invoice",
"pauseCollection": null,
"discount": null,
"taxRateIds": [],
"subscriptionLinkId": null,
"collectionMethod": "charge_automatically",
"daysUntilDue": null,
"pendingUpdate": null,
"cardId": "card_g2a1rex14ff7qh2dc6j6qzmq",
"cardMissingSince": null,
"latestInvoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"checkoutUrl": null,
"reference": "member-1042",
"createdAt": "2026-10-01T04:12:31.000Z"
}
}customer.subscription.paused
A subscription was paused because its trial ended without a card on file and you chose to pause such subscriptions. Nothing is billed until it is resumed. data is the subscription.
{
"id": "evt_s5p2v7x1c8q4m9k3r0zyhw6n",
"object": "event",
"type": "customer.subscription.paused",
"mode": "test",
"created": "2026-11-15T09:30:00.000Z",
"data": {
"object": "subscription",
"id": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"status": "paused",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1,
"items": [
{
"id": "si_t4c9v2x7q1m6k8r3y0wnhz5p",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1
}
],
"currentPeriodStart": "2026-10-01T04:12:31.000Z",
"currentPeriodEnd": "2026-10-15T04:12:31.000Z",
"billingCycleAnchor": "2026-10-01T04:12:31.000Z",
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancelReason": null,
"cancellationDetails": {
"reason": null,
"feedback": null,
"comment": null
},
"trialStart": "2026-10-01T04:12:31.000Z",
"trialEnd": "2026-10-15T04:12:31.000Z",
"trialEndBehavior": "create_invoice",
"pauseCollection": null,
"discount": null,
"taxRateIds": [],
"subscriptionLinkId": null,
"collectionMethod": "charge_automatically",
"daysUntilDue": null,
"pendingUpdate": null,
"cardId": null,
"cardMissingSince": null,
"latestInvoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"checkoutUrl": null,
"reference": "member-1042",
"createdAt": "2026-10-01T04:12:31.000Z"
}
}customer.subscription.resumed
A paused subscription was resumed: a new period started and its invoice is charged to the card now on file. data is the subscription.
{
"id": "evt_s6r1v8x3c2q7m4k9t0nwhz5y",
"object": "event",
"type": "customer.subscription.resumed",
"mode": "test",
"created": "2026-11-15T09:30:00.000Z",
"data": {
"object": "subscription",
"id": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"status": "active",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1,
"items": [
{
"id": "si_t4c9v2x7q1m6k8r3y0wnhz5p",
"price": {
"id": "price_m3v8q1x6c9k2r5t7y0wn4hzp",
"productName": "Gym membership",
"unitAmountCents": 450000,
"interval": "month",
"intervalCount": 1
},
"quantity": 1
}
],
"currentPeriodStart": "2026-10-01T04:12:31.000Z",
"currentPeriodEnd": "2026-11-01T04:12:31.000Z",
"billingCycleAnchor": "2026-10-01T04:12:31.000Z",
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancelReason": null,
"cancellationDetails": {
"reason": null,
"feedback": null,
"comment": null
},
"trialStart": null,
"trialEnd": null,
"trialEndBehavior": "create_invoice",
"pauseCollection": null,
"discount": null,
"taxRateIds": [],
"subscriptionLinkId": null,
"collectionMethod": "charge_automatically",
"daysUntilDue": null,
"pendingUpdate": null,
"cardId": "card_g2a1rex14ff7qh2dc6j6qzmq",
"cardMissingSince": null,
"latestInvoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"checkoutUrl": null,
"reference": "member-1042",
"createdAt": "2026-10-01T04:12:31.000Z"
}
}invoice.created
An invoice was made: a subscription's first, or a renewal's draft, which is finalised and charged after the draft window. data is the invoice.
{
"id": "evt_i1c9v3x8q2m7k4r6t0ywnh5z",
"object": "event",
"type": "invoice.created",
"mode": "test",
"created": "2026-11-01T04:13:00.000Z",
"data": {
"object": "invoice",
"id": "in_x7c2v9q4m1k6r8t3y0hwnzp5",
"mode": "test",
"number": null,
"status": "draft",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_cycle",
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"lines": [
{
"description": "Gym membership, every month",
"quantity": 1,
"unitAmountCents": 450000,
"amountCents": 450000,
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"proration": false
}
],
"subtotalCents": 450000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 450000,
"creditAppliedCents": 0,
"amountDueCents": 450000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 0,
"nextAttemptAt": null,
"lastFailure": null,
"paidAt": null,
"hostedInvoiceUrl": null,
"createdAt": "2026-11-01T04:13:00.000Z"
}
}invoice.finalized
An invoice was numbered and opened for payment. data is the invoice.
{
"id": "evt_i2v8x1c7q3m9k6r4t0yzhw5n",
"object": "event",
"type": "invoice.finalized",
"mode": "test",
"created": "2026-11-01T05:13:00.000Z",
"data": {
"object": "invoice",
"id": "in_x7c2v9q4m1k6r8t3y0hwnzp5",
"mode": "test",
"number": "INV-000002",
"status": "open",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_cycle",
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"lines": [
{
"description": "Gym membership, every month",
"quantity": 1,
"unitAmountCents": 450000,
"amountCents": 450000,
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"proration": false
}
],
"subtotalCents": 450000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 450000,
"creditAppliedCents": 0,
"amountDueCents": 450000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 0,
"nextAttemptAt": "2026-11-01T05:13:00.000Z",
"lastFailure": null,
"paidAt": null,
"hostedInvoiceUrl": "https://payments.lk/invoice/b3Nq8Zt2Xv6Lm1Kc9Rw4Hy7Pd5Gs0Jf2Ae4Uo7Ik9Mx",
"createdAt": "2026-11-01T04:13:00.000Z"
}
}invoice.sent
An invoice collected by sending it was emailed to the customer, to pay on its page by its due date. data is the invoice.
{
"id": "evt_i8s3v1x7c2q9m4k6r0ynhw5z",
"object": "event",
"type": "invoice.sent",
"mode": "test",
"created": "2026-11-15T09:30:00.000Z",
"data": {
"object": "invoice",
"id": "in_s5v1x8c3q9m2k7r4t0yhwn6z",
"mode": "test",
"number": "INV-000014",
"status": "open",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": null,
"billingReason": "manual",
"periodStart": null,
"periodEnd": null,
"lines": [
{
"description": "Grade 10 maths, third term",
"quantity": 1,
"unitAmountCents": 1800000,
"amountCents": 1800000,
"periodStart": null,
"periodEnd": null,
"proration": false
}
],
"subtotalCents": 1800000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 1800000,
"creditAppliedCents": 0,
"amountDueCents": 1800000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "send_invoice",
"dueDate": "2026-10-31T18:29:59.000Z",
"memo": "Third term, grade 10",
"currency": "LKR",
"attemptCount": 0,
"nextAttemptAt": null,
"lastFailure": null,
"paidAt": null,
"hostedInvoiceUrl": "https://payments.lk/invoice/r2Vd9x4Kc7mZt3Wn1Rb8Hy6Lp5Gs0Jf4Ae2Uo9Ik7Nx",
"createdAt": "2026-10-01T08:00:00.000Z"
}
}invoice.overdue
An invoice sent to be paid by a due date was not paid by then. Its subscription, if any, is past due. data is the invoice.
{
"id": "evt_i9o4v2x8c3q1m5k7r0hwnz6y",
"object": "event",
"type": "invoice.overdue",
"mode": "test",
"created": "2026-11-15T09:30:00.000Z",
"data": {
"object": "invoice",
"id": "in_s5v1x8c3q9m2k7r4t0yhwn6z",
"mode": "test",
"number": "INV-000014",
"status": "open",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": null,
"billingReason": "manual",
"periodStart": null,
"periodEnd": null,
"lines": [
{
"description": "Grade 10 maths, third term",
"quantity": 1,
"unitAmountCents": 1800000,
"amountCents": 1800000,
"periodStart": null,
"periodEnd": null,
"proration": false
}
],
"subtotalCents": 1800000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 1800000,
"creditAppliedCents": 0,
"amountDueCents": 1800000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "send_invoice",
"dueDate": "2026-10-31T18:29:59.000Z",
"memo": "Third term, grade 10",
"currency": "LKR",
"attemptCount": 0,
"nextAttemptAt": null,
"lastFailure": null,
"paidAt": null,
"hostedInvoiceUrl": "https://payments.lk/invoice/r2Vd9x4Kc7mZt3Wn1Rb8Hy6Lp5Gs0Jf4Ae2Uo9Ik7Nx",
"createdAt": "2026-10-01T08:00:00.000Z"
}
}credit_note.created
A credit note was issued: an amount taken off an invoice, off what was due, refunded to the card, or added to the customer's credit balance. data is the credit note.
{
"id": "evt_c9n5v3x9c4q2m6k8r0wnhy7z",
"object": "event",
"type": "credit_note.created",
"mode": "test",
"created": "2026-11-15T09:30:00.000Z",
"data": {
"object": "credit_note",
"id": "cn_r7v2x9c4q1m8k3t6y0wnhz5p",
"mode": "test",
"invoiceId": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"number": "CN-000001",
"amountCents": 150000,
"reason": "order_change",
"memo": "Closed for a week in October",
"amountDueReducedCents": 0,
"refundedCents": 0,
"creditedToBalanceCents": 150000,
"refundId": null,
"createdAt": "2026-10-20T08:00:00.000Z"
}
}entitlements.active_entitlement_summary.updated
A customer's features changed: a subscription started, changed, paused or ended, or a product gained or lost a feature. data is every feature the customer has now.
{
"id": "evt_e1n6v4x1c5q3m7k9r0hwnz8y",
"object": "event",
"type": "entitlements.active_entitlement_summary.updated",
"mode": "test",
"created": "2026-11-15T09:30:00.000Z",
"data": {
"object": "entitlements.active_entitlement_summary",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"entitlements": [
{
"featureId": "feat_g2v7x9c5q1m8k3r6t0hwnz4y",
"lookupKey": "gym"
},
{
"featureId": "feat_p3v8x1c6q9m2k7r4t0ynhw5z",
"lookupKey": "pool"
}
]
}
}invoice.paid
An invoice was paid. Provision for the period it covers from this event. data is the invoice.
{
"id": "evt_i3x2v9c8q1m4k7r6t0hwyn5z",
"object": "event",
"type": "invoice.paid",
"mode": "test",
"created": "2026-10-01T04:15:02.000Z",
"data": {
"object": "invoice",
"id": "in_c4v9x2q7m1k8r3t6y0wnzhp5",
"mode": "test",
"number": "INV-000001",
"status": "paid",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_create",
"periodStart": "2026-10-01T04:12:31.000Z",
"periodEnd": "2026-11-01T04:12:31.000Z",
"lines": [
{
"description": "Gym membership, every month",
"quantity": 1,
"unitAmountCents": 450000,
"amountCents": 450000,
"periodStart": "2026-10-01T04:12:31.000Z",
"periodEnd": "2026-11-01T04:12:31.000Z",
"proration": false
}
],
"subtotalCents": 450000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 450000,
"creditAppliedCents": 0,
"amountDueCents": 450000,
"amountPaidCents": 450000,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 0,
"nextAttemptAt": null,
"lastFailure": null,
"paidAt": "2026-10-01T04:15:02.000Z",
"hostedInvoiceUrl": "https://payments.lk/invoice/q7Vd2x9Kc4mZt1Wn8Rb5Hy3Lp6Gs0Jf2Ae4Uo7Ik9Nx",
"createdAt": "2026-10-01T04:12:31.000Z"
}
}invoice.payment_failed
A charge of the card on file failed, or there was no card to charge. data is the invoice, with attemptCount, lastFailure and nextAttemptAt (null when nothing more will be tried). The customer is emailed a pay link.
{
"id": "evt_i4c1v8x9q2m3k6r7t0nwhz5y",
"object": "event",
"type": "invoice.payment_failed",
"mode": "test",
"created": "2026-11-01T05:13:04.000Z",
"data": {
"object": "invoice",
"id": "in_x7c2v9q4m1k6r8t3y0hwnzp5",
"mode": "test",
"number": "INV-000002",
"status": "open",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_cycle",
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"lines": [
{
"description": "Gym membership, every month",
"quantity": 1,
"unitAmountCents": 450000,
"amountCents": 450000,
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"proration": false
}
],
"subtotalCents": 450000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 450000,
"creditAppliedCents": 0,
"amountDueCents": 450000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 1,
"nextAttemptAt": "2026-11-02T03:30:00.000Z",
"lastFailure": "The processor refused the charge.",
"paidAt": null,
"hostedInvoiceUrl": "https://payments.lk/invoice/b3Nq8Zt2Xv6Lm1Kc9Rw4Hy7Pd5Gs0Jf2Ae4Uo7Ik9Mx",
"createdAt": "2026-11-01T04:13:00.000Z"
}
}invoice.upcoming
A renewal is a few days away (three unless set otherwise). data is a preview of the invoice it will make; its id is not an invoice's.
{
"id": "evt_i5v2x8c1q9m4k3r6t0yhzw7n",
"object": "event",
"type": "invoice.upcoming",
"mode": "test",
"created": "2026-10-29T04:13:00.000Z",
"data": {
"object": "invoice",
"id": "upcoming_sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"mode": "test",
"number": null,
"status": "draft",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_cycle",
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"lines": [
{
"description": "Gym membership, every month",
"quantity": 1,
"unitAmountCents": 450000,
"amountCents": 450000,
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"proration": false
}
],
"subtotalCents": 450000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 450000,
"creditAppliedCents": 0,
"amountDueCents": 450000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 0,
"nextAttemptAt": null,
"lastFailure": null,
"paidAt": null,
"hostedInvoiceUrl": null,
"createdAt": "2026-10-29T04:13:00.000Z"
}
}invoice.voided
An invoice was voided, because its subscription ended or expired before it was paid. data is the invoice.
{
"id": "evt_i6x9v1c2q8m3k7r4t0zwyh5n",
"object": "event",
"type": "invoice.voided",
"mode": "test",
"created": "2026-10-20T08:00:00.000Z",
"data": {
"object": "invoice",
"id": "in_x7c2v9q4m1k6r8t3y0hwnzp5",
"mode": "test",
"number": "INV-000002",
"status": "void",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_cycle",
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"lines": [
{
"description": "Gym membership, every month",
"quantity": 1,
"unitAmountCents": 450000,
"amountCents": 450000,
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"proration": false
}
],
"subtotalCents": 450000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 450000,
"creditAppliedCents": 0,
"amountDueCents": 450000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 1,
"nextAttemptAt": null,
"lastFailure": "The processor refused the charge.",
"paidAt": null,
"hostedInvoiceUrl": "https://payments.lk/invoice/b3Nq8Zt2Xv6Lm1Kc9Rw4Hy7Pd5Gs0Jf2Ae4Uo7Ik9Mx",
"createdAt": "2026-11-01T04:13:00.000Z"
}
}invoice.marked_uncollectible
An invoice was written off: marked uncollectible by you, or when a subscription was canceled for non payment. It is never tried again. data is the invoice.
{
"id": "evt_i7c3v1x9q8m2k5r4t0nhyw6z",
"object": "event",
"type": "invoice.marked_uncollectible",
"mode": "test",
"created": "2026-11-15T09:30:00.000Z",
"data": {
"object": "invoice",
"id": "in_x7c2v9q4m1k6r8t3y0hwnzp5",
"mode": "test",
"number": "INV-000002",
"status": "uncollectible",
"customerId": "cus_q8w3n5v1x7c2m9r4t6ya0kzp",
"subscriptionId": "sub_p6x1c8v3q9m2k7r4t0yhwz5n",
"billingReason": "subscription_cycle",
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"lines": [
{
"description": "Gym membership, every month",
"quantity": 1,
"unitAmountCents": 450000,
"amountCents": 450000,
"periodStart": "2026-11-01T04:12:31.000Z",
"periodEnd": "2026-12-01T04:12:31.000Z",
"proration": false
}
],
"subtotalCents": 450000,
"discountCents": 0,
"couponId": null,
"taxCents": 0,
"taxes": [],
"totalCents": 450000,
"creditAppliedCents": 0,
"amountDueCents": 450000,
"amountPaidCents": 0,
"creditNotesCents": 0,
"collectionMethod": "charge_automatically",
"dueDate": null,
"memo": null,
"currency": "LKR",
"attemptCount": 1,
"nextAttemptAt": null,
"lastFailure": "The processor refused the charge.",
"paidAt": null,
"hostedInvoiceUrl": "https://payments.lk/invoice/b3Nq8Zt2Xv6Lm1Kc9Rw4Hy7Pd5Gs0Jf2Ae4Uo7Ik9Mx",
"createdAt": "2026-11-01T04:13:00.000Z"
}
}