Home API Reference

API Reference

By PalCards
7 articles

Create an order

Create an order to buy a product item — a voucher (charging card) or a top‑up. The type is detected automatically from the product. Send Content-Type: application/json. Base URL https://api.palcards.ps (or sandbox — see Sandbox & production). 1. (Top‑ups) Validate the account first — preflight For top‑up products, validate the target account and get the account holder's nickname before charging: curl -X POST "https://api.palcards.ps/order/top-up/preflight" \ -H "x-api-key: sk_prod_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "productItem": 5002, "context": { "accountId": "123456789" } }' { "success": true, "message": "OK", "nickname": "PlayerOne" } 2. Create the order curl -X POST "https://api.palcards.ps/order" \ -H "x-api-key: sk_prod_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "productItem": 5001, "customer": { "phone": "+970590000000" }, "orderCurrency": "USD", "clientOrderRef": "ref-0001", "notificationUrl": "https://your-server.example/webhooks/palcards" }' POST /order Body | Field | Type | Required | Description | |---|---|---|---| | productItem | number | Yes | Product item ID (from the catalog) | | customer.phone | string | Yes | Customer phone, international format, e.g. +97059… | | orderCurrency | string | No | ISO‑4217 currency code, e.g. USD. Defaults to your default wallet's currency. You must have a wallet in this currency | | context | object | Conditional | Required‑info fields for the product (e.g. accountId, serverId) — see the product's customerRequiredInfo | | clientOrderRef | string (≤512) | No | Your own reference, echoed back in responses & webhooks. Not an idempotency key — see the warning below | | notificationUrl | string | No | Your HTTPS webhook URL; receives the final order (see Order webhooks) | Response { "id": 9001, "status": "in-progress", "type": "chargingCard", "orderCurrency": "USD", "clientOrderRef": "ref-0001", "appliedDealerPrices": { "price": 9.5, "discount": 0.5, "discountPercent": 5, "revenue": 0.5, "revenuePercent": 5 }, "soldItem": null, "createdAt": "2026-06-07T12:00:00.000Z", "updatedAt": "2026-06-07T12:00:00.000Z" } - status: pending → in-progress → success | failed | rejected. Fulfillment is asynchronous — a 200 means accepted, not fulfilled. See Order status lifecycle. - On success, a voucher's card is in soldItem: { "id": 7001, "voucher": { "code": "…", "pin": "…" } }. - Track completion with a webhook (notificationUrl) or by polling GET /order/{id} (see Retrieve orders). ⚠️ clientOrderRef is not idempotent clientOrderRef is stored and echoed back only — it does not de‑duplicate. Two POST /order calls with the same clientOrderRef create two orders and charge you twice. To avoid accidental duplicates, treat a network timeout as unknown (not failed): before retrying, look for the order via GET /order (e.g. by date), and only retry if you received no order id. Currency & wallets - The order is charged from your wallet whose currency equals orderCurrency. - Omitting orderCurrency uses your default wallet's currency. - A currency you have no wallet for → 400 "Required wallet for currency 'X' is missing." - A balance below the price → 400 "Insufficient balance" and no order is created (you are not charged). See Wallet & balance. Rate limits Order creation is not currently rate‑limited, but design your client to handle HTTP 429 on any endpoint (see Errors & rate limits) and avoid hammering the API.

Last updated on Jun 07, 2026

Retrieve orders

List your orders, or fetch a single order to check its status and result. List orders curl -H "x-api-key: sk_prod_your_key_here" \ "https://api.palcards.ps/order?ps=20&status=success" GET /order — you only ever see your own orders. Query parameters | Param | Type | Description | |---|---|---| | orderType | string | chargingCard, topUp, or manual | | status | string[] | Filter by status (repeatable) | | startDate / endDate | string | Date range (ISO 8601) | | pn | number | Page number | | ps | number | Page size | Filter by multiple statuses by repeating the param: ?status=success&status=failed. Response (paginated) { "pageNumber": 1, "pageSize": 20, "total": 137, "data": [ { "id": 9001, "status": "success", "type": "chargingCard", "clientOrderRef": "ref-0001", "createdAt": "2026-06-07T12:00:00.000Z" } ] } Get one order curl -H "x-api-key: sk_prod_your_key_here" \ "https://api.palcards.ps/order/9001" GET /order/{id} Response (single order) { "id": 9001, "status": "success", "type": "chargingCard", "orderCurrency": "USD", "clientOrderRef": "your-ref-0001", "soldItem": { "id": 7001, "voucher": { "code": "…", "pin": "…" } }, "customer": { "phone": "+97059…" }, "appliedDealerPrices": { "price": 9.5, "discount": 0.5, "discountPercent": 5 }, "createdAt": "2026-06-07T12:00:00.000Z", "updatedAt": "2026-06-07T12:00:03.000Z" } - Poll this endpoint to follow an order to a terminal status — or use a webhook to avoid polling. See Order status lifecycle. - For vouchers, the delivered card is in soldItem ({ id, voucher: { code, pin } }) when status is success.

Last updated on Jun 07, 2026

Order status lifecycle

Orders are fulfilled asynchronously. A successful POST /order means the order was accepted — not yet fulfilled. Track the order's status to know the outcome. Statuses | Status | Meaning | Final? | Webhook fires? | soldItem | |---|---|---|---|---| | pending | Accepted, not yet processing | No | No | — | | in-progress | Being fulfilled | No | No | — | | success | Fulfilled | Yes | Yes | Populated | | failed | Could not be fulfilled | Yes | Yes | — | | rejected | Rejected | Yes | Yes | — | Terminal statuses (success, failed, rejected) never change — stop polling once you see one. pending and in-progress are transient. Knowing an order is done - Webhook (recommended): pass a notificationUrl on the order; PalCards POSTs you the full order when it reaches a terminal status (success, failed, or rejected). Webhooks are unsigned and sent once — confirm by re-fetching GET /order/{id} (see Order webhooks). - Polling: call GET /order/{id} every few seconds until status is terminal. On success For a voucher (chargingCard), the delivered card is in soldItem: "soldItem": { "id": 7001, "voucher": { "code": "XXXX-XXXX", "pin": "123456" } } Top‑up (topUp) orders also reach success (the target account is charged), but generally carry no voucher payload. On failure failed / rejected orders do not leave a net charge on your wallet — the charge is rolled back. Inspect the order for the reason and, if appropriate, submit a new order (there is no automatic retry, and re-using the same clientOrderRef does not prevent a duplicate — see Create an order).

Last updated on Jun 07, 2026

Order webhooks

Instead of polling, pass a notificationUrl when you create an order. PalCards sends an HTTP POST to that URL when the order reaches a terminal status (success, failed, or rejected), with the full order as the body. Set it on the order { "productItem": 5001, "customer": { "phone": "+97059…" }, "notificationUrl": "https://your-server.example/webhooks/palcards" } Your endpoint must be reachable over HTTPS. What you receive A POST with the same body shape as GET /order/{id}: { "id": 9001, "status": "success", "type": "chargingCard", "clientOrderRef": "ref-0001", "soldItem": { "id": 7001, "voucher": { "code": "…", "pin": "…" } }, "updatedAt": "2026-06-07T12:00:03.000Z" } Important delivery semantics - Unsigned. There is no signature or shared secret on the request, so don't trust the payload on its own — confirm by calling GET /order/{id} with your API key before acting on the result. - Each notification is a single attempt — no retry. PalCards posts once per status change (about a 5‑minute timeout) and does not resend a POST your endpoint missed. So downtime on your side means a missed notification — always keep GET /order/{id} / polling as your source of truth. - One order can produce several notifications — one per status change. For example, an order that briefly succeeded and was then rolled back sends a POST for success and another for the new status (no single change is ever retried). So make your handler idempotent, keyed on id + status. - Respond quickly with HTTP 2xx; do slow work asynchronously. Recommended pattern 1. Receive the webhook, store the id, return 200 immediately. 2. Asynchronously call GET /order/{id} and act on the fetched status (the authoritative value). 3. As a safety net for missed webhooks, also poll recently created non‑terminal orders. Example receiver (Node.js) import express from "express"; const app = express(); app.use(express.json()); const seen = new Set(); // de-dup by id+status (use a DB in production) app.post("/webhooks/palcards", async (req, res) => { res.sendStatus(200); // 1) acknowledge fast const { id, status } = req.body; const key = `${id}:${status}`; if (seen.has(key)) return; // idempotent seen.add(key); // 2) Don't trust the payload — re-fetch the authoritative order const order = await fetch(`https://api.palcards.ps/order/${id}`, { headers: { "x-api-key": process.env.PALCARDS_API_KEY }, }).then((r) => r.json()); if (order.status === "success") { // 3) deliver order.soldItem.voucher to your customer } }); app.listen(3000);

Last updated on Jun 07, 2026

Errors & rate limits

The API uses standard HTTP status codes. Error bodies use a consistent JSON envelope. Error response shape { "statusCode": 400, "type": "BadRequestException", "timestamp": "2026-06-07T12:00:00.000Z", "path": "/order", "message": "Bad Request Exception", "errors": { "statusCode": 400, "message": ["productItem must be a number", "customer.phone must be a valid phone number"], "error": "Bad Request" } } For validation errors, the field‑level messages are in errors.message[]. Validation failures return 400 (there is no 422). Status codes | Code | Meaning | What to do | |---|---|---| | 200 / 201 | Success | — | | 400 | Invalid request / validation / insufficient balance | Read errors.message; fix the request or top up your wallet | | 401 | Unauthorized | Check the x-api-key header and that your IP is on the key's allowlist | | 403 | Forbidden | Your account/role can't perform this action | | 404 | Not found | Check the path and any {id} | | 429 | Too many requests | Back off (see below) | | 5xx | Server error | Retry later with backoff; if it persists, contact support | Insufficient balance Creating an order your wallet can't cover returns 400 "Insufficient balance" (in errors.message), and no order is created — you are not charged. Check Wallet & balance first. Rate limits (429) Rate limiting is not currently enforced on the dealer endpoints, but the API can return 429 — so design defensively. When it does, the retry delay is in the body, not in headers (there is no Retry-After / X-RateLimit-*): { "statusCode": 429, "type": "RateLimitExceededException", "message": "...", "errors": { "statusCode": 429, "error": "RATE_LIMIT_EXCEEDED", "message": "Rate limit exceeded. Retry after the window resets.", "retryAfterMs": 2000 } } Honor errors.retryAfterMs (or suggestedRetryDelayMs for concurrency limits), then retry with exponential backoff + jitter. Because clientOrderRef does not prevent duplicates, check whether an order already exists before retrying POST /order. Getting help Stuck or seeing unexpected behavior? Email support@palcards.ps, or use the support chat in the PalCards panel. Include the order id, your clientOrderRef, the request path, the response timestamp, and the full error body — these speed up diagnosis.

Last updated on Jun 07, 2026