Errors
Every failure returns the same envelope with a stable machine-readable code, so you
can branch on failures with the same confidence as on success.
The envelope
json
{
"error": {
"type": "validation_error",
"code": "parameter_invalid",
"message": "One or more parameters were invalid.",
"errors": {
"amount": ["Amount must be at least 1000 paisa (रु 10.00)."]
},
"doc_url": "https://qora.laayo.com.np/docs/errors"
}
}
| Field | Use it for |
|---|---|
type | Broad category. Useful for logging and metrics. |
code | Branch on this. Stable across releases. |
message | Human-readable. Safe to log; do not parse. |
errors | Present on validation failures only: field name to messages. |
Status codes
| Status | Meaning | Retry? |
|---|---|---|
200 / 201 | Success. | — |
400 | Malformed request. | No — fix the request. |
401 | Authentication failed. | No — fix the key. |
403 | Authenticated but not permitted. | No. |
404 | No such object for this key's mode. | No. |
409 | Conflicts with current state. | Sometimes — read the code. |
422 | Well-formed but invalid. | No — fix the parameters. |
429 | Rate limited. | Yes, after backing off. |
5xx | Our problem, or Fonepay's. | Yes, with the same idempotency key. |
Codes you should handle
| Code | Status | What it means and what to do |
|---|---|---|
authentication_required | 401 | No key sent. Check your Authorization header. |
invalid_api_key | 401 | Unknown, revoked, or a publishable key used server-side. |
live_mode_unavailable | 403 | Live key used before verification. Finish KYC. |
account_suspended | 403 | Contact support. |
parameter_invalid | 422 | Read error.errors for the offending field. |
credentials_missing | 422 | No Fonepay credentials for this mode. Add them in Settings. |
gateway_unavailable | 502 | Fonepay declined or timed out. Retry with the same idempotency key. |
gateway_unreachable | 503 | We could not reach Fonepay. Retry shortly. |
payment_already_succeeded | 409 | Cannot cancel a paid payment. |
idempotency_in_progress | 409 | A duplicate is in flight. Wait, then retry. |
idempotency_key_reuse | 409 | Same key, different body. A bug in your caller. |
rate_limit_exceeded | 429 | Back off. See rate limits. |
Handling them
php
$response = $api->post('/v1/payments', ['amount' => $order->total_paisa]);
class="tok-key">if ($response->failed()) {
$error = $response->json('error');
match($error['code']) {
// Ours to fix: alert someone, and fall back to another tender.
'credentials_missing', 'invalid_api_key' => alert_ops($error['message']),
// Transient: safe to retry with the same idempotency key.
'gateway_unavailable', 'gateway_unreachable' => retry_later($order),
'rate_limit_exceeded' => backoff_and_retry($order),
default => show_cashier('Could not create the QR. Take payment another way.'),
};
}
A 5xx never leaks internal detail — no table names, paths, or query fragments.
If you need us to trace one, send the timestamp and the payment reference.