Developers / Errors
Every error, and what to do about it.
When the API refuses a request it says which rule it hit, in a code and a reason your code can match, a sentence a person can read, and a link to the entry on this page that explains it. This page is built from the same list the API reads.
The error body
Every error is JSON of the same shape, with the HTTP status on the response.
- code
- One of the codes below. Stable: match on it.
- reason
- The precise reason, in snake case, when there is one. Stable: match on it before the code. Absent when the code says it all.
- message
- A sentence written for a person. It may be reworded, so show it, but never match on it.
- docUrl
- This page, at the anchor for the reason, or for the code when there is no reason.
- traceId
- The id of the request in our logs. Quote it when you write to us. The x-trace-id header carries the same id.
- fields
- Validation failures only: the path of each field that was refused and what is wrong with it.
{
"code": "CONFLICT",
"reason": "merchant_not_live",
"message": "Live payments open once the account is approved and activated.",
"docUrl": "https://payments.lk/developers/errors#merchant_not_live",
"traceId": "4f1c2d7e-9a0b-4c3d-8e5f-6a7b8c9d0e1f"
}{
"code": "VALIDATION_FAILED",
"message": "Some of the details provided are not valid.",
"docUrl": "https://payments.lk/developers/errors#validation_failed",
"traceId": "0b8e7d6c-5a4f-4e3d-9c2b-1a0f9e8d7c6b",
"fields": [
{
"path": "amountCents",
"message": "Required"
},
{
"path": "",
"message": "Unrecognized key(s) in object: 'amount', 'currency'"
}
]
}What to retry
Retry only what time can change: a 429, a 500, a 503, and idempotency_key_in_progress. Wait a little, and send exactly the same request with the same Idempotency-Key, so a retry can never create a second payment. Every other error needs a change to the request, the key or the account first, and sends back the same answer until then.
An Idempotency-Key is remembered for 24 hours, separately for test and live keys. The same key with the same body inside that time gets the first answer back, marked with the header Idempotent-Replayed: true; after it, the key counts as new. More in the guide.
400 VALIDATION_FAILED
VALIDATION_FAILED
The request is not valid400 VALIDATION_FAILED, do not retry unchanged- Means
- The body or the query string failed validation. Every request is checked against a strict schema: a missing field, a value of the wrong type, or a field the endpoint does not take is refused, never ignored.
- Fix
- Read fields: each entry names the path of a field and what is wrong with it. Correct those and send the request again. Amounts go in amountCents, whole cents of LKR; there is no amount or currency field.
- Message
- Some of the details provided are not valid.
json_required
The body was not sent as JSON400 VALIDATION_FAILED, do not retry unchanged- Means
- Every write takes a JSON body. A body sent without Content-Type: application/json is not read as JSON, so none of its fields are found. curl -d sends a form unless the header is added.
- Fix
- Add -H "Content-Type: application/json" to curl, or set that header in your HTTP client. The SDKs set it for you.
- Message
- Send the request body as JSON, with the header Content-Type: application/json.
invalid_json
The body is not valid JSON400 VALIDATION_FAILED, do not retry unchanged- Means
- The body was sent as JSON but could not be parsed.
- Fix
- Send one JSON object. Look for a trailing comma, single quotes, or an unescaped quote or line break inside a string.
- Message
- The request body is not valid JSON.
idempotency_key_missing
No Idempotency-Key header400 VALIDATION_FAILED, do not retry unchanged- Means
- Every write needs an Idempotency-Key, so that a retry can never create a second payment, refund, link or charge.
- Fix
- Send a key made from your own record, such as order- followed by your order id: 8 to 255 letters, digits, dashes, underscores, colons or dots.
- Message
- Send an Idempotency-Key header (8 to 255 letters, digits, dashes, underscores, colons or dots) with every write.
amount
The amount is out of range400 VALIDATION_FAILED, do not retry unchanged- Means
- A payment is between Rs. 10 and Rs. 1,000,000, which is amountCents 1000 to 100000000. A refund is at least Rs. 1 and at most what is still refundable on the payment.
- Fix
- Send amountCents as whole cents of LKR: Rs. 1,450 is 145000. To refund everything that is left on a payment, leave amountCents out.
- Message
- The amount is outside what is allowed.
billing_date_invalid
Date out of range400 VALIDATION_FAILED, do not retry unchanged- Means
- A date that has passed would act at once without saying so, and one more than two years away is almost always a typing mistake.
- Fix
- Send an ISO 8601 time after now and before two years from now, or trialEnd "now" to end a trial at once.
- Message
- A trial's end, a cancellation date or a resume date is in the future and within two years.
billing_cycle_anchor_invalid
Billing cycle anchor out of range400 VALIDATION_FAILED, do not retry unchanged- Means
- The anchor is where renewals fall, and the part period before it is billed at once. The 1st of the month suits monthly and yearly prices only, and an anchor more than one period away would bill more than a period up front.
- Fix
- Send now, first_of_month with a monthly or yearly price, or an ISO 8601 time within one period of now; or leave it out for your billing settings' default.
- Message
- A billing cycle anchor is now, first_of_month for a monthly or yearly price, or a time after now and at most one period ahead.
items_invalid
Items cannot go together400 VALIDATION_FAILED, do not retry unchanged- Means
- Every item of a subscription renews together, so every price on it bills on the same interval and count, and a subscription keeps at least one item.
- Fix
- Use active prices from GET /v1/prices that share an interval, and change quantity rather than adding a price twice.
- Message
- A subscription's items are active prices of yours, each once, all on the same interval.
price_invalid
The price cannot be made400 VALIDATION_FAILED, do not retry unchanged- Means
- A tiered price prices every quantity from its tiers, so each tier starts where the one before ends and the last covers every unit beyond.
- Fix
- Send the tiers in order, each upTo above the one before, and the last upTo null.
- Message
- The tiers cannot price anything: 2 to 10 tiers, each upTo above the one before, the last one open.
meter_invalid
No such meter400 VALIDATION_FAILED, do not retry unchanged- Means
- A metered price bills a meter's usage, and a meter event is counted by the meter whose eventName it carries. Both name an active meter made with a key of the same mode.
- Fix
- Create the meter with POST /v1/meters, or use the eventName of an active one from GET /v1/meters, with a key of the same mode.
- Message
- No active meter of yours has that id or event name in this mode.
body_too_large
The body is too large413 VALIDATION_FAILED, do not retry unchanged- Means
- The body is over 256 kB. No endpoint needs anything near that.
- Fix
- Send only the fields the endpoint takes.
- Message
- The request body is larger than the API accepts, which is 256 kB.
401 UNAUTHENTICATED
UNAUTHENTICATED
No valid API key401 UNAUTHENTICATED, do not retry unchanged- Means
- The request did not carry an API key the API accepts. The reason says which of the cases below it is.
- Fix
- Send the header Authorization: Bearer followed by your secret key. Keys are created in the dashboard under Developers.
- Message
- Send a valid API key in the Authorization header, as Bearer followed by the key.
missing_api_key
No API key was sent401 UNAUTHENTICATED, do not retry unchanged- Means
- The request had no Authorization header. Keys in the query string or the body are not read.
- Fix
- Send the header Authorization: Bearer sk_test_... with your own key. Keep a secret key on your server.
- Message
- No API key was sent. Send it in the Authorization header, as Bearer followed by the key.
malformed_api_key
That is not an API key401 UNAUTHENTICATED, do not retry unchanged- Means
- The header is not of the form Bearer and a key, or the token is not shaped like a Payments.lk key: it was cut short when copied, has a space or a character too many, or is some other token. Basic authentication is not accepted.
- Fix
- Copy the key again from where you stored it when it was created; the dashboard shows only its first characters. If it is lost, create a new key under Developers and revoke the old one.
- Message
- The Authorization header does not carry a Payments.lk API key. It must be Bearer followed by one key, such as sk_test_...
unknown_api_key
The key is not recognised401 UNAUTHENTICATED, do not retry unchanged- Means
- The key is shaped correctly, but no active key matches it: it was revoked in the dashboard, an agent key passed its end date, or it belongs to a different Payments.lk environment.
- Fix
- Check which key your server reads from its configuration. If the key was revoked, create a new one in the dashboard under Developers.
- Message
- This API key is not recognised. It may have been revoked or have expired, or it was made for another environment.
403 FORBIDDEN
FORBIDDEN
This key cannot do that403 FORBIDDEN, do not retry unchanged- Means
- The key is valid, but its kind does not allow this endpoint.
- Fix
- Call the endpoint from your server with your secret key.
- Message
- This API key is not allowed to call this endpoint.
key_not_permitted
This kind of key cannot do that403 FORBIDDEN, do not retry unchanged- Means
- A publishable key (pk_) may only create checkouts. An agent key (ak_) works only at the MCP endpoint, https://api.payments.lk/mcp, and a secret key does not open that endpoint. Everything else on the REST API needs a secret key (sk_).
- Fix
- Call the endpoint from your server with your secret key. Never put a secret key in a browser or an app. Connect an agent with an agent key from the dashboard's AI agents page.
- Message
- This kind of API key cannot call this endpoint. Use your secret key, from your server.
404 NOT_FOUND
NOT_FOUND
No such object for this key404 NOT_FOUND, do not retry unchanged- Means
- The id does not name an object this key can see. It is mistyped, it belongs to another account, or it was made in the other mode.
- Fix
- Check the id. Test and live are kept apart: an id made with a test key works only with test keys, and one made with a live key only with live keys.
- Message
- No such object for this key. A test key sees only test mode objects, and a live key only live ones.
unknown_endpoint
No such endpoint404 NOT_FOUND, do not retry unchanged- Means
- The path, or the method used on it, is not part of the API. Paths are lower case and start with /v1/.
- Fix
- Compare the method and the path with the API reference on the developers page.
- Message
- There is no endpoint at this path for this method. Check both against the API reference.
mode_mismatch
The object is in the other mode404 NOT_FOUND, do not retry unchanged- Means
- The id names an object of your account, but it was made in the other mode: a live object asked for with a test key, or a test object with a live key.
- Fix
- Use a key of the object's own mode. A checkout made with a test key is paid, read and refunded with test keys only.
- Message
- This object belongs to the other mode. A test key sees only test mode objects, and a live key only live ones.
409 CONFLICT
CONFLICT
Not allowed in the current state409 CONFLICT, do not retry unchanged- Means
- The request is valid, but the account or the object is not in a state that allows it. The reason says which.
- Fix
- Read the object again and act on the reason. Sending the same request again will get the same answer until the state changes.
- Message
- That change conflicts with the current state.
idempotency_key_reused
Key already used for another request409 CONFLICT, do not retry unchanged- Means
- Within the last 24 hours this key was sent, in the same mode, with a different body, method or path. A key stands for one request, so the second one is refused rather than guessed at.
- Fix
- Use a new key for a new request. To retry the first request, send exactly the same body to the same path again.
- Message
- This Idempotency-Key was already used with a different request.
idempotency_key_in_progress
The first request is still running409 CONFLICT, safe to retry- Means
- Two requests with the same key overlapped. The first is still being processed, and its answer is what a retry will get back.
- Fix
- Wait a second or two, then send the same request again.
- Message
- The first request with this Idempotency-Key is still running. Retry in a moment.
merchant_not_live
Live mode is not open for this account yet409 CONFLICT, do not retry unchanged- Means
- A live payment, link, page or saved card setting was asked for before the account was approved and activated, or while it is not active.
- Fix
- Use a test key until the dashboard says the account is live, then create live keys there.
- Message
- Live payments open once the account is approved and activated.
not_refundable
The payment cannot be refunded409 CONFLICT, do not retry unchanged- Means
- Only a payment that succeeded, or is partly refunded, can be refunded. One that is processing, failed, or already refunded in full cannot.
- Fix
- Refund after payment.succeeded has arrived. Check refundedCents to see what is left.
- Message
- Only a successful payment can be refunded.
saved_cards_off
Saved cards are not switched on409 CONFLICT, do not retry unchanged- Means
- The checkout asked to keep the card (saveCard: true), but saved cards are not switched on. They use Payable's Advanced plan.
- Fix
- Switch saved cards on in the dashboard under Business settings, or send saveCard: false.
- Message
- Saved cards are not switched on for this account.
card_not_reusable
This saved card cannot be charged again409 CONFLICT, do not retry unchanged- Means
- The saved card is missing what the processor needs to charge it again, or it was saved by the local simulator, which Payable cannot charge. This does not change with time, so sending the request again gets the same answer.
- Fix
- Ask the customer to pay once at a checkout with saveCard: true, then charge the new card id that card.saved sends you.
- Message
- This card cannot be charged again. Ask the customer to save it at a new checkout.
subscription_ended
Subscription has ended409 CONFLICT, do not retry unchanged- Means
- A canceled subscription, or one whose first payment never came (incomplete_expired), is final. Its invoices and payments stay readable.
- Fix
- Start a new subscription for the customer with POST /v1/subscriptions.
- Message
- This subscription has ended, so it can no longer change.
invoice_not_open
Invoice is not open409 CONFLICT, do not retry unchanged- Means
- Charging, voiding and marking uncollectible act on an open invoice (voiding also on a draft). A paid, void or uncollectible invoice is final.
- Fix
- Read the invoice with GET /v1/invoices/{id} and act on its status. A draft is finalised with POST /v1/invoices/{id}/finalize.
- Message
- This invoice is not open, so it cannot be charged, voided or written off.
invoice_payment_pending
A payment for this invoice is with the processor409 CONFLICT, do not retry unchanged- Means
- An invoice is charged once at a time. The outcome of the charge already sent arrives as payment.succeeded or payment.failed, usually within seconds.
- Fix
- Wait for invoice.paid or invoice.payment_failed, then act on the invoice if it is still open.
- Message
- A charge for this invoice is with the processor. Wait for its outcome before trying again.
no_card_on_file
No card on file409 CONFLICT, do not retry unchanged- Means
- An invoice is charged to the subscription's card or the customer's default card, and neither is usable: never saved, or deleted since.
- Fix
- Send the customer the invoice's hostedInvoiceUrl: paying there saves the card for the next renewals.
- Message
- This customer has no card on file to charge. Send them the invoice's page to pay.
subscription_change_refused
Change not possible in this status409 CONFLICT, do not retry unchanged- Means
- Some changes depend on where a subscription stands: a trial is ended or moved only while it is trialing, collection is paused only while it renews, a paused subscription is resumed only with a card on file.
- Fix
- Read the subscription with GET /v1/subscriptions/{id} and make the change its status allows.
- Message
- That change does not apply to this subscription in its current status.
coupon_invalid
Coupon cannot be used409 CONFLICT, do not retry unchanged- Means
- A coupon is taken while it is active, has redemptions left and its redeem-by date has not passed, by a subscription of the same mode.
- Fix
- Use an active coupon from GET /v1/coupons, or create one with POST /v1/coupons.
- Message
- That coupon cannot be used: it is archived, used up, past its redeem-by date, or in the other mode.
tax_rate_invalid
Tax rate cannot be used409 CONFLICT, do not retry unchanged- Means
- A subscription takes active tax rates of yours made with a key of the same mode.
- Fix
- Use active rates from GET /v1/tax_rates, or create one with POST /v1/tax_rates.
- Message
- A tax rate is archived, in the other mode, or not yours.
feature_exists
A feature already has that lookup key409 CONFLICT, do not retry unchanged- Means
- Your app checks a customer's entitlements by lookup key, so each key names one feature in each mode.
- Fix
- Use the existing feature from GET /v1/entitlements/features, or choose another lookup key.
- Message
- Another feature in this mode already has that lookup key.
meter_exists
A meter already counts that event409 CONFLICT, do not retry unchanged- Means
- A meter event is counted by the one meter whose eventName it carries, so an eventName belongs to one meter in each mode.
- Fix
- Use the existing meter from GET /v1/meters, or choose another eventName.
- Message
- Another meter in this mode already counts that eventName. Choose another.
monthly_limit_reached
Monthly limit reached409 CONFLICT, do not retry unchanged- Means
- Each plan takes card payments up to a monthly limit: Starter to the first pricing bracket's ceiling, Growth to the second's. Payments that succeeded this month and payments with the processor now count toward it, and a payment that would take the month past the limit is refused whole. Refunds do not free up room. The limit resets at midnight on the 1st, Sri Lankan time. Test mode has its own count against the same limit.
- Fix
- Request an upgrade to the next plan in the dashboard (Plans) or with POST /v1/account/upgrade_requests. In test mode, reset the sandbox's usage on the Developers page. Customers are told only that card payments are not available right now.
- Message
- This payment would take this month's card payments past your plan's limit. Request an upgrade in the dashboard, or try again after the 1st.
429 RATE_LIMITED
RATE_LIMITED
Too many requests429 RATE_LIMITED, safe to retry- Means
- More requests arrived from one address than the API allows: 120 a minute in general, fewer on a few endpoints.
- Fix
- Wait a minute, then retry with the same Idempotency-Key. Spread bulk work out rather than sending it at once.
- Message
- Too many requests. Wait a moment and try again.
500 INTERNAL
INTERNAL
Something went wrong on our side500 INTERNAL, safe to retry- Means
- An error we did not expect. It is logged in full behind the traceId.
- Fix
- Retry with the same Idempotency-Key. If it keeps happening, write to [email protected] with the traceId.
- Message
- Something went wrong on our side. Please try again.
501 NOT_SUPPORTED
NOT_SUPPORTED
Not available yet501 NOT_SUPPORTED, do not retry unchanged- Means
- The endpoint exists, but what it does is not built or not switched on yet.
- Fix
- Nothing in your request will change this. The message says what is missing.
- Message
- This is not available yet.
503 SERVICE_UNAVAILABLE
Seen on the hosted pages
These reasons belong to the hosted checkout, checkout pages, the dashboard and agent tools. The REST API does not answer with them today, but a customer sent back to a checkout, or an agent, may meet them.
promotion_invalid
The promotion code does not work400 VALIDATION_FAILED, do not retry unchanged- Means
- The page takes no promotion codes, or the code is unknown, switched off or used up. In the customer portal, the code names no active coupon of the merchant's in that mode that can still be taken.
- Fix
- Check the code in the dashboard: on the checkout page's promotion codes, or, for the customer portal, on the coupon under Subscriptions, Billing settings.
- Message
- That promotion code is not valid, or has run out.
card_number_refused
A card number was typed on the checkout page400 VALIDATION_FAILED, do not retry unchanged- Means
- Something the customer typed on a checkout page (an answer, the delivery address, the tax number or the name) looks like a payment card number: 13 to 19 digits that pass the Luhn check. It was refused and nothing was stored. Card numbers are entered only on the processor's payment page.
- Fix
- Remove the number and pay again. A page that asks customers for card details is refused when it is saved; report one you see to us.
- Message
- Never type a card number on this page. You enter your card only on the secure payment page that comes next.
order_invalid
The order does not match the page400 VALIDATION_FAILED, do not retry unchanged- Means
- A quantity, an item or a delivery option is outside what the published page allows. The order is priced again on our side, so an edited order is refused.
- Fix
- Reload the page and order again.
- Message
- The order does not match what the page allows.
details_invalid
Details not accepted400 VALIDATION_FAILED, do not retry unchanged- Means
- An answer, the delivery address, the terms, or a setting of a checkout page was not accepted. The message names which.
- Fix
- Correct what the message names and try again.
- Message
- Some of the details were not accepted.
not_open
Already paid or closed409 CONFLICT, do not retry unchanged- Means
- The checkout was paid or closed, so it cannot be paid again. For an agent, it also means 20 prepared actions are already waiting for a person.
- Fix
- Create a new checkout for a new payment. An agent should ask the merchant to approve or decline what is waiting.
- Message
- This checkout has already been paid or closed.
expired
The checkout expired409 CONFLICT, do not retry unchanged- Means
- A checkout lasts 30 minutes from when it was made; after that it cannot be paid, and checkout.expired is sent.
- Fix
- Create a new checkout when the customer is ready to pay.
- Message
- This checkout has expired.
link_inactive
The payment link is off409 CONFLICT, do not retry unchanged- Means
- The payment link was turned off, or the code in its address is wrong.
- Fix
- Send the customer a new link.
- Message
- This payment link is no longer active.
page_inactive
The checkout page cannot do that409 CONFLICT, do not retry unchanged- Means
- The checkout page is not published, is archived, or cannot move to the status asked for.
- Fix
- Publish the page first. An archived page cannot come back; make a new one.
- Message
- This page cannot be changed that way.
page_closed
The checkout page is not taking payments409 CONFLICT, do not retry unchanged- Means
- The page is paused, has passed its end date, or has taken as many payments as it allows.
- Fix
- Resume the page in the dashboard, or change its end date or its limit.
- Message
- This page is not taking payments right now.
portal_action_refused
Not available in the customer portal409 CONFLICT, do not retry unchanged- Means
- The customer portal offers only what the merchant's billing settings allow: cancelling (at the period's end or at once), switching to the prices they chose, changing the card, and, where the merchant turned them on, a quantity within their bounds, a promotion code, a pause of a few months, the customer's own details and a retention offer. A subscription that has ended, is paused, is set to end, or has a change waiting cannot be changed there.
- Fix
- The merchant can turn these on under Subscriptions, Billing settings, Customer portal, or make the change for the customer in the dashboard.
- Message
- This change is not available here. Contact the business to make it.
page_busy
The last payments on this page are being made409 CONFLICT, do not retry unchanged- Means
- The page takes a fixed number of payments, and open checkouts hold every one that is left. A place is held for 30 minutes from when its checkout opened and freed if that checkout is not paid, so a page never takes more payments than its limit.
- Fix
- Try again in a few minutes. The merchant can raise the limit in the dashboard.
- Message
- The last payments this page allows are being made right now. Try again in a few minutes.
page_changed
The checkout page was saved somewhere else409 CONFLICT, do not retry unchanged- Means
- Two saves of one checkout page arrived together. Every save is a new numbered version that is never rewritten, so only one of them could take the number and the other is refused instead of overwriting it.
- Fix
- Reload the page in the dashboard and save again.
- Message
- This page was saved somewhere else at the same moment. Reload it and make your change again.
page_not_ready
The page cannot be published yet409 CONFLICT, do not retry unchanged- Means
- A test page is published on the Payable sandbox, which is set up once the application is submitted.
- Fix
- Submit your application, then publish the page.
- Message
- Test pages can be published once your application is submitted.
promotion_limited
Too many wrong promotion codes on this page429 RATE_LIMITED, safe to retry- Means
- A checkout page allows a fixed number of wrong promotion codes every ten minutes, counted across everyone who opens it, so codes cannot be guessed. Until the count falls, the page takes no promotion code at all; paying without one still works.
- Fix
- Wait ten minutes and try the code again, or pay without it.
- Message
- Too many wrong promotion codes were tried on this page. Try again in a few minutes, or pay without a code.
Something not explained here, or an error that keeps coming back? Write to [email protected] with the traceId. The developer guide walks through a first payment step by step.