{"activeVersionTag":"latest","latestAvailableVersionTag":"latest","collection":{"info":{"_postman_id":"70ea23c2-964d-4373-a1c3-0d28c1c0ff71","name":"Orbt Wholesale API v2","description":"# Orbt Wholesale API (v2)\n\nThe Orbt Wholesale API lets you buy digital gift cards and vouchers programmatically, paid from your prepaid Orbt wallets. You get the live catalog with your pricing, place orders, track them and collect the delivered cards, codes and PINs.\n\nOnly **instantly delivered (realtime) products** are sold through the API. Products delivered later by a supplier stay available in the Orbt portal.\n\n## Environments\n\n| Environment | Base URL | Use it for |\n| --- | --- | --- |\n| UAT | `https://api-partner-uat.orbt.com` | Integration and testing. Separate credentials and wallets. |\n| PROD | `https://api-partner.orbt.com` | Live orders. Real funds are debited and real cards are issued. |\n\nAll endpoints below sit under `{{baseUrl}}/api/wholesale/v2`. UAT and PROD use different credentials; keys from one do not work on the other.\n\n## Before you start\n\nYour Orbt account manager sets up your API access per environment and gives you:\n\n- **API key** (`x-api-key`)\n    \n- **Client ID** (`x-client-id`)\n    \n- **HMAC secret**, used to sign requests. It is never sent over the wire.\n    \n- **IP allow-list**: send us the public egress IPs your servers call from. Calls from other IPs are rejected.\n    \n- **Permissions** on the key: `accounts:read`, `products:read`, `orders:read`, `orders:write`.\n    \n\nCall the API from your backend only. Never put the API key or HMAC secret in a browser or mobile app.\n\n## Quick start\n\n<img src=\"https://mintlify.s3.us-west-1.amazonaws.com/orbt/images/01-integration-flow.png\" alt=\"Integration%20Flow\">\n\n1. **Check funds**: `GET /accounts/balance` returns your wallets and their balances.\n    \n2. **Load the catalog**: `GET /products` returns everything you can buy, with your pricing.\n    \n3. **Lock an FX rate** (only when paying from a wallet in another currency): `GET /products/{productId}?currency=EUR` returns the amount you will pay and an `fxQuoteId`.\n    \n4. **Place the order**: `POST /orders` with your own `orderReference`. The wallet is debited and the order starts as `PENDING`.\n    \n5. **Track it**: poll `GET /orders/{orderReference}` until it is `FULFILLED` or `FAILED`.\n    \n6. **Collect the cards**: `GET /orders/{orderReference}/fulfillments` returns codes, PINs and a hosted gift card link.\n    \n\n### Using this collection in Postman\n\n1. Click **Run in Postman** (or import the collection) and import the **Orbt UAT** environment.\n    \n2. Fill in `apiKey`, `clientId`, `hmacSecret` and `walletId` in the environment.\n    \n3. Send the requests top to bottom. The collection signs every request for you and remembers `productId`, `fxQuoteId` and `orderReference` between calls.\n    \n\n## Authentication and request signing\n\nEvery request carries five headers:\n\n| Header | Value |\n| --- | --- |\n| `x-api-key` | Your API key |\n| `x-client-id` | Your client ID |\n| `x-timestamp` | Current time in UTC, ISO 8601, e.g. `2026-10-26T11:20:00.000Z` |\n| `x-body-hash` | Base64 of the SHA-256 hash of the raw request body. For requests without a body (all GETs), hash the empty string. |\n| `x-signature` | Base64 of HMAC-SHA256 over the string to sign, keyed with your HMAC secret |\n\n<img src=\"https://mintlify.s3.us-west-1.amazonaws.com/orbt/images/02-request-signing.png\">\n\nThe string to sign is four values joined by a newline (`\\n`), with no trailing newline:\n\n```\nMETHOD\nTIMESTAMP\nCLIENT_ID\nBODY_HASH\n\n ```\n\nRules that trip people up:\n\n- `METHOD` is upper case: `GET`, `POST`.\n    \n- Hash the **exact bytes you send**. Serialize the JSON once, hash that string and send that same string. Re-serializing after hashing changes whitespace or key order and breaks the signature.\n    \n- The HMAC secret is used as a plain UTF-8 string key. Do not Base64-decode it.\n    \n- Generate a fresh timestamp for every request and keep your server clock in sync (NTP).\n    \n- The empty-body hash is always `47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=`.\n    \n\n**Node.js**\n\n``` javascript\nconst crypto = require('crypto');\nfunction orbtHeaders(method, body, { apiKey, clientId, hmacSecret }) {\n  const raw = body ? JSON.stringify(body) : '';\n  const timestamp = new Date().toISOString();\n  const bodyHash = crypto.createHash('sha256').update(raw, 'utf8').digest('base64');\n  const stringToSign = [method.toUpperCase(), timestamp, clientId, bodyHash].join('\\n');\n  const signature = crypto.createHmac('sha256', hmacSecret).update(stringToSign, 'utf8').digest('base64');\n  return {\n    raw, // send exactly this string as the request body\n    headers: {\n      'Content-Type': 'application/json',\n      'x-api-key': apiKey,\n      'x-client-id': clientId,\n      'x-timestamp': timestamp,\n      'x-body-hash': bodyHash,\n      'x-signature': signature,\n    },\n  };\n}\n\n ```\n\n**Python**\n\n``` python\nimport base64, datetime, hashlib, hmac, json\ndef orbt_headers(method, body, api_key, client_id, hmac_secret):\n    raw = json.dumps(body, separators=(\",\", \":\")) if body is not None else \"\"\n    timestamp = datetime.datetime.now(datetime.timezone.utc).isoformat(timespec=\"milliseconds\").replace(\"+00:00\", \"Z\")\n    body_hash = base64.b64encode(hashlib.sha256(raw.encode(\"utf-8\")).digest()).decode()\n    string_to_sign = \"\\n\".join([method.upper(), timestamp, client_id, body_hash])\n    signature = base64.b64encode(\n        hmac.new(hmac_secret.encode(\"utf-8\"), string_to_sign.encode(\"utf-8\"), hashlib.sha256).digest()\n    ).decode()\n    headers = {\n        \"Content-Type\": \"application/json\",\n        \"x-api-key\": api_key,\n        \"x-client-id\": client_id,\n        \"x-timestamp\": timestamp,\n        \"x-body-hash\": body_hash,\n        \"x-signature\": signature,\n    }\n    return raw, headers  # send raw as the body (data=raw), not json=body\n\n ```\n\n**Java**\n\n``` java\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Base64;\nimport java.util.Map;\npublic final class OrbtSigner {\n    public static Map<String, String> headers(String method, String rawBody,\n                                              String apiKey, String clientId, String hmacSecret) throws Exception {\n        String body = rawBody == null ? \"\" : rawBody; // send exactly this string\n        String timestamp = Instant.now().truncatedTo(ChronoUnit.MILLIS).toString();\n        byte[] hash = MessageDigest.getInstance(\"SHA-256\").digest(body.getBytes(StandardCharsets.UTF_8));\n        String bodyHash = Base64.getEncoder().encodeToString(hash);\n        String stringToSign = String.join(\"\\n\", method.toUpperCase(), timestamp, clientId, bodyHash);\n        Mac mac = Mac.getInstance(\"HmacSHA256\");\n        mac.init(new SecretKeySpec(hmacSecret.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\n        String signature = Base64.getEncoder().encodeToString(mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)));\n        return Map.of(\n            \"Content-Type\", \"application/json\",\n            \"x-api-key\", apiKey,\n            \"x-client-id\", clientId,\n            \"x-timestamp\", timestamp,\n            \"x-body-hash\", bodyHash,\n            \"x-signature\", signature);\n    }\n}\n\n ```\n\n## Pricing\n\nEvery product carries a `pricing` object that tells you what you pay against the face value:\n\n| `adjustmentType` | `unit` | Example | You pay for a USD 100 card |\n| --- | --- | --- | --- |\n| `DISCOUNT` | `PERCENTAGE` | `value: 3.0` | USD 97.00 |\n| `FEE` | `PERCENTAGE` | `value: 10.0` | USD 110.00 |\n| `FEE` | `AMOUNT` | `value: 1.0` | USD 101.00 |\n| `NONE` | `null` | `value: null` | USD 100.00 |\n\nProducts are either `FIXED` (one `amount`, the face value) or `VARIABLE` (any amount between `minAmount` and `maxAmount`, sent as `amount` on the order item).\n\n## Wallets and currencies\n\nYou can hold one wallet per currency. Each order is paid from the wallet you pass as `walletId`.\n\nWhen the product currency differs from the wallet currency, Orbt converts at the current rate when the order is placed. The rate used comes back on each item as `fxRate`, with the amount charged as `walletAmount`.\n\nTo know the exact amount before you buy, get a quote first and send its `fxQuoteId` with the order. The order is placed only if that rate is still quoted; otherwise you get `409 FX_QUOTE_CHANGED` and **nothing is charged**. One quote covers one currency pair, so an order that converts several currencies into the wallet currency cannot use a single `fxQuoteId`.\n\n<img src=\"https://mintlify.s3.us-west-1.amazonaws.com/orbt/images/04-fx-quote-flow.png\">\n\n## Orders, idempotency and status\n\n<img src=\"https://mintlify.s3.us-west-1.amazonaws.com/orbt/images/03-order-placement-lifecycle.png\">\n\n**`orderReference`** **is your idempotency key.** Use your own unique ID (max 90 characters) for every new order.\n\n- Sending the same order again with the same `orderReference` returns the order already placed. Nothing is charged twice, so retrying after a timeout or network error is safe.\n    \n- Sending a **different** order under a reference you already used returns `409 ORDER_REFERENCE_IN_USE`.\n    \n\n**Order status** (from `GET /orders/{orderReference}`):\n\n| `status` | Meaning |\n| --- | --- |\n| `PENDING` | Paid and being fulfilled by the supplier. |\n| `FULFILLED` | Cards are ready in `/fulfillments`. |\n| `FAILED` | The order could not be fulfilled. `paid` is `false`. |\n\nThe create order response reports the new order with `status: Open` and `fulfillmentStatus: PENDING`. Treat it as `PENDING` and use Get order for tracking.\n\nEach item also has its own `fulfillmentStatus` and `fulfilledQuantity`. Compare `fulfilledQuantity` with `quantity` to see how many cards of that item were delivered.\n\n**Polling.** Poll Get order every 2 to 5 seconds at first, then back off. Stop on `FULFILLED` or `FAILED`.\n\n## Pagination\n\nTwo styles are supported:\n\n| Style | Parameters | Response `meta.pagination` | Used by |\n| --- | --- | --- | --- |\n| Page | `page` (from 0), `pageSize` (1 to 100) | `type: page`, `page`, `size`, `totalPages`, `totalElements` | List products |\n| Cursor | `limit` (1 to 100), `cursor` | `type: cursor`, `nextCursor`, `hasMore` | List products, List orders |\n\nFor cursor paging, omit `cursor` on the first call, then pass the previous response's `nextCursor` until `hasMore` is `false`.\n\n## Errors\n\nErrors return a non-2xx status and an `error` code you can branch on. Most errors look like this:\n\n``` json\n{\n  \"error\": {\n    \"code\": \"INSUFFICIENT_FUNDS\",\n    \"message\": \"Not enough prepaid funds for currencyId=4\"\n  }\n}\n\n ```\n\nSome validation and not-found errors return a flat body with `code`, `message`, `retryable` and, for field validation, `fieldErrors`. Branch on `code` in both cases.\n\n| HTTP | `code` | What to do |\n| --- | --- | --- |\n| 400 | `VALIDATION_ERROR` / `VALIDATION_FAILED` | Fix the request. See `message` or `fieldErrors`. |\n| 400 | `FX_QUOTE_AMBIGUOUS` | The order converts more than one currency; drop `fxQuoteId` or split the order. |\n| 403 |  | Credentials, signature, timestamp or source IP rejected. Check your headers and IP allow-list. |\n| 404 | `PRODUCT_ERROR` | Product not found or not available to you. |\n| 404 | `ACCOUNT_ERROR` | You have no wallet in the requested currency. |\n| 404 | `ORDER_NOT_FOUND` | No order with that reference on your account. |\n| 409 | `ORDER_REFERENCE_IN_USE` | Reference already used for a different order. Send a new one. |\n| 409 | `FX_QUOTE_CHANGED` | The quoted rate is no longer valid. Nothing was charged. Get a new quote. |\n| 422 | `PRODUCT_ERROR` | A product cannot be ordered through the API (not realtime). |\n| 422 | `INSUFFICIENT_FUNDS` | Top up the wallet, then retry. |\n| 502 | `FULFILLMENT_UNAVAILABLE` | Cards are temporarily unavailable. Retry later; the order is not affected. |\n| 5xx |  | Retry with backoff. For orders, retry with the **same** `orderReference`. |\n\n## Support\n\nQuestions or access requests: contact your Orbt account manager, or send an email to [support@orbt.com](https://mailto:support@orbt.com)","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","isPublicCollection":false,"owner":"38333563","team":6448726,"collectionId":"70ea23c2-964d-4373-a1c3-0d28c1c0ff71","publishedId":"2sBYHUULuT","public":true,"publicUrl":"https://v2-reference.orbt.com","privateUrl":"https://go.postman.co/documentation/38333563-70ea23c2-964d-4373-a1c3-0d28c1c0ff71","customColor":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"FF6C37"},"documentationLayout":"classic-double-column","customisation":{"metaTags":[{"name":"description","value":""},{"name":"title","value":""}],"appearance":{"default":"light","themes":[{"name":"dark","logo":null,"colors":{"top-bar":"212121","right-sidebar":"303030","highlight":"FF6C37"}},{"name":"light","logo":null,"colors":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"FF6C37"}}]}},"version":"8.12.10","publishDate":"2026-10-26T14:03:55.000Z","activeVersionTag":"latest","documentationTheme":"light","metaTags":{"title":"","description":""},"logos":{"logoLight":null,"logoDark":null}},"statusCode":200},"environments":[],"user":{"authenticated":false,"permissions":{"publish":false}},"run":{"button":{"js":"https://run.pstmn.io/button.js","css":"https://run.pstmn.io/button.css"}},"web":"https://www.getpostman.com/","team":{"logo":"https://res.cloudinary.com/postman/image/upload/t_team_logo_pubdoc/v1/team/36f334aa1475e3faa309aa4af59f53c12df1532bd9a52540e242f01d2e9f6898","favicon":"https://orbt.com/favicon.ico"},"isEnvFetchError":false,"languages":"[{\"key\":\"csharp\",\"label\":\"C#\",\"variant\":\"HttpClient\"},{\"key\":\"csharp\",\"label\":\"C#\",\"variant\":\"RestSharp\"},{\"key\":\"curl\",\"label\":\"cURL\",\"variant\":\"cURL\"},{\"key\":\"dart\",\"label\":\"Dart\",\"variant\":\"http\"},{\"key\":\"go\",\"label\":\"Go\",\"variant\":\"Native\"},{\"key\":\"http\",\"label\":\"HTTP\",\"variant\":\"HTTP\"},{\"key\":\"java\",\"label\":\"Java\",\"variant\":\"OkHttp\"},{\"key\":\"java\",\"label\":\"Java\",\"variant\":\"Unirest\"},{\"key\":\"javascript\",\"label\":\"JavaScript\",\"variant\":\"Fetch\"},{\"key\":\"javascript\",\"label\":\"JavaScript\",\"variant\":\"jQuery\"},{\"key\":\"javascript\",\"label\":\"JavaScript\",\"variant\":\"XHR\"},{\"key\":\"c\",\"label\":\"C\",\"variant\":\"libcurl\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Axios\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Native\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Request\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Unirest\"},{\"key\":\"objective-c\",\"label\":\"Objective-C\",\"variant\":\"NSURLSession\"},{\"key\":\"ocaml\",\"label\":\"OCaml\",\"variant\":\"Cohttp\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"cURL\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"Guzzle\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"HTTP_Request2\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"pecl_http\"},{\"key\":\"powershell\",\"label\":\"PowerShell\",\"variant\":\"RestMethod\"},{\"key\":\"python\",\"label\":\"Python\",\"variant\":\"http.client\"},{\"key\":\"python\",\"label\":\"Python\",\"variant\":\"Requests\"},{\"key\":\"r\",\"label\":\"R\",\"variant\":\"httr\"},{\"key\":\"r\",\"label\":\"R\",\"variant\":\"RCurl\"},{\"key\":\"ruby\",\"label\":\"Ruby\",\"variant\":\"Net::HTTP\"},{\"key\":\"shell\",\"label\":\"Shell\",\"variant\":\"Httpie\"},{\"key\":\"shell\",\"label\":\"Shell\",\"variant\":\"wget\"},{\"key\":\"swift\",\"label\":\"Swift\",\"variant\":\"URLSession\"}]","languageSettings":[{"key":"csharp","label":"C#","variant":"HttpClient"},{"key":"csharp","label":"C#","variant":"RestSharp"},{"key":"curl","label":"cURL","variant":"cURL"},{"key":"dart","label":"Dart","variant":"http"},{"key":"go","label":"Go","variant":"Native"},{"key":"http","label":"HTTP","variant":"HTTP"},{"key":"java","label":"Java","variant":"OkHttp"},{"key":"java","label":"Java","variant":"Unirest"},{"key":"javascript","label":"JavaScript","variant":"Fetch"},{"key":"javascript","label":"JavaScript","variant":"jQuery"},{"key":"javascript","label":"JavaScript","variant":"XHR"},{"key":"c","label":"C","variant":"libcurl"},{"key":"nodejs","label":"NodeJs","variant":"Axios"},{"key":"nodejs","label":"NodeJs","variant":"Native"},{"key":"nodejs","label":"NodeJs","variant":"Request"},{"key":"nodejs","label":"NodeJs","variant":"Unirest"},{"key":"objective-c","label":"Objective-C","variant":"NSURLSession"},{"key":"ocaml","label":"OCaml","variant":"Cohttp"},{"key":"php","label":"PHP","variant":"cURL"},{"key":"php","label":"PHP","variant":"Guzzle"},{"key":"php","label":"PHP","variant":"HTTP_Request2"},{"key":"php","label":"PHP","variant":"pecl_http"},{"key":"powershell","label":"PowerShell","variant":"RestMethod"},{"key":"python","label":"Python","variant":"http.client"},{"key":"python","label":"Python","variant":"Requests"},{"key":"r","label":"R","variant":"httr"},{"key":"r","label":"R","variant":"RCurl"},{"key":"ruby","label":"Ruby","variant":"Net::HTTP"},{"key":"shell","label":"Shell","variant":"Httpie"},{"key":"shell","label":"Shell","variant":"wget"},{"key":"swift","label":"Swift","variant":"URLSession"}],"languageOptions":[{"label":"C# - HttpClient","value":"csharp - HttpClient - C#"},{"label":"C# - RestSharp","value":"csharp - RestSharp - C#"},{"label":"cURL - cURL","value":"curl - cURL - cURL"},{"label":"Dart - http","value":"dart - http - Dart"},{"label":"Go - Native","value":"go - Native - Go"},{"label":"HTTP - HTTP","value":"http - HTTP - HTTP"},{"label":"Java - OkHttp","value":"java - OkHttp - Java"},{"label":"Java - Unirest","value":"java - Unirest - Java"},{"label":"JavaScript - Fetch","value":"javascript - Fetch - JavaScript"},{"label":"JavaScript - jQuery","value":"javascript - jQuery - JavaScript"},{"label":"JavaScript - XHR","value":"javascript - XHR - JavaScript"},{"label":"C - libcurl","value":"c - libcurl - C"},{"label":"NodeJs - Axios","value":"nodejs - Axios - NodeJs"},{"label":"NodeJs - Native","value":"nodejs - Native - NodeJs"},{"label":"NodeJs - Request","value":"nodejs - Request - NodeJs"},{"label":"NodeJs - Unirest","value":"nodejs - Unirest - NodeJs"},{"label":"Objective-C - NSURLSession","value":"objective-c - NSURLSession - Objective-C"},{"label":"OCaml - Cohttp","value":"ocaml - Cohttp - OCaml"},{"label":"PHP - cURL","value":"php - cURL - PHP"},{"label":"PHP - Guzzle","value":"php - Guzzle - PHP"},{"label":"PHP - HTTP_Request2","value":"php - HTTP_Request2 - PHP"},{"label":"PHP - pecl_http","value":"php - pecl_http - PHP"},{"label":"PowerShell - RestMethod","value":"powershell - RestMethod - PowerShell"},{"label":"Python - http.client","value":"python - http.client - Python"},{"label":"Python - Requests","value":"python - Requests - Python"},{"label":"R - httr","value":"r - httr - R"},{"label":"R - RCurl","value":"r - RCurl - R"},{"label":"Ruby - Net::HTTP","value":"ruby - Net::HTTP - Ruby"},{"label":"Shell - Httpie","value":"shell - Httpie - Shell"},{"label":"Shell - wget","value":"shell - wget - Shell"},{"label":"Swift - URLSession","value":"swift - URLSession - Swift"}],"layoutOptions":[{"value":"classic-single-column","label":"Single Column"},{"value":"classic-double-column","label":"Double Column"}],"versionOptions":[],"environmentOptions":[{"value":"0","label":"No Environment"}],"canonicalUrl":"https://v2-reference.orbt.com/view/metadata/2sBYHUULuT"}