{"activeVersionTag":"latest","latestAvailableVersionTag":"latest","collection":{"info":{"_postman_id":"c2d77dde-b214-4f18-8f97-a3ad93190472","name":"mouroam Reseller API","description":"A server-to-server API so you can resell mouroam's catalog — SMM engagement, ready-made social/email accounts, VPN access, travel phone-number eSIMs, and mobile proxies — on your own storefront, without your customers ever seeing which upstream provider sits behind it.\n\n**This documentation is public and free to read.** Every endpoint, field, and example below was pulled directly from the live implementation, not written from memory — you do not need an account to browse it. You DO need your own live API key to actually call anything (see \"Getting a key\" below); every request in this collection inherits Bearer auth from a blank `api_key` collection variable, so nothing here can leak or spend a real key.\n\n## Getting a key\n\n1. Sign up for a mouroam account and fund your wallet to at least the reseller balance minimum shown on `/dashboard/account` (a pre-filter, not the real gate).\n2. From `/dashboard/account`, submit a \"Request API access\" application — an optional note about your use case helps the review.\n3. A mouroam admin reviews and manually approves or denies the request. Balance alone never auto-approves.\n4. On approval, your key is minted and revealed **exactly once**, the next time you visit your own dashboard. Copy it immediately — it's a raw secret (`sk_live_...`) and cannot be shown again. There is currently no self-service rotation; contact support if you lose it or need it revoked and reissued.\n5. Set the `api_key` variable in this collection (Variables tab, top right) to that value and you're live.\n\n## The four-step contract\n\n```\nGET  /catalog?kind=...        → browse what you can resell\nPOST /quotes                  → lock in a real, server-computed price (valid 10 minutes)\nPOST /orders                  → place the order against that quote\nGET  /orders/{id}             → poll for status, or configure a webhook (see below)\nGET  /orders/{id}/delivery    → fetch credentials/activation info once ready\n```\n\nNothing you send is ever trusted as a price. The server recomputes it at both quote time and order time, and refuses to charge more than the quote you approved — if the live price moved in between, the order lands `failed` with `error_code: \"price_changed\"` instead of silently overcharging you. Get a fresh quote and retry with a new `external_order_id`.\n\nTwo endpoints round out the API without being part of this core flow: `GET /balance` (your account's current spendable balance) and, for a fulfilled `smm` order, `GET`/`POST /orders/{id}/refill` (see \"Refills\" below). `/smm/orders` and `/smm/orders/{id}` are also available as plain aliases for `/orders` and `/orders/{id}`, if you prefer a kind-scoped path — identical behavior, no separate rate limit.\n\n## Auth\n\n```\nAuthorization: Bearer sk_live_your_own_key_here\nContent-Type: application/json   (POST only)\n```\n\nA key resolves to exactly one mouroam account and spends that account's wallet balance through the same debit path the mouroam dashboard itself uses — it grants no other access. An unknown key and a revoked key are indistinguishable: both return `401 unauthorized`, so a caller can never probe which part of a credential was wrong. A revoked key does not affect your account's underlying access approval — contact support for a replacement key rather than reapplying.\n\n## Product kinds\n\n| `kind` | what it is | `quantity` means | needs `link`? | has `GET /orders/{id}/delivery`? |\n|---|---|---|---|---|\n| `smm` | Social engagement (followers/likes/views/comments/traffic/SEO) across Instagram, TikTok, YouTube, Twitter/X, Facebook, Telegram | a real unit count (or set automatically from `comments.length` for a Custom Comments product) | yes — a public profile/post URL, tracking params stripped server-side | no — status lives entirely on the order (`link`, `start_count`, `remains`, `refillable`) |\n| `social_account` | A ready-made social account (login credentials) | number of accounts, 1+, capped by live stock | no | yes — `accounts[].fields` |\n| `email_account` | A ready-made email account (Gmail/Outlook/Yahoo/Protonmail) | number of accounts, 1+, capped by live stock | no | yes — `accounts[].fields` |\n| `vpn` | A VPNClient app account, 7 or 30 day access | always `1` | no | yes — `username`/`password`/`app` |\n| `esim` | A real phone-number eSIM (voice + SMS + data; travel/calling use only) | always `1` | no | yes — `activation_code`/`lpa_string`/`iccid`/`phone_number` |\n| `esim_topup` | A data/voice/SMS top-up for an eSIM you already bought through this API | always `1` | no | no — it extends the existing eSIM in place; check the order snapshot instead |\n| `mobile_proxy` | A fixed-term mobile proxy (real carrier 5G IP, SOCKS5 + HTTP, unlimited data for the term) | always `1` | no | yes — a single opaque `credential` string |\n\nThere is no `order` (disposable OTP numbers) or `rental` kind — both are permanently disabled and won't be added without a separate explicit decision.\n\n`esim_topup` has one extra catalog requirement: pass `&esim_id=<your already-purchased eSIM's id>` on `GET /catalog` — that eSIM must already be `active` (fully provisioned) before it has any top-up options.\n\nA `smm` catalog product (and a placed `smm` order) also carries `refill_mode` (`\"api\"`/`\"manual\"`/`\"none\"`) and, when applicable, `refill_window_days` — see \"Refills\" below.\n\n## Response envelope\n\nEvery response is one of:\n\n```json\n{ \"success\": true, \"data\": { ... } }\n{ \"success\": false, \"error\": \"human-readable message\", \"code\": \"machine_readable_code\" }\n```\n\n`X-Request-Id` is set on every response — include it when reporting an issue. A `429` always sets `Retry-After: 300`.\n\n## Rate limits\n\n- **Order-placing calls** (`POST /orders`, `POST /orders/{id}/refill`): 30 per 5 minutes.\n- **Read calls** (catalog, quotes, order status/list, delivery, balance, `GET /orders/{id}/refill`): 120 per 5 minutes.\n- Both buckets are keyed by **account**, not by individual key — if you ever hold more than one active key, they share the same two buckets.\n- A broad backstop (600 requests / 5 minutes, keyed by IP) is checked *before* your key is even validated, so spamming invalid/missing keys still costs rate-limit budget.\n\n## Idempotency\n\n`external_order_id` (yours, 1–128 characters) is what makes retrying safe: resubmitting the exact same `external_order_id` always returns the exact same order — it is never placed twice. If that id was already used with a `quote_id` resolving to *different* order parameters, the request is refused as `409 idempotency_conflict` rather than silently substituted. On a genuinely unknown outcome (`503 temporarily_unavailable`, a network error on your end) always retry with the *same* `external_order_id` — never mint a new one just because a call failed; a new id there risks a real double order.\n\n## Order status values\n\n`queued`, `processing`, `pending`, `purchasing`, `in_progress`, `partial`, `active`, `completed`, `delivered`, `canceled`, `refunded`, `failed`, `review_required`.\n\n`review_required` means the order needs a human on our side before it can move — but it covers **two genuinely different situations**, so always branch on the order's `error_code`, never on the status alone:\n\n- `error_code: \"confirmation_pending\"` — the last purchase attempt got an unclear response from the upstream supplier (a timeout, a 5xx). We genuinely don't know whether it went through, so it's never auto-refunded and never silently retried on top of a possibly-real charge. **This one does not resolve on its own on any fixed schedule** — it requires a human to confirm the real outcome. If it sits for more than a few hours, contact support with the order id rather than waiting indefinitely.\n- `error_code: \"delivery_overdue\"` or `\"delivery_stalled\"` (`smm` only) — the purchase itself was confirmed fine; it's *delivery* that is late, or has stopped advancing. These carry a 24-hour internal review target. `delivery_stalled` can clear **by itself** if the supplier resumes delivering, in which case the order simply returns to its normal status (`in_progress` etc.) with no action from you — treat it as \"watch\", not \"escalate immediately\".\n\nAn integration that checks `error_code === \"confirmation_pending\"` to detect the review state will silently miss the two delivery reasons. Match on the `review_required` status, then read `error_code` for the reason.\n\n`refunded_usd_cents` / `net_price_usd_cents` are always present on an order and reflect any partial or full refund recorded so far — trust these over `status` alone for your own accounting (an SMM order that under-delivers can be *partially* refunded while still showing `in_progress`).\n\nA freshly placed order (`POST /orders` returning `202`) starts with almost nothing filled in — just `id`, `external_order_id`, `kind`, `product_id`, `status: \"queued\"`, `version: 0`, and timestamps. `quantity`/`price_usd_cents`/etc. only appear once the first status snapshot lands, moments later — poll `GET /orders/{id}` or wait for the webhook rather than assuming those fields exist on the immediate response.\n\n## Refills\n\nA routed `smm` order (followers/likes/views/subscribers) that drops after delivery can request a refill through `GET`/`POST /orders/{id}/refill` (see the \"Refills\" folder). Whether a refill is possible at all, and how it gets fulfilled, is driven entirely by that product's `refill_mode` — already visible on the catalog entry before you ever order, and on the order itself afterward:\n\n- `\"none\"` — this supplier tier carries no refill guarantee. The endpoint always returns `409 refill_unavailable`.\n- `\"api\"` — the supplier's own refill endpoint is called directly the moment you `POST`. `status` reflects its real, polled outcome: `submitting` → `pending` → `completed`/`rejected`.\n- `\"manual\"` — this supplier only offers a refill guarantee outside any automatable API. A `POST` queues a `manual_review` request for a human on our side to verify against the real supplier record and honor it — not auto-resolved, but not ignored either.\n\n`refill_window_days` is how many days after the order's `completed_at` a refill can still be requested at all. At most one active refill request per order at a time, and at most one submission every 24 hours per order — hitting either limit returns `409 refill_unavailable`, same as requesting one outside the window or on an order that never actually fulfilled with a supplier.\n\n## Errors worth handling specifically\n\n| HTTP | `code` | Meaning |\n|---|---|---|\n| 400 | `invalid_request` | A field was missing, the wrong type, or too long/short — see `error` for which one. |\n| 400 | `invalid_product_kind` | The `kind` query param isn't one of the seven supported kinds. |\n| 400 | `invalid_link` | `link` isn't a public `http(s)` URL, or embeds credentials. |\n| 400 | `invalid_comments` | `comments` is required/forbidden for this product, or has 0 or >1000 entries. |\n| 400 | `invalid_quantity` | Quantity is outside the product's current bounds, or isn't `1` for a one-at-a-time product. |\n| 400 | `no_credentials` | You called delivery on a kind that has nothing to deliver (`smm`, `esim_topup`) — use order status instead. |\n| 401 | `unauthorized` | Missing, invalid, or revoked API key. |\n| 404 | `not_found` | The product, quote, or order id doesn't exist (or isn't yours). |\n| 409 | `idempotency_conflict` | Your `external_order_id` was already used with different order parameters. Use a new id. |\n| 409 | `quote_expired` | The quote is more than 10 minutes old. Get a fresh one and place the order again. |\n| 409 | `product_unavailable` | The product's live availability/bounds changed between quote and order. Refresh the catalog and re-quote. |\n| 409 | `esim_not_ready` | You tried to quote/browse top-ups for an eSIM that isn't fully provisioned yet. |\n| 409 | `refill_unavailable` | This order can't currently request a refill — it isn't a fulfilled `smm` order, it's outside its `refill_window_days`, or one was already requested in the last 24 hours. See \"Refills\" above. |\n| 409 | `delivery_pending` | The order exists but isn't ready to deliver yet. Poll order status first. |\n| 413 | `body_too_large` | Request body over 64KB. |\n| 415 | `unsupported_media_type` | Missing/wrong `Content-Type` on a `POST` — must be `application/json`. |\n| 429 | `rate_limited` | Back off; `Retry-After` is set to `300`. |\n| 503 | `temporarily_unavailable` | Always safe to retry with the exact same request (same `external_order_id`). |\n\nSeparately, an **order itself** can carry its own `error_code` once it lands `failed` or `review_required` — a field on the order object, not an HTTP status:\n\n| `error_code` | Meaning |\n|---|---|\n| `insufficient_balance` | The mouroam account behind your key was out of funds at purchase time. Top it up and retry with a **new** `external_order_id`. |\n| `price_changed` | The live price moved past your quote's approved price between quote and purchase. Get a fresh quote and retry with a new `external_order_id`. |\n| `product_unavailable` | The product became unavailable/out of bounds at the exact moment of purchase. Refresh the catalog and re-quote. |\n| `confirmation_pending` | On a `review_required` order — the purchase attempt's outcome is still being confirmed by a human. Keep polling; do not retry. |\n| `delivery_overdue` | On a `review_required` `smm` order — the purchase was confirmed, but delivery has passed its expected window. Under review with a 24-hour target. Do not retry. |\n| `delivery_stalled` | On a `review_required` `smm` order — the purchase was confirmed, but delivery has stopped advancing. Can clear by itself if the supplier resumes, returning the order to its normal status. Do not retry. |\n\n## Webhooks\n\nThere is currently no self-service API to register your own webhook — contact mouroam with your endpoint URL and an admin wires it up against your account (per account, not per key, so rotating a key never silently breaks delivery).\n\nOn any change to an order's public snapshot, mouroam `POST`s to your URL:\n\n```\nPOST <your webhook_url>\nContent-Type: application/json\nX-Webhook-Id: <event id>\nX-Webhook-Timestamp: <unix seconds>\nX-Webhook-Signature: v1=<hex hmac>\nX-Webhook-Event: order.updated\n```\n\n```json\n{\n  \"id\": \"event-uuid\",\n  \"event\": \"order.updated\",\n  \"created_at\": \"2026-09-16T15:12:03.000Z\",\n  \"version\": 3,\n  \"data\": {\n    \"status\": \"completed\",\n    \"currency\": \"USD\",\n    \"quantity\": 500,\n    \"price_usd_cents\": 2495,\n    \"refunded_usd_cents\": 0,\n    \"net_price_usd_cents\": 2495,\n    \"id\": \"order-uuid\",\n    \"external_order_id\": \"your-own-order-id\"\n  }\n}\n```\n\n**Important:** `data` is the raw internal status snapshot plus `id`/`external_order_id` merged in — it is *not* byte-identical to `GET /orders/{id}`. It does **not** include `kind`, `product_id`, `created_at`, or `updated_at` (those are added only by the `GET /orders/{id}` response). It also never includes refill state — refills are tracked separately via `GET /orders/{id}/refill` and do not trigger an `order.updated` webhook of their own. If your handler needs those, call `GET /orders/{id}` on receipt rather than assuming the webhook body has everything.\n\nVerify the signature yourself before trusting a callback:\n\n```\nexpected = HMAC_SHA256(your_webhook_secret, \"<X-Webhook-Timestamp>.\" + <raw request body bytes>)\nvalid = hex(expected) === the part of X-Webhook-Signature after \"v1=\"\n```\n\nYour endpoint must be a public HTTPS URL on port 443, no embedded credentials, no fragment, and DNS resolving only to public (non-private, non-loopback, non-link-local) addresses — anything else is rejected at configuration time. Delivery retries on failure with exponential backoff starting at 1 minute and doubling each attempt, capped at 24 hours between attempts, for several days; a `2xx` response stops retries. **Make your handler idempotent on `(order id, version)`** — a retried delivery and your own poll can both land. If your endpoint is down for an extended stretch, `GET /orders/{id}` still works — this API never depends on a webhook actually arriving.\n\n## Support\n\nQuestions, a webhook URL to register, or a lost/exposed key that needs revoking and reissuing: contact mouroam support through the usual channel linked from your dashboard. Include the `X-Request-Id` from any failing response you're asking about.","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","isPublicCollection":true,"owner":"15852868","team":20622470,"collectionId":"c2d77dde-b214-4f18-8f97-a3ad93190472","publishedId":"2sBYB1MTVS","public":true,"publicUrl":"https://documenter-api.postman.tech/view/15852868/2sBYB1MTVS","privateUrl":"https://go.postman.co/documentation/15852868-c2d77dde-b214-4f18-8f97-a3ad93190472","customColor":{"top-bar":"#FFFFFF","highlight":"#0D9488","right-sidebar":"#FFFFFF"},"documentationLayout":"classic-single-column","customisation":{"metaTags":[{"value":"mouroam Reseller API","name":"title"},{"value":"Public documentation for mouroam's reseller API — browse for free; live requests require your own sk_live_... API key.","name":"description"}],"appearance":{"themes":[{"colors":{"top-bar":"#FFFFFF","highlight":"#0D9488","right-sidebar":"#FFFFFF"},"name":"light"},{"colors":{"top-bar":"#141821","highlight":"#2DD4BF","right-sidebar":"#141821"},"name":"dark"}],"default":"dark"}},"version":"8.12.8","publishDate":"2026-09-16T17:24:23.000Z","activeVersionTag":"latest","documentationTheme":"light","metaTags":{"title":"mouroam Reseller API","description":"Public documentation for mouroam's reseller API — browse for free; live requests require your own sk_live_... API key."},"logos":{}},"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/074c53bcd285ff31779117d4dd87bbdec16cbf762ad3ee03712428a9c6e3e120","favicon":""},"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://documenter.gw.postman.com/view/metadata/2sBYB1MTVS"}