Every endpoint of the psyke Reseller API. All require the X-Reseller-API-Key header, and all can return the shared auth (401) and throttle (429) errors from the error reference.
Base URL: https://api.p-syke.com/api/reseller. Request bodies: read endpoints take none; POST /orders is multipart/form-data. Responses are JSON, except GET /orders/{order_id}/result, which is a file download.
GET/api/reseller/services
Returns the full service catalog (11 services). The code is what you pass as service_code to POST /orders; price_per_number is the USD price charged per number.
import requests
r = requests.get(f"{BASE}/services", headers={"X-Reseller-API-Key": KEY})
r.raise_for_status()
for s in r.json()["services"]:
print(s["code"], s["price_per_number"])
Node.js
const r = await fetch(`${BASE}/services`, { headers: { "X-Reseller-API-Key": KEY } });
if (!r.ok) throw new Error(`services failed: ${r.status}`);
const { services } = await r.json();
for (const s of services) console.log(s.code, s.price_per_number);
Response 200
Response schema
Field
Type
Description
services
array<Service>
The 11 resellable services.
services[].code
string
Service code, e.g. "tg-active". Pass as service_code on POST /orders.
services[].name
string
Human-readable service name.
services[].platform
string
Platform label: telegram, whatsapp, facebook, instagram, amazon, binance, or other.
services[].price_per_number
number
USD charged per accepted number. Small values may serialize in scientific notation (e.g. 1.544e-05).
You exceeded the rate limit (120/min, per API key).
GET/api/reseller/countries
Returns the supported country catalog (the FMT country list filtered to valid 2-letter ISO codes). Use code (uppercase ISO-2) as the country value on POST /orders.
import requests
r = requests.get(f"{BASE}/countries", headers={"X-Reseller-API-Key": KEY})
r.raise_for_status()
codes = [c["code"] for c in r.json()["countries"]]
print(len(codes), "countries")
Node.js
const r = await fetch(`${BASE}/countries`, { headers: { "X-Reseller-API-Key": KEY } });
if (!r.ok) throw new Error(`countries failed: ${r.status}`);
const { countries } = await r.json();
console.log(countries.length, "countries");
Response 200
Response schema
Field
Type
Description
countries
array<Country>
Supported countries, in catalog order (not alphabetical).
countries[].code
string
ISO-2 code, uppercase. Pass as country on POST /orders.
countries[].name
string
English country name (no flag). A few rows have name == code — real catalog data.
countries[].area_code
string
International dialing prefix, e.g. "1" for US, "55" for Brazil.
You exceeded the rate limit (120/min, per API key).
POST/api/reseller/orders
Submit a phone-number list for scanning. This is the only write endpoint and the only one that spends your wallet. The request is multipart/form-data (file upload).
Send an Idempotency-Key header (any unique string, ≤ 64 chars) so a network retry replays the original order (HTTP 200 with "replayed": true) instead of charging twice. Without it, every retry that reaches the server creates a new, separately charged order.
Request headers
Field
Type
Description
X-Reseller-API-Key
string (required)
Your API key.
Content-Type
multipart/form-data
Set automatically by curl -F / FormData.
Idempotency-Key
string (optional, ≤ 64 chars)
Replay guard. Scoped to your account.
Request body (multipart form fields)
Field
Type
Description
service_code
string (required)
One of the 11 catalog codes from GET /services. Trimmed server-side.
country
string (required)
ISO-2 code; trimmed then uppercased. Must be in GET /countries.
file
file (required)
Phone-number list, one number per line, UTF-8, digits only (no + prefix). Max 20 MB.
import requests
with open("numbers.txt", "rb") as fh:
r = requests.post(
f"{BASE}/orders",
headers={"X-Reseller-API-Key": KEY, "Idempotency-Key": "order-2026-07-17-001"},
data={"service_code": "tg-active", "country": "US"},
files={"file": fh},
)
r.raise_for_status()
order = r.json()
print(order["order_id"], "cost", order["cost"])
Node.js
import { readFile } from "node:fs/promises";
const form = new FormData();
form.set("service_code", "tg-active");
form.set("country", "US");
form.set("file", new Blob([await readFile("numbers.txt")]), "numbers.txt");
const r = await fetch(`${BASE}/orders`, {
method: "POST",
headers: { "X-Reseller-API-Key": KEY, "Idempotency-Key": "order-2026-07-17-001" },
body: form,
});
if (!r.ok) throw new Error(`submit failed: ${r.status}`);
const order = await r.json();
console.log(order.order_id, "cost", order.cost);
Response 201
Response schema
Field
Type
Description
order_id
string
UTC microsecond timestamp, format YYYYMMDDThhmmssffffffZ (chronologically sortable). Rare same-microsecond collisions get a "-<hex>" suffix. Treat as opaque.
status
string
Order status right after submission — normally "queued".
service_code
string
Echo of the submitted service code.
country
string
Echo of the normalized (uppercased) country code.
input_count
integer
Count of valid, deduplicated numbers accepted and billed.
cost
string
Total charge as a fixed 6-decimal string = input_count × price_per_number.
wallet_balance_after
string
Your wallet balance after the charge (decimal string).
replayed
boolean (only on replay)
Present and true only when an Idempotency-Key matched an existing order; the response is then HTTP 200, not 201.
No order matches that id for your account (never existed, malformed id, or belongs to another tenant).
GET/api/reseller/orders/{order_id}/result
Download the finished result. The success response is a FILE (CSV or TSV), not JSON. Only call it once GET /orders/{order_id} reports status == "completed".
import requests
res = requests.get(f"{BASE}/orders/{order_id}/result", headers={"X-Reseller-API-Key": KEY})
res.raise_for_status()
with open(f"{order_id}_result.csv", "wb") as out:
out.write(res.content)
Node.js
import { writeFile } from "node:fs/promises";
const res = await fetch(`${BASE}/orders/${orderId}/result`, { headers: { "X-Reseller-API-Key": KEY } });
if (!res.ok) throw new Error(`download failed: ${res.status}`);
await writeFile(`${orderId}_result.csv`, Buffer.from(await res.arrayBuffer()));
Response 200
The success response is a file download (CSV or TSV), not JSON. Use the Content-Type / Content-Disposition header to pick the extension.