Skip to main content

API reference

Every endpoint the Warya reseller API publishes, the scope each one needs, the parameters it accepts, and the exact envelope every answer carries. Readable without an account.

Base URL https://api.waryashop.org/api/v1
01

Authentication

Send your key in the X-API-Key request header on every call. Never in a URL or query string. A missing, unknown, disabled or revoked key answers 401 and names nothing else about the account. An account holds 1 key; it carries scopes and may restrict the addresses it answers from (up to 32 rules).

curl "https://api.waryashop.org/api/v1/balance" \
  -H "X-API-Key: YOUR_API_KEY"
Limits

Rate limits and pagination

Where Per caller Whole deployment
Any /api/v1 request, before the key is checked 600/min 6,000/min
Your key (per key, configurable) 60/min (default, up to 6,000) —
POST https://api.waryashop.org/api/v1/player/validate 60/min 600/min

Every answer carries X-RateLimit-Limit and X-RateLimit-Remaining; a 429 also carries Retry-After and X-RateLimit-Reset, and repeats the wait as meta.retry_after. Listing endpoints are paged with page and per_page (alias limit): per_page is clamped to 1..100 on the order and transaction listings, while the catalogue listings default to and are capped at 500 families per page, with meta.has_more set when there are more.

Catalog

GET https://api.waryashop.org/api/v1/games catalog:read

List games and products

The code, English name, optional Persian name and Warya-hosted image URL. Paged; the default page is the whole list.

Success:
200
Scope:
catalog:read
Query:
search, category, page, per_page, sort
GET https://api.waryashop.org/api/v1/games/{code}/catalogue catalog:read

Packages and sale prices

Use the exact catalogue_name returned here when creating an order. These are Warya sale prices in USD, as decimal strings. Only packages that can be priced right now are listed: an item that is no longer on sale, or one whose amount cannot be confirmed, is absent here rather than published at 0.000000, and naming it in an order is a 422.

Success:
200
Scope:
catalog:read
Path:
code
Query:
search, in_stock, page, per_page, sort
GET https://api.waryashop.org/api/v1/games/{code}/fields catalog:read

Required player fields

The identifiers this game's order form must collect, with meta.source and meta.freshness saying where the answer came from. Vouchers return an empty list.

Success:
200
Scope:
catalog:read
Path:
code
GET https://api.waryashop.org/api/v1/games/{code}/servers catalog:read

Servers and regions

An empty list is a verified answer: this game needs no server selection. A game whose list cannot be confirmed is a 503, never an empty list.

Success:
200
Scope:
catalog:read
Path:
code

Checkout

POST https://api.waryashop.org/api/v1/player/validate players:validate

Validate player

Confirm an account before charging your customer. A temporary failure is a 503 with Retry-After, never "invalid account".

Success:
200
Scope:
players:validate
Body:
game, player_id, server_id?, charname?
POST https://api.waryashop.org/api/v1/orders orders:write

Create order

Confirms the current Warya price, charges your wallet and starts fulfillment. If the price cannot be confirmed, the answer is a 503 and nothing is charged.

Success:
201
Scope:
orders:write
Header:
Idempotency-Key
Body:
game, catalogue_name, player_id, server_id?, charname?, customer_reference?, callback_url?

Requires the Idempotency-Key header. Here is the 201 it answers with:

{
    "success": true,
    "order_id": "WR-20261010-8F3KD",
    "status": "processing",
    "charged": "0.950000",
    "currency": "USD",
    "idempotent_replay": false,
    "data": {
        "order_id": "WR-20261010-8F3KD",
        "status": "processing",
        "charged": "0.950000",
        "currency": "USD",
        "idempotent_replay": false
    },
    "meta": {
        "request_id": "01J8ZQ4M6W9K2N7P3R5T8V0XYZ"
    }
}
GET https://api.waryashop.org/api/v1/orders/{orderId} orders:read

Get order status

For completed vouchers, delivery contains the redeem code.

Success:
200
Scope:
orders:read
Path:
orderId
GET https://api.waryashop.org/api/v1/orders orders:read

List orders

Paginated order history; newest first.

Success:
200
Scope:
orders:read
Query:
status, type, game, customer_reference, from, to, page, per_page, sort

Account

GET https://api.waryashop.org/api/v1/balance balance:read

Wallet balance

Available and locked USD balances are returned as decimal strings.

Success:
200
Scope:
balance:read
GET https://api.waryashop.org/api/v1/transactions transactions:read

Wallet transactions

Paginated wallet ledger for reconciliation.

Success:
200
Scope:
transactions:read
Query:
type, status, from, to, page, per_page, sort
Responses

Status and error contract

Success answers success: true with the endpoint keys at the top level, the same keys under data, and meta.request_id (also in the X-Request-Id header). A failure answers success: false with error_code and message, repeating them under error.

Status Codes Meaning
200 / 201 — Success. success: true, the endpoint's own keys, the same keys under data, and meta.request_id.
400 BAD_REQUEST / IDEMPOTENCY_KEY_REQUIRED The request could not be understood, or a key that requires an Idempotency-Key did not send one.
401 MISSING_API_KEY / INVALID_API_KEY / UNAUTHENTICATED No key, an unknown key, or a disabled or revoked key. Nothing else about the account is disclosed.
402 INSUFFICIENT_BALANCE The wallet cannot cover the order. Nothing was charged and no order was created.
403 SCOPE_FORBIDDEN / ACCOUNT_BLOCKED / IP_NOT_ALLOWED The key does not hold this endpoint's scope, the account is blocked, or the request came from outside the key's IP allow-list.
404 NOT_FOUND / GAME_NOT_FOUND / ORDER_NOT_FOUND Nothing is served at that address, that product is not on sale, or that order is not yours (another account's id is a 404, never a 403).
405 METHOD_NOT_ALLOWED The HTTP method is not the one this endpoint accepts.
409 IDEMPOTENCY_CONFLICT / CONFLICT The Idempotency-Key was already used by this account for a different order. Reuse it only with the identical body; a key another account happens to use is that account's own and never collides with yours.
422 VALIDATION_FAILED / INVALID_REQUEST A field is missing, malformed or not something we sell. errors names each field.
429 Retry-After: 60s RATE_LIMITED Too many requests. Wait for the Retry-After header (seconds) before retrying.
500 INTERNAL_ERROR / SERVICE_TEMPORARILY_UNAVAILABLE An unexpected failure on our side. meta.request_id identifies it in our logs; retry with backoff.
503 Retry-After: 60s CATALOG_TEMPORARILY_UNAVAILABLE / SERVICE_TEMPORARILY_UNAVAILABLE The catalogue or a price could not be confirmed, or the service is temporarily unavailable. Nothing was charged; RETRY_AFTER seconds are enough before asking again.

401 · No key

{
    "success": false,
    "error": {
        "code": "MISSING_API_KEY",
        "message": "X-API-Key header is required."
    },
    "error_code": "MISSING_API_KEY",
    "message": "X-API-Key header is required.",
    "meta": {
        "request_id": "01J8ZQ4M6W9K2N7P3R5T8V0XYZ"
    }
}

403 · Missing scope

{
    "success": false,
    "error": {
        "code": "SCOPE_FORBIDDEN",
        "message": "This API key does not grant the \"catalog:read\" scope."
    },
    "error_code": "SCOPE_FORBIDDEN",
    "message": "This API key does not grant the \"catalog:read\" scope.",
    "meta": {
        "request_id": "01J8ZQ4M6W9K2N7P3R5T8V0XYZ"
    }
}

422 · Validation failed

{
    "success": false,
    "error": {
        "code": "VALIDATION_FAILED",
        "message": "The request could not be validated."
    },
    "error_code": "VALIDATION_FAILED",
    "message": "The request could not be validated.",
    "errors": {
        "game": [
            "The game field is required."
        ]
    },
    "meta": {
        "request_id": "01J8ZQ4M6W9K2N7P3R5T8V0XYZ"
    }
}
Orders

Order statuses

GET /orders/{orderId} publishes one of four statuses. delivery carries the redeem code only for a completed voucher order; it is null in every other state. These bodies are produced by the serializer the endpoint itself answers with.

created
{
    "success": true,
    "order_id": "WR-20261010-8F3KD",
    "status": "processing",
    "charged": "0.950000",
    "currency": "USD",
    "idempotent_replay": false,
    "data": {
        "order_id": "WR-20261010-8F3KD",
        "status": "processing",
        "charged": "0.950000",
        "currency": "USD",
        "idempotent_replay": false
    },
    "meta": {
        "request_id": "01J8ZQ4M6W9K2N7P3R5T8V0XYZ"
    }
}
processing
{
    "order_id": "WR-20261028-8F3KD",
    "status": "processing",
    "type": "game_topup",
    "game": "pubgm",
    "catalogue_name": "60 UC",
    "player_id": "12345678",
    "server_id": null,
    "total_amount": "0.950000",
    "currency": "USD",
    "customer_reference": "MY-ORDER-123",
    "delivery": null,
    "created_at": "2026-10-10T09:00:00+00:00",
    "updated_at": "2026-10-10T09:00:00+00:00"
}
completed
{
    "order_id": "WR-20261028-8F3KD",
    "status": "completed",
    "type": "game_topup",
    "game": "pubgm",
    "catalogue_name": "60 UC",
    "player_id": "12345678",
    "server_id": null,
    "total_amount": "0.950000",
    "currency": "USD",
    "customer_reference": "MY-ORDER-123",
    "delivery": {
        "code": "WARYA-60UC-4KQ7T2M9"
    },
    "created_at": "2026-10-10T09:00:00+00:00",
    "updated_at": "2026-10-10T09:00:00+00:00"
}
failed
{
    "order_id": "WR-20261028-8F3KD",
    "status": "failed",
    "type": "game_topup",
    "game": "pubgm",
    "catalogue_name": "60 UC",
    "player_id": "12345678",
    "server_id": null,
    "total_amount": "0.950000",
    "currency": "USD",
    "customer_reference": "MY-ORDER-123",
    "delivery": null,
    "created_at": "2026-10-10T09:00:00+00:00",
    "updated_at": "2026-10-10T09:00:00+00:00"
}
refunded
{
    "order_id": "WR-20261028-8F3KD",
    "status": "refunded",
    "type": "game_topup",
    "game": "pubgm",
    "catalogue_name": "60 UC",
    "player_id": "12345678",
    "server_id": null,
    "total_amount": "0.950000",
    "currency": "USD",
    "customer_reference": "MY-ORDER-123",
    "delivery": null,
    "created_at": "2026-10-10T09:00:00+00:00",
    "updated_at": "2026-10-10T09:00:00+00:00"
}
Webhooks

Customer webhooks

Create an order with a callback_url and Warya pushes each order event to it. Each delivery is signed. The endpoint must be HTTPS; redirects are not followed and the host is re-resolved and re-checked at every attempt.

Events

  • order.created
  • order.processing
  • order.completed
  • order.failed
  • order.refunded

Delivery

  • Signature: X-Warya-Signature
  • Timestamp: X-Warya-Timestamp
  • Event id: X-Warya-Event-Id
  • api_version: 2026-10-28
  • Retries: 5 attempts, waits 1, 5, 15, 60 min
  • Grace after rotation: 60 min

Verifying the signature

v1 is HMAC-SHA256 over "{t}.{event_id}.{raw_body}" keyed with your webhook secret. Recompute over the exact bytes you received and compare in constant time.

:raw_body = the exact bytes you received
t, v1 = the two values in X-Warya-Signature
expected = hmac_sha256(key = WEBHOOK_SECRET, message = t + "." + event_id + "." + raw_body)
ok = hash_equals(expected, v1)  # compare in constant time
API CONTRACT

OpenAPI 3.1 specification

The machine-readable contract, reachable without a key. Import it into Postman, Insomnia or Swagger, inspect the schemas, or generate a typed SDK. The same document this reference renders from is available at the address below.

https://api.waryashop.org/openapi.json

Machine answer, human answer. Contract read from the running API.