Errors & rate limits
Predictable error shapes, idempotent writes and documented limits.
Error shape
Every failure returns the same envelope. request_id identifies the exact call — include it when you contact support. details is present on validation failures.
{
"success": false,
"request_id": "req_9f2c…",
"mode": "live",
"error": {
"code": "QUOTE_EXPIRED",
"message": "This quote expired. Request a new one.",
"details": { }
}
}Error codes
| Field | Type | Description | |
|---|---|---|---|
| INVALID_AMOUNT | 400 | optional | Amount missing or non-positive. |
| INVALID_RECEIVER | 400 | optional | Mobile number malformed. |
| QUOTE_EXPIRED | 400 | optional | Quote confirmed after its 10-minute TTL. |
| INSUFFICIENT_BALANCE | 400 | optional | You don't hold enough of the asset. |
| SANDBOX_ONLY | 400 | optional | Endpoint is sandbox-only (e.g. /sandbox/reset). |
| INVALID_TOKEN | 401 | optional | Missing, malformed or unrecognised bearer token. |
| INVALID_OTP | 401 | optional | Wrong OTP. 5 attempts, then the quote locks. |
| INSUFFICIENT_PARTNER_FUNDS | 402 | optional | Your wallet float / credit line can't cover this buy. |
| INSUFFICIENT_USER_BALANCE | 402 | optional | The end user doesn't hold enough to sell or redeem. |
| API_NOT_ALLOWED | 403 | optional | Your profile isn't enabled for this capability. |
| LIVE_NOT_ENABLED | 403 | optional | Live mode not activated yet. Sandbox keeps working. |
| SANDBOX_DISABLED | 403 | optional | Sandbox access disabled for your partner. |
| PARTNER_DISABLED | 403 | optional | Master kill-switch; both modes disabled. |
| KYC_REQUIRED | 403 | optional | The user must complete KYC first. |
| WALLET_NOT_CONFIGURED | 403 | optional | Partner wallet not set up — a separate onboarding step. |
| FEATURE_DISABLED | 403 | optional | e.g. instant KYC is off for your profile. |
| QUOTE_NOT_FOUND | 404 | optional | quote_id was never issued, or isn't yours. |
| USER_CODE_NOT_FOUND | 404 | optional | user_code isn't recognised for your partner. |
| USER_CODE_CONSUMED | 409 | optional | Single-use embed code already exchanged. |
| QUOTE_ALREADY_CONSUMED | 409 | optional | Quote already used. Replay with the original Idempotency-Key. |
| DAILY_LIMIT_EXCEEDED | 429 | optional | The send breaches your daily INR limit. |
| RATE_LIMITED | 429 | optional | Too many requests in the window — see below. |
| BUY_PROCESSING_FAILED | 500 | optional | Fulfilment failed after debit; your wallet is auto-refunded and the order voided. |
| PRICES_UNAVAILABLE | 503 | optional | Upstream price feed is down. |
Idempotency
Send an Idempotency-Key header on mutating endpoints (buy, sell, send, gift and delivery confirms) to make retries safe. Any string up to 100 characters — a UUID is ideal.
Keys are scoped to your partner profile and endpoint and stay valid for at least 24 hours. Reusing one returns the original response with no new side effects, flagged by an idempotent_replay field in the envelope and an Idempotent-Replayed: true response header.
curl -X POST https://api.oropocket.com/partner/buy/confirm \
-H "Authorization: Bearer oro_live_xxx" \
-H "Idempotency-Key: 8f14e45f-cea1-4b2c-9d3a-1f2e3d4c5b6a" \
-d '{"quote_id":"tqt_xxx"}'QUOTE_ALREADY_CONSUMED you can't safely interpret.Rate limits
| Field | Type | Description | |
|---|---|---|---|
| POST /auth/request-otp | 5 / 15 min | optional | Per (IP, mobile). |
| POST /auth/verify-otp | 10 / 15 min | optional | Per (IP, mobile). |
| All other endpoints | 120 / min | optional | Per IP. |
Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset — back off when remaining hits zero rather than retrying blindly.