Errors are JSON with a stable machine-readable error code and, where useful, a human-readable detail:
{
"error": "replay_detected",
"detail": "nonce already used"
}
| Status | error | When |
|---|
| 400 | missing_idempotency_key | Money endpoint called without Idempotency-Key. |
| 401 | missing_signature | No X-PP-Signature header. |
| 401 | invalid_signature | Signature does not verify — tampered body/path or wrong key. |
| 401 | auth_failed | Token issuance: unknown consumer key or bad proof. |
| 401 | signature_error | Malformed signature material. |
| 403 | — | Bearer invalid/expired, or calling IP not whitelisted. |
| 409 | replay_detected | Nonce reused, or timestamp stale/future. |
| 422 | maker_checker_violation | Payout approver is the same user as the submitter. |
| 422 | (validation) | Field validation — standard Laravel shape with per-field errors. |
| 429 | — | Rate limit exceeded (e.g. token endpoint, checkout STK). |
Treat any 5xx as retryable with the same Idempotency-Key; treat 4xx as a bug in the request that retrying unchanged will not fix.