Keys24x7 Reseller API

Resell our full digital-goods catalog on your own website, SMM panel or WooCommerce store. Orders debit your wallet and auto-deliver — stock credentials arrive instantly, manual items follow within their ETA. All delivery notifications go out by email.

These docs are public — read everything before you create an account.

Overview & Authentication

There are two API surfaces over the same account, wallet and catalog:

APIBase URLAuthBest for
SMM Panel API https://keys24x7.com/api/v2 key parameter (form or JSON) SMM panels (Perfect Panel, SmartPanel, Rental panels…) via the standard provider format
REST API v1 https://keys24x7.com/api/v1 Authorization: Bearer <api_key> (or ?key=) Custom websites, AI-built storefronts, the WordPress plugin, scripts

Get your API key by creating a reseller account — the key is issued instantly and shown on your dashboard, where you also top up your wallet.

Wallet & errors. Every order debits your wallet at the product's INR rate × quantity. If the balance is too low, v1 responds 402 and v2 responds {"error":"..."}. Suspended accounts get {"error":"Account suspended"} on every call.
Keep the key server-side. Anyone holding your key can spend your balance. Never ship it in browser JavaScript, mobile apps or public repos — call the API from your backend and keep the key in an environment variable.

SMM Panel API — POST /api/v2

The industry-standard SMM provider format: one endpoint, POST only, form-encoded (or JSON) body, an action parameter, responses in JSON. Errors always come back as {"error":"message"} with HTTP 200, as SMM panels expect.

POST action=services

Returns the full catalog as SMM services. service is a stable integer id; rate is the INR price per unit.

ParameterRequiredDescription
keyYesYour API key
actionYesservices
curl
curl -X POST https://keys24x7.com/api/v2 \
  -d 'key=YOUR_API_KEY' \
  -d 'action=services'
200 · json
[
  {
    "service": 12,
    "name": "Netflix Premium — 1 Month",
    "type": "Package",
    "category": "Streaming",
    "rate": "99.00",
    "min": 1,
    "max": 14,
    "refill": false,
    "cancel": false
  }
]

max reflects available stock for instant-delivery products (falls back to 1000 for manual delivery).

POST action=add

Places an order: checks the product is active and your balance covers rate × quantity, debits the wallet atomically, then delivers instantly when stock is available.

ParameterRequiredDescription
keyYesYour API key
actionYesadd
serviceYesService id from action=services
quantityNoDefaults to 1
customer_emailNoNon-standard extra: your buyer's email — credentials are emailed there (you get a copy). Defaults to your reseller email.
curl
curl -X POST https://keys24x7.com/api/v2 \
  -d 'key=YOUR_API_KEY' \
  -d 'action=add' \
  -d 'service=12' \
  -d 'quantity=1' \
  -d '[email protected]'
200 · json
{ "order": 5731 }

POST action=status

Check one order. credentials is included only once the order is Completed.

ParameterRequiredDescription
keyYesYour API key
actionYesstatus
orderYesOrder id returned by add
curl
curl -X POST https://keys24x7.com/api/v2 \
  -d 'key=YOUR_API_KEY' \
  -d 'action=status' \
  -d 'order=5731'
200 · json
{
  "charge": "99.00",
  "start_count": "0",
  "status": "Completed",
  "remains": "0",
  "currency": "INR",
  "credentials": "[email protected]:password123"
}
StatusMeaning
CompletedPaid and delivered — credentials included
In progressPaid, delivery being prepared
PendingAwaiting fulfillment (manual-delivery products)
RefundedCharge returned to your wallet
CanceledOrder cancelled

POST action=status (multiple orders)

Pass orders as a comma-separated list (instead of order) to check up to 100 orders in one call.

curl
curl -X POST https://keys24x7.com/api/v2 \
  -d 'key=YOUR_API_KEY' \
  -d 'action=status' \
  -d 'orders=5731,5732,5733'
200 · json
{
  "5731": { "charge": "99.00", "start_count": "0", "status": "Completed", "remains": "0", "currency": "INR", "credentials": "[email protected]:password123" },
  "5732": { "charge": "199.00", "start_count": "0", "status": "In progress", "remains": "0", "currency": "INR" },
  "5733": { "error": "Incorrect order ID" }
}

POST action=balance

curl
curl -X POST https://keys24x7.com/api/v2 \
  -d 'key=YOUR_API_KEY' \
  -d 'action=balance'
200 · json
{ "balance": "1234.50", "currency": "INR" }
200 · error shape
{ "error": "Not enough funds in the balance" }

POST action=sync

Catalog sync for panels that prefer the v2 surface. Returns a version fingerprint alongside the services — store it, and skip the next import while it is unchanged. Add updated_since (ISO 8601) to receive only what changed since your last sync; rows that were deactivated come back with "active": false so you can disable them in your panel.

curl
curl -X POST https://keys24x7.com/api/v2 \
  -d 'key=YOUR_API_KEY' \
  -d 'action=sync' \
  -d 'updated_since=2026-08-05T00:00:00Z'
200 · json
{
  "version": "9f2c1a77b0e34d51",
  "product_count": 148,
  "updated_at": "2026-08-05 12:41:03",
  "services": [
    {
      "service": 12,
      "name": "Netflix Premium — 1 Month",
      "type": "Package",
      "category": "Streaming",
      "rate": "99.00",
      "min": 1,
      "max": 14,
      "refill": false,
      "cancel": false,
      "active": true,
      "updated_at": "2026-08-05 12:41:03"
    }
  ]
}

REST API v1

Clean JSON over proper HTTP verbs and status codes. Authenticate with Authorization: Bearer <api_key> (a ?key= query parameter also works for quick tests).

CodeMeaning
401Missing or invalid API key
402Insufficient wallet funds
404Unknown product or order
409Price below supplier cost — the order was refused and your wallet was not debited. See below.
409Out of stock (out_of_stock) — more requested than we hold; nothing debited. See below.
409Retry still running (idempotency_in_flight) — see Idempotency.
422Validation error (bad quantity, missing fields…)
429Rate limit exceeded — see Rate limits

GET /api/v1/products

Full catalog, including live stock for instant-delivery products.

curl
curl https://keys24x7.com/api/v1/products \
  -H 'Authorization: Bearer YOUR_API_KEY'
200 · json
[
  {
    "id": "netflix_premium_1m",
    "api_service_id": 12,
    "name": "Netflix Premium — 1 Month",
    "category": "Streaming",
    "description": "4K UHD private profile, instant delivery",
    "price_inr": 99,
    "price_usdt": 1.2,
    "image_url": "https://keys24x7.com/images/netflix.png",
    "delivery_type": "auto",
    "eta_minutes": 0,
    "in_stock": true,
    "stock_count": 14,
    "active": true,
    "updated_at": "2026-08-05 12:41:03"
  }
]

Supports ?updated_since=, ETag and If-None-Match — see Keeping in sync.

GET /api/v1/products/:id

A single product — price, live stock, in_stock and the delivery type/ETA. Accepts the string id or the integer api_service_id. Use it to refresh one product without pulling the whole catalog (for example right before you take a payment).

curl
curl https://keys24x7.com/api/v1/products/netflix_premium_1m \
  -H 'Authorization: Bearer YOUR_API_KEY'
200 · json
{
  "id": "netflix_premium_1m",
  "api_service_id": 12,
  "name": "Netflix Premium — 1 Month",
  "price_inr": 99,
  "delivery_type": "auto",
  "eta_minutes": 0,
  "in_stock": true,
  "stock_count": 14,
  "active": true,
  "updated_at": "2026-08-05 12:41:03"
}

POST /api/v1/orders

Create an order. Pass either product_id (string id) or service (integer id from the SMM services list). If the product delivers instantly, credentials is already in the response.

curl
curl -X POST https://keys24x7.com/api/v1/orders \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: order-8f21c9a4' \
  -d '{
    "product_id": "netflix_premium_1m",
    "quantity": 1,
    "customer_email": "[email protected]"
  }'
201 · json
{
  "order_id": 5731,
  "charge": 99,
  "balance_after": 1135.5,
  "status": "Completed",
  "credentials": "[email protected]:password123"
}
402 · json
{ "error": "Insufficient funds" }
409 · json
{ "error": "Price below supplier cost — contact support" }
409 · json
{ "error": "Only 2 in stock", "error_code": "out_of_stock", "available": 2 }
About the 409. Some catalog items are sourced from a supplier. If the price your account would be charged has fallen below what that item costs us — a stale price, or a volume discount applied to an item whose cost has since risen — the order is refused before anything is debited: your wallet is untouched and no order is created. Retrying will not help; the price is corrected on our side, usually within one catalog sync. Re-read the product (GET /api/v1/products/:id) and, if it still fails, contact support. Products we own outright are never affected.
Out of stock. Stock-backed products are only sold while the keys are actually in hand. If you ask for more than we hold, the order is refused before any debit — available tells you how many you could have taken. This is the same number as stock_count on the product and max in the SMM services list, so a client that respects those will rarely see it.

Idempotency

Send an Idempotency-Key header with every order you create. If a request times out you cannot tell whether it was received, and retrying without a key places a second order and takes a second debit. With a key, the retry returns the original order instead.

  • Use a fresh, unique value per order you intend to place — a UUID is ideal. Reusing one key across genuinely different orders is a bug, and we report it rather than guess.
  • A retry of a completed order returns the original response with "idempotent_replay": true and HTTP 200 (the first reply is 201). Same order_id, no extra charge.
  • A retry while the first is still running returns 409 idempotency_in_flight — wait and poll GET /api/v1/orders/:id rather than ordering again.
  • Reusing a key with a different payload returns 422 idempotency_key_reuse.
  • Rejected orders (insufficient funds, out of stock, bad quantity) release the key — nothing was created, so you may fix the request and retry with the same key.
  • Keys are remembered for 24 hours. After that the same value starts a new order.
  • Omitting the key preserves the old behaviour exactly: two identical calls create two real orders. SMM panels cannot send headers — they may pass idempotency_key as a form field instead.

White-label delivery

Your customers can receive their credentials under your brand, with nothing identifying us. Turn it on under White-label delivery in your dashboard — no API changes are needed, it applies automatically to every order you place.

What changes. Delivery and order-confirmation emails to your buyer carry your brand name, your support address and your site — no logo, no footer domain and no tracking link back to us.

The sender address. Without your own mail server we send through ours: the email shows your brand name, but our sending domain stays visible in the address. Add your SMTP username and password in the dashboard and the mail goes out from your own domain instead — fully unbranded.

Why we don't just forge it. Putting your address on mail sent through our server fails SPF and DKIM checks, which lands your customer's credentials in spam. Your own SMTP is the only way to get both your domain and reliable delivery.

Use Send me a sample email in the dashboard to see exactly what your buyer will receive before a real order goes out. Your own copy of each delivery is unaffected and still carries our branding and tracking link.

GET /api/v1/orders/:id

Order details. Poll this until status is Completed for manual-delivery products.

curl
curl https://keys24x7.com/api/v1/orders/5731 \
  -H 'Authorization: Bearer YOUR_API_KEY'
200 · json
{
  "order_id": 5731,
  "product": "Netflix Premium — 1 Month",
  "status": "Completed",
  "charge": 99,
  "credentials": "[email protected]:password123",
  "created_at": "2026-08-05 12:41:03"
}

GET /api/v1/orders?limit=50

Your most recent orders, newest first. limit defaults to 50.

curl
curl 'https://keys24x7.com/api/v1/orders?limit=10' \
  -H 'Authorization: Bearer YOUR_API_KEY'
200 · json
[
  { "order_id": 5731, "product": "Netflix Premium — 1 Month", "status": "Completed", "charge": 99, "created_at": "2026-08-05 12:41:03" },
  { "order_id": 5729, "product": "Spotify Premium — 3 Months", "status": "Pending", "charge": 249, "created_at": "2026-08-05 11:02:47" }
]

GET /api/v1/balance

curl
curl https://keys24x7.com/api/v1/balance \
  -H 'Authorization: Bearer YOUR_API_KEY'
200 · json
{ "balance": 1135.5, "currency": "INR" }

GET /api/v1/me

Account details — handy as a "test connection" call.

curl
curl https://keys24x7.com/api/v1/me \
  -H 'Authorization: Bearer YOUR_API_KEY'
200 · json
{
  "name": "Acme Digital",
  "email": "[email protected]",
  "status": "active",
  "created_at": "2026-07-01 09:15:00"
}

Keeping in sync

Prices and stock move. Three tools, cheapest first — combine them and a store of any size stays current with a handful of requests per hour.

  1. 1Poll /api/v1/catalog/version every few minutes. One tiny JSON response; if version matches what you stored, nothing has changed and you are done.
  2. 2When it changes, pull only the delta with ?updated_since= your last sync time.
  3. 3Subscribe to webhooks so deliveries and catalog changes are pushed to you instead of polled at all.

GET /api/v1/catalog/version

version is a short hash of the catalog's newest change plus its size. It changes whenever any product's price, stock state or availability changes.

curl
curl https://keys24x7.com/api/v1/catalog/version \
  -H 'Authorization: Bearer YOUR_API_KEY'
200 · json
{
  "version": "9f2c1a77b0e34d51",
  "product_count": 148,
  "updated_at": "2026-08-05 12:41:03"
}

GET /api/v1/products?updated_since=…

Only products whose price, stock or availability changed after that instant. The value is any ISO 8601 timestamp (a bad one returns 422). In this mode deactivated products are included with "active": false — that is how your store learns something was withdrawn. A plain GET /api/v1/products lists active products only.

curl
curl 'https://keys24x7.com/api/v1/products?updated_since=2026-08-05T00:00:00Z' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Use the updated_at of the newest row you received (or the time you started the request) as the next updated_since. Overlapping slightly is safe — updates are idempotent.

ETag & 304 Not Modified

Every GET /api/v1/products and GET /api/v1/catalog/version response carries an ETag. Send it back as If-None-Match and an unchanged answer costs you a 304 with an empty body instead of the whole catalog.

curl
# first call — note the ETag header
curl -i https://keys24x7.com/api/v1/products \
  -H 'Authorization: Bearer YOUR_API_KEY'
# → ETag: "3d9a…"

# next call — 304, nothing transferred
curl -i https://keys24x7.com/api/v1/products \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'If-None-Match: "3d9a…"'
# → HTTP/1.1 304 Not Modified

Webhooks

Register an HTTPS endpoint and we push events to it — no polling, no delay. This is what makes automatic delivery on your own store possible: the moment credentials exist, they are on your server.

POST /api/v1/webhooks

Returns the signing secret once — store it immediately, it is never shown again. Omit events to subscribe to all of them. Up to 10 endpoints per account.

curl
curl -X POST https://keys24x7.com/api/v1/webhooks \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://yourstore.example/hooks/keys",
    "events": ["order.delivered", "order.failed", "catalog.updated"]
  }'
201 · json
{
  "id": 3,
  "url": "https://yourstore.example/hooks/keys",
  "events": ["order.delivered", "order.failed", "catalog.updated"],
  "active": true,
  "created_at": "2026-08-05 13:20:11",
  "secret": "6f1b…"
}

GET /api/v1/webhooks lists yours (without secrets), GET /api/v1/webhooks/:id fetches one, DELETE /api/v1/webhooks/:id removes it (204).

What we send & how to verify it

http
POST /hooks/keys HTTP/1.1
Content-Type: application/json
X-Keys-Event: order.delivered
X-Keys-Delivery: 918
X-Keys-Timestamp: 1785935112
X-Keys-Signature: sha256=4b0f8c…

{"event":"order.delivered","created_at":"2026-08-05T13:25:12.004Z","data":{ … }}

The signature is an HMAC-SHA256 of the raw request body keyed with your webhook secret. Always verify it before trusting a payload, and always compare in constant time.

node
import crypto from 'crypto';

// IMPORTANT: use the RAW body, not a re-serialised object.
app.post('/hooks/keys', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.KEYS_WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');
  const got = String(req.headers['x-keys-signature'] || '');
  const ok = got.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
  if (!ok) return res.status(401).end();

  const { event, data } = JSON.parse(req.body.toString('utf8'));
  // … deliver to your buyer, update your catalog …
  res.status(200).end();   // respond 2xx quickly; do the work after
});
php
$raw = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw, getenv('KEYS_WEBHOOK_SECRET'));
if (!hash_equals($expected, $_SERVER['HTTP_X_KEYS_SIGNATURE'] ?? '')) {
    http_response_code(401); exit;
}
$payload = json_decode($raw, true);

Events

EventFired whendata
order.deliveredCredentials are attached to one of your ordersorder_id, reference, product, charge, status:"Completed", credentials
order.failedOne of your orders was moved to manual revieworder_id, reference, product, charge, status:"manual_review", reason, message
catalog.updatedA catalog import or auto-sync changed prices/stockversion, product_count, updated_at, source
Delivery rules. Any 2xx counts as accepted. A non-2xx reply, a connection error or a response slower than 10 seconds is retried 3 times with 1 s → 5 s → 15 s backoff, then dropped. Reply 200 first and process afterwards. Retries mean the same event can arrive twice — key your handling on X-Keys-Delivery or the order reference and make it idempotent. On catalog.updated, treat the payload as a hint: re-pull with updated_since rather than trusting the counts.

Rate limits

240 requests per minute per API key, counted across /api/v1 and /api/v2 together in a fixed 60-second window. That is far more than a healthy integration needs — polling catalog/version every 10 seconds plus normal order traffic uses a fraction of it.

HeaderMeaning
X-RateLimit-LimitRequests allowed in the window
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetUnix time when the window resets
Retry-AfterSeconds to wait (sent with 429 only)

Over the limit, v1 answers 429 {"error":"Rate limit exceeded"}; v2 answers the same {"error":…} body with HTTP 200, as the SMM convention requires. Back off and retry — nothing is charged or created. If you genuinely need a higher ceiling, ask support.

Connect from an SMM Panel

Keys24x7 speaks the standard SMM provider protocol, so any panel software (Perfect Panel, SmartPanel, rental panels, custom scripts) can plug us in as a provider:

  1. 1In your panel's admin area open Providers (sometimes called API Providers or Services → Providers).
  2. 2Click Add provider.
  3. 3API URL: https://keys24x7.com/api/v2
  4. 4API key: paste the key from your reseller dashboard.
  5. 5Save, then import services — our catalog appears with INR rates. Set your own markup per service.
  6. 6Map order delivery: when an order completes, fetch action=status — the credentials field contains what your buyer receives (also emailed automatically when you pass customer_email on add).
Tip: keep your wallet topped up — orders fail instantly (no queue) when the balance can't cover the charge, and most panels will mark the order as errored.

Integrate with an AI Website Builder

Building a storefront with Lovable, v0, Bolt or a similar AI builder? Paste the prompt below, replace YOUR_API_KEY, and the builder will wire a complete storefront against our REST API — with the key safely kept server-side.

Never expose the key client-side. The prompt explicitly instructs the builder to create a server-side proxy that injects the key from an environment variable. If a builder puts the key in browser code, regenerate your key and fix the proxy first.
prompt · paste into your AI builder
You are building a digital-goods storefront that resells products from the
Keys24x7 Reseller API.

API BASE URL: https://keys24x7.com/api/v1
API KEY: YOUR_API_KEY

AUTH: every request to the Keys24x7 API needs the header
  Authorization: Bearer <API KEY>

SECURITY (most important requirement):
Create server-side API routes (for example /api/licenses/*) that forward
requests to the Keys24x7 API and inject the Authorization header from an
environment variable named RESELLER_API_KEY. The browser must only ever call
these proxy routes. Never place the key in client-side code, bundles, or
public config — if the framework only supports client code, generate a small
serverless/edge function for the proxy.

BUILD THIS:

1. Catalog page
   - GET {base}/products returns an array of:
     { id, api_service_id, name, category, description, price_inr,
       price_usdt, image_url, delivery_type, eta_minutes, in_stock,
       stock_count }
   - Render a responsive product grid grouped by category with name, image,
     description, delivery ETA and an "In stock" badge.
   - Show a selling price of price_inr with my margin applied (make the
     margin a single constant I can edit, default 20%).
   - Grey out / disable buying when in_stock is false.

2. Checkout
   - Collect the buyer's email address, then POST {base}/orders with JSON:
     { "product_id": "<id>", "quantity": 1, "customer_email": "<buyer email>" }
   - A 201 response returns { order_id, charge, balance_after, status,
     credentials? }. If credentials is present, the item was delivered
     instantly — show it on a success page with a copy button.
   - Error handling: 402 means my reseller wallet is out of funds — show the
     buyer "temporarily unavailable, try again soon" (never mention the
     wallet); 404 unknown product; 422 validation error; show friendly toasts.

3. Order status page
   - For orders that are not yet Completed, poll GET {base}/orders/{order_id}
     every 15 seconds. The response is { order_id, product, status, charge,
     credentials?, created_at }.
   - While status is "Pending" or "In progress" show a friendly "your order
     is being prepared" state with the ETA. When it becomes "Completed",
     stop polling and reveal the credentials with a copy button.
   - Give the buyer a link they can bookmark to come back to this page.

4. Utilities (server-side only)
   - GET {base}/balance returns { balance, currency } — log a server-side
     warning when the balance drops below 500 so I know to top up.
   - GET {base}/me verifies the key works; use it in a health-check route.

GENERAL: mobile-first responsive design, clear loading and empty states,
no exposure of wholesale price_inr or my wallet anywhere in the UI.

WordPress Plugin

Already running WooCommerce? The Keys24x7 Reseller plugin imports our catalog as WooCommerce products with your margin, keeps prices and stock in sync hourly, and fulfills paid orders automatically — credentials land in the buyer's completion email.

Download watshop-reseller.zip

Install & set up

  1. 1Requirements: WordPress with WooCommerce active, PHP 7.4+.
  2. 2WP Admin → Plugins → Add New → Upload Plugin → choose watshop-reseller.zipInstall Now.
  3. 3Click Activate.
  4. 4Open Settings → Keys24x7 Reseller and enter the API URL https://keys24x7.com/api/v1 and your API key. Set your price margin % and (optionally) enable hourly auto-sync.
  5. 5Click Test connection — it calls /api/v1/me and shows your account plus wallet balance.
  6. 6Click Import now — the catalog is created as virtual WooCommerce products (SKU = product id, price = INR price × (1 + margin)), with categories and images mapped.

How selling works

  1. 1A customer pays for an imported product in your WooCommerce store.
  2. 2The plugin places the order on Keys24x7 with your buyer's email and stores the Keys24x7 order id on the WooCommerce order.
  3. 3A cron job polls the order until it is Completed, then saves the credentials to the order, adds an order note and sends them to the buyer in the completion email.
  4. 4If the order fails (for example your wallet hits 402), the WooCommerce order stays in processing with a note, you get an admin email, and a Retry button appears in the order's Keys24x7 Reseller meta box.
Hourly sync keeps prices in line with the Keys24x7 catalog and flips WooCommerce products to out-of-stock when our stock runs dry. Uninstalling the plugin cleans up its settings.

Keys24x7 Reseller API · Portal login · Create account