SnappyDigits
Programmatically list services, place orders, and track deliveries. Suitable for resellers, custom storefronts, and AI agents.
https://snappydigits.com/api/v1The SnappyDigits Reseller API lets you sell account services (social media accounts, Google Voice, etc.) directly from your own storefront. Purchases deduct from your SnappyDigits balance. No inventory management required; we handle fulfillment.
Base URL
https://snappydigits.com/api/v1Format
JSON (application/json)Auth
Bearer token (API key)All requests must include your API key in the Authorization header using the Bearer scheme. Generate a key from the API Access page.
Authorization: Bearer sd_live_a1b2c3d4e5f6...All API keys start with sd_live_ followed by 64 hex characters (72 chars total). Keys are hashed using SHA-256 before storage; we never retain the plain-text key.
Your webhook signing secret is a unique 64-char hex string used to verify that incoming webhook requests genuinely came from SnappyDigits (see Webhooks for verification examples). Retrieve it from the keys endpoint:
GET /api/user/api-keys
Authorization: Bearer sd_live_...
// Response includes:
{
"webhookSecret": "a3f9b2c1d4e5...", // 64 hex chars — treat like a password
...
}All error responses share the same envelope:
{
"ok": false,
"error": "Human-readable message",
"code": "MACHINE_READABLE_CODE"
}| HTTP | code | Meaning |
|---|---|---|
| 200 | — | Success |
| 201 | — | Resource created (new order) |
| 400 | BAD_REQUEST | Missing or invalid request field |
| 401 | UNAUTHORIZED | Missing, invalid, or revoked API key |
| 402 | INSUFFICIENT_BALANCE | Account balance too low for the order |
| 404 | NOT_FOUND | Resource does not exist (or belongs to another account) |
| 409 | OUT_OF_STOCK | Service has no stock available right now. Try again later. |
| 409 | CONFLICT | State conflict (e.g. already cancelled) |
| 429 | RATE_LIMITED | Too many requests. Slow down and retry. |
| 503 | SERVICE_UNAVAILABLE | Service is paused or temporarily unavailable |
Services are account products available for purchase. Stock and pricing can change at any time, so always fetch fresh before displaying to your customers.
/api/v1/servicesList all active purchasable services{
"ok": true,
"services": [
{
"id": "clxyz123",
"name": "TikTok Accounts",
"description": "Aged TikTok profiles ready for use.",
"price": 9.90,
"stock": 3,
"delivery": "instant",
"logoUrl": "https://cdn.simpleicons.org/tiktok",
"createdAt": "2026-01-15T10:00:00.000Z"
},
{
"id": "clxyz456",
"name": "Google Voice Numbers",
"description": "US Google Voice number with account credentials.",
"price": 24.00,
"stock": null,
"delivery": "manual",
"logoUrl": null,
"createdAt": "2026-01-20T12:00:00.000Z"
}
]
}| Field | Type | Description | |
|---|---|---|---|
| id | string | optional | Unique service ID. Use this when placing an order. |
| name | string | optional | Display name. |
| description | string|null | optional | Optional longer description. |
| price | number | optional | Price per unit in USD. |
| stock | number|null | optional | Units in stock. null for manual/unlimited services. |
| delivery | "instant"|"manual" | optional | "instant" = auto-delivered. "manual" = delivered by admin within hours. |
| logoUrl | string|null | optional | Icon URL for displaying the service. |
An order deducts from your balance and fulfills credentials. Delivery mode depends on the service:
Instant delivery
Items are returned immediately in the 201 response. No polling needed. status is already "delivered" and items[] is populated in the POST reply itself.
Manual delivery
An admin fills the order, typically within a few hours. The POST response returns status: "pending" and items: []. Poll GET /orders/:id every 15–30s until status === "delivered", then read items[].
/api/v1/ordersPlace a new order{
"serviceId": "clxyz123",
"quantity": 2
}| Field | Type | Req? | Description |
|---|---|---|---|
| serviceId | string | required | ID from GET /api/v1/services. |
| quantity | integer | optional | Units to purchase. Default 1, min 1, max 100 per order. |
For instant services the credentials are in the response immediately. No polling required.
{
"ok": true,
"order": {
"id": "ord_abc123",
"serviceId": "clxyz123",
"serviceName": "TikTok Accounts",
"quantity": 2,
"amount": 19.80,
"status": "delivered",
"delivery": "instant",
"items": [
{ "email": "user1@example.com", "password": "P@ssw0rd1" },
{ "email": "user2@example.com", "password": "P@ssw0rd2" }
],
"deliveredAt": "2026-05-30T14:22:00.137Z",
"createdAt": "2026-05-30T14:22:00.000Z"
}
}For manual services the order is placed but items are empty. Poll GET /orders/:id until status === "delivered".
{
"ok": true,
"order": {
"id": "ord_def456",
"serviceId": "clxyz456",
"serviceName": "Google Voice Numbers",
"quantity": 1,
"amount": 24.00,
"status": "pending",
"delivery": "manual",
"items": [],
"deliveredAt": null,
"createdAt": "2026-05-30T14:22:00.000Z"
}
}/api/v1/ordersList your orders (newest first)| Param | Default | Description |
|---|---|---|
| limit | 50 | Number of orders per page (max 100). |
| cursor | — | Pagination cursor from previous response nextCursor. |
{
"ok": true,
"orders": [ /* array of order objects */ ],
"nextCursor": "ord_xyz789",
"hasMore": true
}/api/v1/orders/:idGet a single order by ID| Field | Type | Description | |
|---|---|---|---|
| id | string | optional | Order ID. |
| serviceId | string | optional | Service that was ordered. |
| serviceName | string | optional | Human-readable service name. |
| quantity | integer | optional | Number of units ordered. |
| amount | number | optional | Total charged in USD. |
| status | "pending"|"delivered"|"cancelled" | optional | Current order status. |
| delivery | "instant"|"manual" | optional | "instant" = items in the POST response immediately. "manual" = poll until delivered. |
| items | array<object> | optional | Decrypted account credentials. Populated only when status === "delivered". Each object shape varies by service; see items structure below. |
| deliveredAt | string|null | optional | ISO 8601 timestamp of delivery. |
| cancelledAt | string|null | optional | ISO 8601 timestamp of cancellation. |
| createdAt | string | optional | ISO 8601 timestamp when order was placed. |
Each element of items[] is a JSON object whose fields depend on what the admin stored for that service. Common shapes:
// Typical account with email + password
{ "email": "user@example.com", "password": "P@ssw0rd" }
// Account with additional fields
{ "email": "user@example.com", "password": "P@ssw0rd", "phone": "+1-800-555-0000", "recovery": "backup@example.com" }
// Raw/pipe-delimited (admin stored as a single string)
{ "details": "user@example.com:P@ssw0rd:+18005550000" }items[] as a flexible object array. Always check each element's keys at runtime rather than assuming a fixed schema; the shape can vary per service. When displaying to end-users, render all key–value pairs you find.{
"ok": true,
"order": {
"id": "ord_def456",
"serviceId": "clxyz456",
"serviceName": "Google Voice Numbers",
"quantity": 1,
"amount": 24.00,
"status": "delivered",
"delivery": "manual",
"items": [
{ "email": "gv@example.com", "password": "Secure123!", "phone": "+14155550100" }
],
"deliveredAt": "2026-05-30T16:45:00.000Z",
"cancelledAt": null,
"createdAt": "2026-05-30T14:22:00.000Z"
}
}pendingOrder placed; awaiting delivery (manual services).deliveredAccount credentials are in the items array.cancelledOrder was cancelled and the amount was refunded to your balance.Order followers, likes, views, comments, and more across 20+ platforms (Instagram, TikTok, YouTube, Twitter/X, Facebook, Spotify, etc.). Pricing is per 1,000 units. Each service has a minimum and maximum quantity.
/api/v1/smm/servicesList all active SMM services{
"ok": true,
"services": [
{
"id": "clxyz111",
"name": "Instagram Followers: High Quality",
"category": "Instagram",
"description": null,
"pricePerThousand": 1.50,
"minQty": 100,
"maxQty": 50000
},
{
"id": "clxyz222",
"name": "TikTok Views: Fast Delivery",
"category": "TikTok",
"description": null,
"pricePerThousand": 0.08,
"minQty": 500,
"maxQty": 1000000
}
]
}| Field | Type | Description | |
|---|---|---|---|
| id | string | optional | Unique service ID. Use this in the order body. |
| name | string | optional | Service display name. |
| category | string | optional | Platform category (e.g. Instagram, TikTok). |
| description | string|null | optional | Optional service description. |
| pricePerThousand | number | optional | Price in USD for 1,000 units. |
| minQty | integer | optional | Minimum order quantity. |
| maxQty | integer | optional | Maximum order quantity. |
/api/v1/smm/ordersPlace an SMM boost order{
"serviceId": "clxyz111",
"link": "https://instagram.com/yourpage",
"quantity": 1000
}| Field | Type | Req? | Description |
|---|---|---|---|
| serviceId | string | required | ID from GET /api/v1/smm/services. |
| link | string | required | Target URL (profile, post, video, etc.). |
| quantity | integer | required | Number of units (must be within minQty–maxQty). |
{
"ok": true,
"order": {
"id": "ord_smm_abc123",
"serviceId": "clxyz111",
"serviceName": "Instagram Followers: High Quality",
"category": "Instagram",
"link": "https://instagram.com/yourpage",
"quantity": 1000,
"charge": 1.50,
"status": "processing",
"createdAt": "2026-07-01T10:00:00.000Z"
}
}charge = pricePerThousand × (quantity / 1000). The amount is deducted from your balance at order time. Orders enter processing immediately and complete asynchronously. Poll GET /smm/orders/:id for status updates./api/v1/smm/ordersList your SMM orders (newest first)Supports ?limit and ?cursor pagination.
/api/v1/smm/orders/:idGet a single SMM order| Field | Type | Description | |
|---|---|---|---|
| id | string | optional | Order ID. |
| serviceId | string | optional | The SMM service ordered. |
| serviceName | string | optional | Human-readable service name. |
| category | string | optional | Platform category. |
| link | string | optional | The target URL submitted. |
| quantity | integer | optional | Units ordered. |
| charge | number | optional | Total charged in USD. |
| startCount | integer|null | optional | Follower/view count at order start (set by provider). |
| remains | integer|null | optional | Units remaining to be delivered (set by provider). |
| status | string | optional | Current order status (see below). |
| createdAt | string | optional | ISO 8601 creation timestamp. |
pendingOrder created; not yet sent to provider (retry imminent).processingSent to provider and being fulfilled. Delivery is in progress.completedDelivery finished. Check startCount and remains for details.partialOrder partially completed (provider stopped early). No refund for completed portion.cancelledOrder was cancelled. Contact support if unexpected.Top up your balance from the dashboard. The API lets you check your current balance programmatically.
/api/v1/balanceGet your current account balance{
"ok": true,
"balance": 45.20,
"currency": "USD"
}Buy virtual phone numbers for SMS verification across multiple integrated number routes. Numbers are charged from your balance the moment they are provisioned.
Server 1
Fastest, widest service & country coverage
Server 2
Alternative routes, good regional coverage
Server 3 & 4
Pool-based routing, well-suited for high-volume orders
/api/v1/numbers/servicesList services available for purchase| Param | Default | Description |
|---|---|---|
| server | all | Server route ID (1, 2, 3, or 4). Omit to get all routes. |
| country | usa | Country code for route stock counts. |
/api/v1/numbers/countriesList countries for a service on a specific server| Param | Req? | Description |
|---|---|---|
| server | required | Server route ID (1, 2, 3, or 4). |
| service | required | Service/product code (e.g. "wa", "tg"). |
/api/v1/numbers/ordersBuy a virtual number{
"server": 1,
"country": "usa",
"service": "wa",
"operator": "any"
}| Field | Type | Req? | Description |
|---|---|---|---|
| server | integer | required | Server route ID (1, 2, 3, or 4). |
| country | string | required | Country code in the route format (from /numbers/countries). |
| service | string | required | Service code from /numbers/services (e.g. "wa", "tg", "ig"). |
| operator | string | optional | Operator/pool preference (default "any"). |
{
"ok": true,
"order": {
"id": "clxyz789",
"orderId": 1234567890,
"server": 1,
"phone": "+12025551234",
"service": "wa",
"country": "usa",
"price": 0.50,
"status": "created",
"sms": [],
"createdAt": "2026-05-30T14:30:00.000Z"
}
}/api/v1/numbers/ordersList your number ordersSupports ?limit, ?cursor (pagination) and ?status filter.
/api/v1/numbers/orders/:idGet a single order (poll this for SMS codes){
"ok": true,
"order": {
"id": "clxyz789",
"orderId": 1234567890,
"server": 1,
"phone": "+12025551234",
"service": "wa",
"country": "usa",
"price": 0.50,
"status": "received",
"sms": [
{ "code": "123456", "text": "Your WhatsApp code: 123456" }
],
"createdAt": "2026-05-30T14:30:00.000Z",
"lastCheckAt": "2026-05-30T14:31:00.000Z"
}
}created / pending / waitingNumber purchased; waiting for SMS to arrive.received / completed / finishedSMS arrived. Check sms[] for the code.expired / timeoutNumber expired before SMS arrived. Refund is automatic if charged.canceled / cancelledOrder cancelled. Refund issued if charged./api/v1/numbers/orders/:id/cancelCancel a number order and get a refund// Response
{ "ok": true, "cancelled": true, "refunded": true }Set a webhook URL from your API Access page. When an admin delivers a manual order, SnappyDigits sends an HMAC-signed POST to your URL with the full order and credentials.
GET /api/v1/orders/:id as a fallback. Instant-delivery orders are already fulfilled in the POST response and do not fire a webhook.POST https://your-site.com/webhook/snappy
Content-Type: application/json
X-Snappy-Signature: sha256=a3f9b2c1d4e5...
{
"event": "order.delivered",
"orderId": "ord_def456",
"serviceId": "clxyz456",
"serviceName": "Google Voice Numbers",
"quantity": 1,
"amount": 24.00,
"items": [
{ "email": "gv@example.com", "password": "Secure123!", "phone": "+14155550100" }
],
"deliveredAt": "2026-05-30T16:45:00.000Z"
}Every webhook request includes an X-Snappy-Signature header. The value is sha256=<HMAC-SHA256 hex digest> of the raw request body, signed with your webhook secret. Always verify this before processing the payload. Reject any request that fails or is missing the header.
Retrieve your signing secret once from the keys endpoint:
const { webhookSecret } = await fetch("https://snappydigits.com/api/user/api-keys", {
headers: { Authorization: "Bearer sd_live_..." },
}).then((r) => r.json());
// Store webhookSecret server-side — never expose it client-side.import crypto from "crypto";
// Express / Next.js API route — read the RAW body bytes first!
export async function POST(req) {
const rawBody = await req.text(); // must be the raw string, not parsed JSON
const signature = req.headers.get("x-snappy-signature") ?? "";
const expected = "sha256=" +
crypto.createHmac("sha256", WEBHOOK_SECRET).update(rawBody).digest("hex");
// Constant-time comparison to prevent timing attacks
const valid = signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!valid) return new Response("Forbidden", { status: 403 });
const event = JSON.parse(rawBody);
if (event.event === "order.delivered") {
// Safe to use event.items here
console.log("Delivered items:", event.items);
}
return new Response("OK", { status: 200 });
}import hmac, hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret_here" # from GET /api/user/api-keys
@app.route("/webhook/snappy", methods=["POST"])
def handle_webhook():
raw_body = request.get_data() # raw bytes — do NOT call request.json() first
sig = request.headers.get("X-Snappy-Signature", "")
expected = "sha256=" + hmac.new(
WEBHOOK_SECRET.encode(), raw_body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(403)
event = request.get_json()
if event["event"] == "order.delivered":
print("Items:", event["items"])
return "", 200<?php
$webhookSecret = "your_webhook_secret_here"; // from GET /api/user/api-keys
$rawBody = file_get_contents("php://input");
$sigHeader = $_SERVER["HTTP_X_SNAPPY_SIGNATURE"] ?? "";
$expected = "sha256=" . hash_hmac("sha256", $rawBody, $webhookSecret);
if (!hash_equals($expected, $sigHeader)) {
http_response_code(403);
exit("Forbidden");
}
$event = json_decode($rawBody, true);
if ($event["event"] === "order.delivered") {
// Safe to use $event["items"]
error_log(print_r($event["items"], true));
}
http_response_code(200);
echo "OK";Return any 2xx HTTP status within 10 seconds. Non-2xx or timeout means delivery was not confirmed; fall back to polling GET /api/v1/orders/:id. Webhook URLs must be HTTPS and publicly reachable; http:// and private/local addresses are rejected.
Copy-paste examples for common languages.
const API_KEY = "sd_live_your_key_here";
const BASE = "https://snappydigits.com/api/v1";
const res = await fetch(`${BASE}/services`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const { ok, services } = await res.json();
console.log(services);For instant services, credentials are in the 201 response. No polling needed.
const res = await fetch(`${BASE}/orders`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ serviceId: "clxyz123", quantity: 2 }),
}).then((r) => r.json());
const { order } = res;
if (order.delivery === "instant" && order.status === "delivered") {
// Items are ready immediately — no polling required
console.log("Credentials:", order.items);
// order.items = [ { email: "...", password: "..." }, ... ]
} else {
// Manual service — fall through to polling
}async function waitForDelivery(orderId, maxWaitMs = 300_000) {
const deadline = Date.now() + maxWaitMs;
while (Date.now() < deadline) {
const { order } = await fetch(`${BASE}/orders/${orderId}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
}).then((r) => r.json());
if (order.status === "delivered") return order.items;
if (order.status === "cancelled") throw new Error("Order cancelled");
await new Promise((r) => setTimeout(r, 15_000)); // poll every 15s
}
throw new Error("Timed out waiting for delivery");
}
const items = await waitForDelivery(order.order.id);
// items is an array of objects — keys vary by service, e.g. { email, password }
// iterate over all keys to display credentials:
for (const account of items) {
console.log(Object.entries(account).map(([k, v]) => `${k}: ${v}`).join(" | "));
}import requests, time
API_KEY = "sd_live_your_key_here"
BASE = "https://snappydigits.com/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# List services
services = requests.get(f"{BASE}/services", headers=HEADERS).json()
svc = services["services"][0]
# Place order
resp = requests.post(
f"{BASE}/orders",
json={"serviceId": svc["id"], "quantity": 1},
headers=HEADERS,
).json()
order = resp["order"]
if order["delivery"] == "instant":
# Items delivered immediately in the POST response
print("Credentials:", order["items"])
else:
# Manual service — poll until delivered
for _ in range(20):
result = requests.get(f"{BASE}/orders/{order['id']}", headers=HEADERS).json()
if result["order"]["status"] == "delivered":
print("Credentials:", result["order"]["items"])
break
if result["order"]["status"] == "cancelled":
raise Exception("Order cancelled")
time.sleep(15)<?php
$apiKey = "sd_live_your_key_here";
$base = "https://snappydigits.com/api/v1";
function snappyRequest($method, $path, $body = null) {
global $apiKey, $base;
$ch = curl_init($base . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $apiKey",
"Content-Type: application/json",
],
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_POSTFIELDS => $body ? json_encode($body) : null,
]);
return json_decode(curl_exec($ch), true);
}
$services = snappyRequest("GET", "/services");
$resp = snappyRequest("POST", "/orders", [
"serviceId" => $services["services"][0]["id"],
"quantity" => 1,
]);
$order = $resp["order"];
if ($order["delivery"] === "instant") {
// Credentials available immediately
print_r($order["items"]);
} else {
// Poll for manual delivery
for ($i = 0; $i < 20; $i++) {
$result = snappyRequest("GET", "/orders/" . $order["id"]);
if ($result["order"]["status"] === "delivered") {
print_r($result["order"]["items"]);
break;
}
sleep(15);
}
}Paste this into your AI agent to give it full, accurate API knowledge:
You have access to the SnappyDigits Reseller API.
Base URL: https://snappydigits.com/api/v1
Auth header: Authorization: Bearer {API_KEY}
═══ ACCOUNT SERVICES (social accounts, Google Voice, etc.) ═══
GET /services → list account services (id, name, price, stock, delivery)
POST /orders → buy account service body: { serviceId, quantity (max 100) }
GET /orders → list account orders ?limit=50&cursor=<id>
GET /orders/:id → get single order
GET /balance → USD balance
DELIVERY MODEL — critical, implement correctly:
- "instant" services: POST /orders returns HTTP 201 with status="delivered" and items[]
populated immediately. DO NOT poll — credentials are already in the POST response.
- "manual" services: POST /orders returns status="pending" and items=[].
Poll GET /orders/:id every 15–30s until status="delivered", then read items[].
items[] is an array of objects. Each object's keys vary per service (e.g. { email, password }
or { email, password, phone } or { details: "..." }). Iterate all keys at runtime.
Never assume a fixed schema. items[] is empty ([]) until status === "delivered".
Error codes: UNAUTHORIZED (401), BAD_REQUEST (400), NOT_FOUND (404),
INSUFFICIENT_BALANCE (402), OUT_OF_STOCK (409 — service has no stock, retry later),
CONFLICT (409), RATE_LIMITED (429), SERVICE_UNAVAILABLE (503)
Rate limits: writes (POST) 20/min shared; reads (GET) 60/min per endpoint.
═══ SMM BOOST SERVICES (followers, likes, views, etc.) ═══
GET /smm/services → list SMM services (id, name, category, pricePerThousand, minQty, maxQty)
POST /smm/orders → place SMM order body: { serviceId, link, quantity }
GET /smm/orders → list SMM orders ?limit=50&cursor=<id>
GET /smm/orders/:id → get single SMM order
Charge formula: charge = pricePerThousand × (quantity / 1000)
Deducted from balance immediately on order creation.
quantity must be within minQty–maxQty for the chosen service.
SMM order statuses: pending → processing → completed | partial | cancelled
- pending: created but not yet sent to provider
- processing: provider is fulfilling the order
- completed: delivery finished (check startCount + remains)
- partial: partially delivered (provider stopped early)
- cancelled: order cancelled
SMM order workflow:
1) GET /smm/services to find service id and pricePerThousand
2) POST /smm/orders { serviceId, link: "https://...", quantity: 1000 }
3) Poll GET /smm/orders/:id every 30–60s until status=completed
═══ VIRTUAL NUMBERS (SMS verification) ═══
GET /numbers/services → list services ?server=1|2|3 ?country=usa
GET /numbers/countries → list countries ?server=1|2|3&service=wa
POST /numbers/orders → buy number body: { server, country, service, operator? }
GET /numbers/orders → list number orders ?limit&cursor&status
GET /numbers/orders/:id → get order + sms[] when code arrives
POST /numbers/orders/:id/cancel → cancel + refund
Servers: 1=fastest/widest coverage, 2=alternative stock, 3=pool-based/bulk
Number order statuses: created/pending/waiting → received/completed (sms[] has code) | expired | cancelled
Number buy workflow:
1) GET /numbers/services?server=1 to find service code
2) GET /numbers/countries?server=1&service=wa for country codes
3) POST /numbers/orders { server:1, country:"usa", service:"wa" }
4) Poll GET /numbers/orders/:id every 10–20s until status=received
5) Return sms[0].code to your user
6) POST /numbers/orders/:id/cancel if user no longer needs it
All responses: { ok: true, ...data } or { ok: false, error: "...", code: "..." }
═══ WEBHOOKS ═══
SnappyDigits sends POST to your webhook URL when a manual order is delivered.
Each request includes: X-Snappy-Signature: sha256=<HMAC-SHA256 of raw body>
Verify before trusting: HMAC-SHA256(rawBody, webhookSecret) must match.
Get webhookSecret from: GET https://snappydigits.com/api/user/api-keys → .webhookSecret
Webhooks are best-effort (no retries). Always poll as a fallback.
Instant-delivery orders do NOT fire a webhook — credentials come in the POST response.