Reseller API auth and limits
Bearer tokens, scopes, IP allowlist, rate limit, error codes, idempotency keys and pagination of the OneProvider reseller API, with examples.
Bearer tokens, scopes, IP allowlist, rate limit, error codes, idempotency keys and pagination of the reseller API.
All models, capabilities, and prices Documentation index for AI agents:https://oneprovider.dev/docs/llms.txt Tokens
A token looks like rsk_ followed by 40 characters. Create it in the cabinet (reseller tab, “API & integrations”); the value is shown once, we store only a hash and the last 4 characters. You can have up to 10 active tokens and revoke any of them at any time.
httpAuthorization: Bearer rsk_... Server side only CORS is disabled. Never embed a token in a browser bundle or a mobile app: call the API from your backend. If a reseller loses access, all their tokens answer 403 reseller_disabled. Scopes
A token carries a set of scopes. Scopes are independent: keys does not imply read. GET /me works with any valid token. Without the required scope the answer is 403 forbidden_scope.
| Scope | What it unlocks |
|---|---|
read | Read: balance and transactions, pricing, models, customers, keys, orders, plans, stats, the action log GET /audit (every GET except codes and webhooks). |
keys | Write: customers (including blocking), issuing, editing, pausing, topping up, rotating and deleting keys, POST /orders. |
codes | Code batches and the codes themselves (read, create, revoke, redeem). Codes are secrets, hence a separate scope. |
plans | Create, edit and delete plans (reading plans needs read). |
billing | Reseller balance top-ups: POST /balance/topups and the payment status. |
webhooks | Manage webhooks, send a test, read the delivery log. |
support | Reserved for upcoming support endpoints. |
bot | Read the state of the bots and cashiers: GET /bot and GET /payment-providers (no tokens in the answers). Bots are connected in the cabinet only. |
Each method's scope is listed in the reference .
IP allowlist
A token can be limited to a list of IPv4/IPv6 addresses or CIDR ranges. A request from another address gets 403 ip_not_allowed. The list is set when the token is created.
Rate limit
20 requests per second per token. When exceeded: 429 rate_limited and a Retry-After header. Wait that many seconds and retry.
Errors
Every error has the same shape. The same id is sent in the X-Request-Id header: quote it to support.
{
"error": {
"code": "insufficient_balance",
"message": "Not enough free reseller balance for this key.",
"request_id": "req_..."
}
} Branch on error.code, not on the message text.
| HTTP | code | Meaning |
|---|---|---|
| 400 | invalid | A field is missing or out of range. |
| 400 | bad_request | The request could not be parsed. |
| 400 | idempotency_key_required | Idempotency-Key header is missing on a money-spending method. |
| 400 | invalid_url | The webhook URL failed validation (https only, no IP literals or private networks). |
| 400 | limit_below_spent | The new key limit is below what the key has already spent. |
| 401 | unauthorized | The token is missing, unknown or revoked. |
| 403 | forbidden_scope | The token lacks the required scope. |
| 403 | ip_not_allowed | The request IP is not in the token's allowlist. |
| 403 | reseller_disabled | The reseller lost access; every token answers like this. |
| 403 | banned | The account is banned. |
| 404 | not_found | The object does not exist or belongs to another reseller. |
| 404 | key_not_found | The key does not exist. |
| 409 | insufficient_balance | The amount does not fit into Free (available_usd), see the money rule. |
| 409 | idempotency_key_reused | The same Idempotency-Key was already used with a different request body: use a new key. |
| 409 | fund_first | Top up the reseller balance first. |
| 409 | not_activated | The reseller cabinet is not activated yet. |
| 409 | name_taken | A key with this name already exists. |
| 409 | key_limit | The key limit is reached. |
| 409 | plan_limit | The plan limit (20) is reached. |
| 409 | customer_limit | The customer limit (10,000) is reached. |
| 409 | code_used | The code is already redeemed. |
| 409 | code_revoked | The code is revoked. |
| 409 | external_order_id_conflict | external_order_id was already used with different parameters. |
| 409 | customer_exists | A customer with this external_id already exists. |
| 409 | customer_blocked | The customer is blocked: keys, orders and redemptions for them are rejected. |
| 409 | token_limit | The active token limit (10) is reached. |
| 409 | webhook_limit | The webhook limit (5) is reached. |
| 429 | rate_limited | More than 20 requests per second; wait Retry-After seconds. |
| 501 | sandbox_not_implemented | The sandbox (rsk_test_...) is not implemented yet. |
| 503 | provider_unavailable | A payment or model provider is temporarily unavailable: retry later. |
Idempotency
Methods that spend money require the Idempotency-Key header (1 to 128 characters); without it the answer is 400 idempotency_key_required. They are marked in the reference. Repeating a request with the same key (per reseller) never spends twice and returns the same object. Use a new key for each new payment or order; for POST /orders the identity is external_order_id.
curl -s -X POST https://app.oneprovider.dev/partner/v1/keys \
-H "Authorization: Bearer $OP_PARTNER_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name":"client-1","limit_usd":"10.00"}' Store the idempotency key with your own order record so that after a network error you retry exactly the same request.
Pagination
List endpoints accept ?limit=1..100 (default 50) and &cursor=. The answer is {"items": [...], "next": "<cursor>"}; next is null on the last page. The cursor is opaque: pass it back as is.
curl -s "https://app.oneprovider.dev/partner/v1/keys?limit=100&cursor=<next from the previous page>" \
-H "Authorization: Bearer $OP_PARTNER_TOKEN" Sandbox
Test tokens rsk_test_... are coming soon. For now such a token gets 501 sandbox_not_implemented.