Omega Store — Developers

Reseller API Documentation

Everything your system needs to sell from our catalogue: pull the products, spend your wallet on orders, and follow those orders to delivery — plain HTTPS and JSON, no screen in the loop. The sections below run in the order you will actually do this in.


Start Here

Two things get you connected: the base URL below, and a token you generate yourself from the API page of your store account. Nothing else is provisioned by hand.

Base URL
https://test-api.mr-omegastore.shop

Prove the connection before writing any integration code. This call needs nothing but the token, touches no money, and its answer tells you the token reached us and was accepted:

First call
curl -H "api-token: YOUR_API_TOKEN" "https://test-api.mr-omegastore.shop/client/api/profile"

A JSON object with your balance means you are through — skip to Importing the Catalogue. Anything else is an error body carrying a number; look the number up in Error Codes instead of reading the message text.

Moving from another supplier?

Our paths, response envelopes and error numbers follow the convention most panels in this market already speak, so a working integration usually needs the base URL swapped and the token replaced. Read Reading Our Responses first to confirm the details line up rather than assuming the rest is identical.

Connecting Your Panel

Where to point it

The base URL is the origin and nothing more — no /client/api on the end. Every endpoint here is written as a full path from the root, so your panel appends it to the origin itself. Carry the prefix in both halves and the request becomes /client/api/client/api/…, which is a path we do not serve.

A field asking for a domain, not a URL

Some panels do not take a URL at all — they take the host on its own and assemble the rest internally, usually with a note telling you to leave out the scheme, the www and the trailing slash. In that field, enter only:test-api.mr-omegastore.shop

How to authenticate

Send the token on every request in the api-token header. It is a password in every sense — it spends your balance — so keep it server-side, and rotate it from your account page the moment you suspect it leaked.

Request header
api-token: YOUR_API_TOKEN

Not every panel can set a custom header, so two other forms are read as well: x-api-key and Authorization: Bearer. They carry the same token and get the same treatment — use whichever your side can actually send.

What can still refuse you

A valid token is not on its own enough. API access is a switch the store holds per account: until it is on, every call comes back as 122 however correct the request is. If your first call returns 122 with a token you just generated, that switch — not your code — is what to ask about.

IP allowlist (optional)

Your token can be pinned to a list of addresses from your account page. While allow all IPs is on — the default — any source is accepted, which suits testing and is worth turning off once your integration runs from known servers. With it off, only listed addresses pass and the rest are rejected with 123.

Reading Our Responses

  • The HTTP status is always 200, success or failure, with Content-Type: application/json. Branch on the body, never on the status line — a handler that reads 200 as success will happily store an error as an order.
  • Two body shapes exist. /profile, /products and /categories answer with the object or array on its own, unwrapped; /content, /newOrder and /check wrap theirs as { "status": "OK", "data": … }. Each section below shows which it is.
  • Errors are one shape with a stable number. Build your handling on the number; the message is written for a human reading a log and may be reworded.
Error body (still HTTP 200)
{
  "status": "error",
  "code": 100,
  "message": "Insufficient balance"
}

You will never get HTML or an empty body

Two failure modes that make integrations hard to debug are deliberately closed. A request to a path we do not serve comes back as error 114 in JSON, naming the path it saw — never an HTML error page. And although every endpoint is documented as GET, a POST to the same URL is accepted rather than refused with an empty response: body parameters are folded into the query string, and the query string wins where the two disagree. Whatever your panel sends, what comes back is JSON you can parse.

Your Account & Balance

Your wallet balance and the currency it is held in. Every price you read and every charge you incur elsewhere in this API is in that same currency, so read it once at startup rather than assuming one.

GET/client/api/profile
Example response
{
  "balance": "8788.683",
  "currency": "USD",
  "email": "reseller@example.com"
}

Importing the Catalogue

Three endpoints cover the catalogue, and which you want depends on the job: naming the sections, opening one of them, or taking the lot in a single sweep.

Naming the sections

An id and a name per category, and deliberately nothing else, so a picker on your side costs one small request instead of a full catalogue pull. Every id it hands back can be opened directly — there is no deeper level to descend through first.

GET/client/api/categories
Example response
[
  { "id": 1, "name": "ببجي موبايل" },
  { "id": 2, "name": "فري فاير" }
]

Opening one section

Pass 0 for the top level and you get the categories again, this time with their images. Pass a category id and you get the products inside it, in the same item shape /products uses — so one parser serves both.

GET/client/api/content/0
Example response — /content/0
{
  "status": "OK",
  "data": {
    "categories": [
      {
        "id": 1,
        "name": "ببجي موبايل",
        "img": "images/products/pubg.webp",
        "parent_id": 0
      }
    ],
    "products": []
  }
}
GET/client/api/content/{category_id}
Example response — /content/1
{
  "status": "OK",
  "data": {
    "categories": [],
    "products": [
      {
        "id": 1,
        "name": "60 UC",
        "price": 1,
        "params": ["ادخل الايدي الاعب"],
        "params_keys": ["playerId"],
        "category_name": "ببجي موبايل",
        "available": true,
        "qty_values": null,
        "product_type": "package",
        "parent_id": 1,
        "base_price": 1,
        "category_img": "images/products/pubg.webp"
      }
    ]
  }
}
An id that does not exist, or one hidden from the API, answers with 109 rather than an empty list — so you can tell a wrong id from an empty section.

Taking the whole catalogue

Every product you may sell, in one bare array. price is already yours — your account's tier pricing is applied before we answer, so you never compute it — and base_price is the same figure before any promotion, so with no promotion running the two are equal. Whichever of the two your panel reads, it reads your own price. A row carrying available: false is still real and worth showing greyed out; it simply cannot be ordered at this moment — its price fields read 0 and must not be sold from.

GET/client/api/products
ParameterInRequiredDescription
products_idqueryNoRestrict the answer to specific ids, comma separated — ?products_id=1,4,9. Useful for refreshing a handful of rows you already linked.
pricequeryNoA trimmed row per product — { id, name, basePrice, priceForQty, price, params, available } — carrying only what moves between one sync and the next.
basequeryNoA middle weight — { id, name, price, base_price, available, qty_values, product_type } — holding what a person needs on screen while matching our products against theirs.
Example response
[
  {
    "id": 1,
    "name": "60 UC",
    "price": 1,
    "params": ["ادخل الايدي الاعب"],
    "params_keys": ["playerId"],
    "category_name": "ببجي موبايل",
    "available": true,
    "qty_values": null,
    "product_type": "package",
    "parent_id": 1,
    "base_price": 1,
    "category_img": "images/products/pubg.webp"
  },
  {
    "id": 4,
    "name": "PUBG UC (by amount)",
    "price": 0.016,
    "params": ["ادخل الايدي الاعب"],
    "params_keys": ["playerId"],
    "category_name": "ببجي موبايل",
    "available": true,
    "qty_values": { "min": "60", "max": "50000" },
    "product_type": "amount",
    "parent_id": 1,
    "base_price": 0.016,
    "category_img": "images/products/pubg.webp"
  }
]
Example response — ?price=1
[
  {
    "id": 1,
    "name": "60 UC",
    "basePrice": 1,
    "priceForQty": 1,
    "price": 1,
    "params": ["ادخل الايدي الاعب"],
    "params_keys": ["playerId"],
    "available": true
  }
]
Example response — ?base=1
[
  {
    "id": 1,
    "name": "60 UC",
    "price": 1,
    "base_price": 1,
    "available": true,
    "qty_values": null,
    "product_type": "package"
  }
]

Sync the catalogue, not each product

One call returns every row, so a scheduled sweep against ?price=1 keeps your prices and availability current for the cost of a single request. Looping over your own product list and asking about each one in turn fetches the same data spread over hundreds of calls.

Reading a product row

params is the list of inputs an order must carry — a player id, a server, whatever this product needs — already in the order they should be asked of your buyer. The strings are the exact labels our own customers see, Arabic included, so they can be rendered as they arrive.

qty_values decides which quantities the product accepts, in one of three forms:

  • null — a fixed package: quantity must be exactly 1, and product_type reads "package".
  • {"min":"500","max":"500000"} — a range, with product_type reading "amount".
  • ["110","150","210"] — a fixed set of allowed quantities. Handle it if you are writing a general parser; our catalogue does not currently produce this form.

priceForQty is always 1

Our prices are per single unit, so under ?price=1 the priceForQty field is always 1. It is sent explicitly rather than omitted so that code dividing by it keeps working untouched.

Placing Orders

One call creates the order and takes price × qty from your balance in the same breath — no separate confirm step to forget, and no window where an order exists unpaid.

GET/client/api/newOrder/{productId}/params?qty=1&playerId=5123456789&order_uuid={uuidv4}
ParameterInRequiredDescription
productIdpathYesThe id of the product, as returned by /products.
order_uuidqueryYesA UUIDv4 you generate for this attempt. It is what makes a retry safe — see below. Absent or malformed gives error 114.
qtyqueryYesWhole number, at least 1, and within the product's qty_values — errors 106 / 112 / 113 tell you which rule you broke.
playerIdqueryDepends on productWhere the order is delivered. Required exactly when the product's params asks for it. Read the matching params_keys entry from the product list and send that key — it is playerId for the first field. We also accept common aliases (player_id, uid, account_id), the field's own label from params sent as the key (URL-encoded, as any HTTP client does), and the positional key 1 for the first field, so panels that only know the labels still work. Prefer the real key.
…any key=valuequeryNoExtra pairs are kept with the order and returned in the response's data object — handy for carrying your own reference through.

Retrying is safe — that is what order_uuid is for

A timeout tells you nothing about whether the order was created. So repeat the request with the same order_uuid: the original order comes back and your balance is not touched again. The rule follows from that — one fresh UUIDv4 per real order, reused only while retrying that same order.

Rate limit

Order creation allows 30 requests per minute per token. Past that you get code 111 and should pause rather than retry immediately.
Example request
https://test-api.mr-omegastore.shop/client/api/newOrder/1/params?qty=1&playerId=5123456789&order_uuid=8f14e45f-ceea-4e67-9d43-1b7f0f2f8a11
Example response
{
  "status": "OK",
  "data": {
    "order_id": "OMG-O-8N5R-P3WX",
    "status": "wait",
    "price": 1,
    "currency": "USD",
    "data": { "playerId": "5123456789" },
    "replay_api": null
  }
}
  • order_id is permanent and public — store it, because it is what /check takes. Orders from before August 2026 carry the older ID_… form; both stay valid forever and neither is ever reissued.
  • A new order answers "status": "wait" — creation and fulfilment are separate moments. Treat it as accepted, not delivered.
  • replay_api and reply are both null here; the answer a customer reads is written when the order settles, so it appears later, on /check.

Tracking Orders

Ask about up to a hundred orders at once. The lookup only ever sees your orders: an id that does not exist, or belongs to someone else, is left out of the answer rather than reported — so a short result means some ids were not yours, not that the call failed.

GET/client/api/check?orders=[OMG-O-8N5R-P3WX,OMG-O-Q6T9-M2KC]
ParameterInRequiredDescription
ordersqueryYesOrder ids in brackets, comma separated — the order_id values you saved at creation. Up to 100 per call.
uuidqueryNoAdd &uuid=1 to look orders up by the order_uuid you generated instead of by our id — useful when a creation call timed out and you never received one.
Example response
{
  "status": "OK",
  "data": [
    {
      "order_id": "OMG-O-8N5R-P3WX",
      "quantity": 1,
      "data": { "playerId": "5123456789" },
      "created_at": "2026-08-23 14:05:12",
      "product_name": "60 UC",
      "price": "1",
      "currency": "USD",
      "status": "accept",
      "replay_api": ["✅ تم شحن الحساب بنجاح"]
    }
  ]
}
  • price is the order total as a decimal string, and created_at is YYYY-MM-DD HH:mm:ss in server local time.
  • replay_api is the answer to show your customer — the same sentence ours reads under the order. Delivered codes come one per element; a rejection comes as its reason; a reply typed by a human comes as written. It stays null until the order settles. Every element is a string — one per line of the answer, never an object, so you can render the array directly. If the fulfilment source returned the account name, level or avatar, they appear inside that text when the store owner puts them in the reply template. It is not repeated as separate fields: one answer, one place, so nothing can disagree with itself.

The three states

StatusMeaning
acceptDelivered. Final — it will not change again.
rejectRejected or cancelled, and anything charged is already back in your wallet. Also final.
waitStill in flight — queued, processing, or held for review. Not a failure; keep checking.

Being told instead of asking

Set a webhook URL on your token from your account page and we will POST this body to it whenever one of your API orders changes state:

Webhook payload (POST body)
{
  "order_id": "OMG-O-8N5R-P3WX",
  "uuid": "8f14e45f-ceea-4e67-9d43-1b7f0f2f8a11",
  "status": "accept",
  "price": "1",
  "updated_at": "2026-08-23T14:09:41.000Z",
  "reply": "✅ تم شحن الحساب بنجاح",
  "player_id": "1000000001",
  "player_name": "player_demo",
  "player_level": "21",
  "player_image": "https://example.com/avatar.png",
  "charged_quantity": "10000"
}
  • order_id and uuid are the two identifiers you already hold, and status is one of the three above.
  • Delivery is best-effort — one retry, then we stop. Treat a webhook as a nudge to look, and let /check stay the thing you believe. An integration that trusts webhooks alone will eventually miss an order.

Polling what changed

If you would rather not track ids, ask what moved instead. Same wrapper, same per-order fields as /check — plus updated_at, which is your cursor: send the newest one back as since next time and you get only what is new.

GET/client/api/changeStateHistory
ParameterInRequiredDescription
sincequeryNoUnix seconds, milliseconds, or an ISO timestamp — all three are read. Omit it and you get the last 12 hours, which is what a panel that has just come back online wants.
limitqueryNoNewest change first; 50 by default, 200 at most. If you hit the cap, the oldest changes are the ones left out — raise limit or poll more often.

Only orders placed through this API are ever listed — what a customer bought on the storefront by hand is not yours to sync, and never appears.

Error Codes

Every error arrives in the shape shown in Reading Our Responses, under HTTP 200. Match on the number.

Access — can occur anywhere

CodeMessageNotes
120Api Token is required!No token reached us in any accepted form.
121Token errorThe token is not one of ours.
122Not allowed to use APIRevoked or disabled token, an inactive account, or API access not switched on for the account yet.
123IP not allowedThe allowlist is enforced and this address is not on it.
130The site is under maintenanceAPI access is paused store-wide. Retry later; nothing is wrong on your side.

Ordering — on /newOrder (109 also on /content)

CodeMessageNotes
100Insufficient balanceYour wallet does not cover price × qty.
105Quantity not available
106Quantity not allowedqty must be exactly 1 when qty_values is null.
107Player ID blocked
1082FA required
109Product deleted or not foundUnknown, deleted, or hidden product / category id.
110Product not available nowIt exists but is switched off at the moment.
111Try again after 1 minuteYou passed 30 order requests in a minute.
112Quantity is too smallBelow the product's minimum.
113Quantity is too largeAbove the product's maximum.
114Unknown errorA validation failure or an unrecognised path — the message names the exact problem, so read it rather than mapping the code alone.
500Unknown errorSomething broke on our side. Retrying later is reasonable.
Omega Store reseller API — base URL https://test-api.mr-omegastore.shop