Skip to content

Errors

Every error response has the same envelope:

{
"error": {
"code": "VALIDATION_ERROR",
"message": "amount must be at least 10000 for virtual_account"
}
}

code is stable and machine-readable — write your error handling against it, not against message or the HTTP status alone. message is meant for you as a developer (safe to log), not for display to your customer.

Status Code Meaning
400 VALIDATION_ERROR The request body is well-formed but fails a business rule (e.g. amount below the method’s minimum)
400 INVALID_REQUEST The request body isn’t valid JSON, or doesn’t match the expected shape
400 IDEMPOTENCY_KEY_REQUIRED This endpoint requires an Idempotency-Key header
401 UNAUTHORIZED The Authorization header is missing, or the API key is invalid or revoked
403 FORBIDDEN The key is valid but isn’t allowed to perform this operation (e.g. some dashboard-only reads aren’t available to API keys)
403 PROJECT_UNAVAILABLE The project exists but isn’t usable right now (inactive, or in production and not yet approved)
403 CORS_ORIGIN_FORBIDDEN The request’s browser Origin isn’t on the project’s allowed list
404 PROJECT_NOT_FOUND The project doesn’t exist, or belongs to a different API key — see Authentication
404 RESOURCE_NOT_FOUND The requested payment link, payment, or other resource doesn’t exist
409 CONFLICT A resource with the same unique value already exists
409 IDEMPOTENCY_CONFLICT The Idempotency-Key was already used with a different request body
409 PAYMENT_LINK_NOT_ACTIVE The payment link is no longer accepting payments
409 PAYMENT_NOT_EDITABLE This transaction can no longer accept a new payment attempt
429 RATE_LIMITED Too many requests — back off and retry
500 INTERNAL_ERROR Unexpected server error. Safe to retry
503 SERVICE_UNAVAILABLE A dependency (e.g. the database) is temporarily unavailable
503 AUTH_UNAVAILABLE Authentication is temporarily unavailable
503 PAYMENT_PROVIDER_UNAVAILABLE The upstream bank/QRIS provider is temporarily unavailable

POST /v1/payments requires an Idempotency-Key header. Retrying the same key with an identical body returns the original resource (not a duplicate); retrying with a different body returns 409 IDEMPOTENCY_CONFLICT. This makes retries on a network timeout safe — see the Direct Payments guide.

500, 503, and 429 are the only codes worth retrying automatically — back off between attempts. Every other code means the request itself needs to change before retrying will help.