Skip to content
Dashboard

Warning: Provisional contract proposal

These endpoints are not operational and no SDK is published. Examples use test identifiers only and nothing is sent anywhere.

Reliability

Errors

How the API reports problems: one error object, a small set of HTTP status codes and stable error codes you can branch on, including the reasons a card payment fails.

The error object

Any response outside the 2xx range has a JSON body with a single error object. Branch on type and code, which are stable; show message to developers only, since its wording may change.

ShapeTypeScript
type KrokoPayError = {  error: {    type:      | "authentication_error"      | "permission_error"      | "invalid_request_error"      | "idempotency_error"      | "card_error"      | "api_error";    code: string;      // stable, machine-readable    message: string;   // for developers, not for payers    param?: string;    // the request field at fault, when there is one  };};

Parameters

typestringRequired
The family of the error. See error types.
codestringRequired
A specific, machine-readable reason, such as parameter_invalid. See error codes.
messagestringRequired
A readable explanation in English, meant for developers and logs.
paramstringOptional
The request field that caused the error, when there is one, such as amount.
A validation errorHTTP
HTTP/1.1 422 Unprocessable EntityContent-Type: application/json​{  "error": {    "type": "invalid_request_error",    "code": "parameter_invalid",    "message": "amount must be a positive integer in centavos.",    "param": "amount"  }}

HTTP status codes

StatusMeaningTypical codes
200 OKThe request worked.None
201 CreatedA new object was created (charge, refund, customer…).None
400 Bad RequestThe body is not valid JSON.invalid_json
401 UnauthorizedNo key, or the key is not valid.missing_api_key, invalid_api_key
402 Payment RequiredA card payment attempt was declined in the checkout.card_declined, insufficient_funds, expired_card, incorrect_cvc, processing_error
403 ForbiddenThe key is valid but cannot call this endpoint.secret_key_required
404 Not FoundThe object or route does not exist.resource_missing
409 ConflictAn Idempotency-Key was reused with a different request.idempotency_key_reused
422 Unprocessable EntityThe JSON is valid but a field is missing, invalid or not allowed in the current state.parameter_missing, parameter_invalid, not_refundable, invalid_amount, insufficient_balance
500 Internal Server ErrorSomething failed on KrokoPay's side.internal_error

Information: Rate limits

Request limits and their response are to be announced. This proposal does not define a 429 response yet.

Error types

typeWhat it meansRetry?
authentication_errorThe key is missing or not valid.No. Fix the key.
permission_errorThe key cannot perform this request.No. Use the secret key.
invalid_request_errorThe request itself is wrong: a field, an id or the object's state.No. Change the request.
idempotency_errorThe Idempotency-Key was already used for a different request.No. Use a new key for a new request.
card_errorThe card payment was declined. code says why.Only with another card or after the payer fixes the data.
api_errorAn unexpected problem on KrokoPay's side.Yes, with the same Idempotency-Key.

Error codes

Request errors

codeStatusWhen it happens
invalid_json400The body could not be parsed as JSON.
missing_api_key401No Authorization: Bearer header.
invalid_api_key401The key is malformed, unknown or was rolled.
secret_key_required403A publishable key was used where the secret key is required.
resource_missing404No object with that id, such as GET /v1/charges/chg_doesnotexist.
idempotency_key_reused409Same Idempotency-Key, different body or endpoint. See Idempotency.
parameter_missing422A required field is absent. param names it.
parameter_invalid422A field has the wrong type or value, such as a negative amount.
not_refundable422The charge is not paid or partially_refunded, so it cannot be refunded.
invalid_amount422The refund amount is zero or more than what is left to refund.
insufficient_balance422The account balance cannot cover the refund.
internal_error500Unexpected failure. Retry with the same Idempotency-Key.
Object not foundHTTP
HTTP/1.1 404 Not FoundContent-Type: application/json​{  "error": {    "type": "invalid_request_error",    "code": "resource_missing",    "message": "No charge with id chg_doesnotexist."  }}

Payment failures

A declined card does not make your POST /v1/charges call fail: the charge is created first, and the buyer pays it later in the checkout. When an attempt is declined, the checkout shows the reason to the buyer, the charge becomes failed with a failure_code, and you receive charge.failed.

failure_codeMeaningWhat the payer can do
card_declinedThe card was declined without a more specific reason.Try another card or pay with Pix.
insufficient_fundsThe card has no available limit for this amount.Use another card or pay with Pix.
expired_cardThe card's expiry date has passed.Check the date or use another card.
incorrect_cvcThe security code does not match.Type the CVC again.
processing_errorThe attempt could not be processed.Try again in a moment.

Pix has no decline step. A Pix charge that nobody pays before the code expires becomes expired and sends charge.expired; create a new charge to try again. Every outcome above can be reproduced with the test cards.

A failed card chargeJSON
{  "id": "chg_7Lc3PvN8yHd2Ws",  "object": "charge",  "amount": 49700,  "currency": "BRL",  "status": "failed",  "method": "card",  "card": {    "brand": "visa",    "last4": "0002"  },  "description": "Curso Identidade Visual do Zero",  "customer_id": "cus_8Hq2LmZr4TxN1a",  "product_id": "prod_3Vb7QpK9sWd2Ee",  "price_id": "price_5Kd1NcR8vYq3Hm",  "link_id": null,  "subscription_id": null,  "fee": null,  "net": null,  "refunded_amount": 0,  "failure_code": "card_declined",  "created_at": "2026-11-03T16:02:19.000Z",  "paid_at": null,  "expires_at": null,  "available_at": null,  "checkout_url": "https://krokopay.app/pay/chg_7Lc3PvN8yHd2Ws"}
charge.failedJSON
{  "id": "evt_7Pp8Qq9Rr0Ss1T",  "object": "event",  "type": "charge.failed",  "api_version": "2026-11-01",  "created_at": "2026-11-03T16:02:24.000Z",  "data": {    "object": {      "id": "chg_7Lc3PvN8yHd2Ws",      "object": "charge",      "amount": 49700,      "currency": "BRL",      "status": "failed",      "method": "card",      "card": {        "brand": "visa",        "last4": "0002"      },      "description": "Curso Identidade Visual do Zero",      "customer_id": "cus_8Hq2LmZr4TxN1a",      "product_id": "prod_3Vb7QpK9sWd2Ee",      "price_id": "price_5Kd1NcR8vYq3Hm",      "link_id": null,      "subscription_id": null,      "fee": null,      "net": null,      "refunded_amount": 0,      "failure_code": "card_declined",      "created_at": "2026-11-03T16:02:19.000Z",      "paid_at": null,      "expires_at": null,      "available_at": null,      "checkout_url": "https://krokopay.app/pay/chg_7Lc3PvN8yHd2Ws"    }  }}

Handling errors

Branch on type, not on messageTypeScript
async function createCharge(body, idempotencyKey) {  const response = await fetch("https://api.krokopay.app/v1/charges", {    method: "POST",    headers: {      Authorization: `Bearer ${process.env.KROKOPAY_SECRET_KEY}`,      "Idempotency-Key": idempotencyKey,      "Content-Type": "application/json",    },    body: JSON.stringify(body),  });​  if (response.ok) return response.json();​  const { error } = await response.json();  switch (error.type) {    case "invalid_request_error":      // Fix the request: error.param names the field.      throw new Error(`Invalid ${error.param ?? "request"}: ${error.message}`);    case "authentication_error":    case "permission_error":      // Configuration problem: alert the team, do not retry.      throw new Error(`KrokoPay key problem: ${error.code}`);    case "api_error":      // Safe to retry with the same Idempotency-Key.      throw Object.assign(new Error(error.message), { retryable: true });    default:      throw new Error(error.message);  }}

Retries

  • Retry only 500 responses and network failures (timeouts, dropped connections), where you cannot know whether the request went through.
  • Always send the same Idempotency-Key when retrying a POST, so a request that did succeed is not repeated.
  • Wait longer between each attempt (for example 1 s, 2 s, 4 s) and stop after a few tries.
  • Never retry 4xx responses unchanged. They will fail the same way.

What to show payers

The hosted checkout already explains card declines and Pix expiry to the buyer. In your own pages, show a short, friendly message ("The payment did not go through. You can try again.") and offer a new attempt. Never show message or raw codes to buyers.

Logging

Log the method, path, status, type and code of every failed call, plus your own order id. Every call is also listed in Developers → Logs with a req_ identifier, so you can match your logs with ours when asking for help.