API Documentation
Perfectpanel-compatible REST API. Share this page with your developer for full integration instructions.
Authentication
Every request must include your API key parameter. Generate or rotate your key from your dashboard under API. Only one active key per account — generating a new key revokes the previous one.
POST https://your-panel.com/api/v2
Content-Type: application/x-www-form-urlencoded
key=YOUR_API_KEY&action=balanceOrder status lifecycle
| Status | Meaning |
|---|---|
| pending | Received, queued for the provider. |
| processing | Submitted to provider, awaiting confirmation. |
| in_progress | Provider is delivering the order. |
| completed | Delivery finished successfully. |
| partial | Partial delivery; remainder refunded. |
| canceled | Canceled; full refund issued. |
| refunded | Order failed and refunded to balance. |
Endpoints
All requests use HTTP POST with form-encoded or JSON body.
Service list
Returns all active services with current rates and limits.
| key | Your API key |
| action | "services" |
curl -X POST https://your-panel.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=services"[
{
"service": "1234",
"name": "Instagram Followers — Real",
"type": "Default",
"category": "Instagram",
"rate": "0.90",
"min": "100",
"max": "10000",
"refill": true,
"cancel": true
}
]Add order
Place a new order. The returned order ID is what you'll use for status and refill calls.
| key | Your API key |
| action | "add" |
| service | Service ID from /services |
| link | Target URL (profile, post, etc.) |
| quantity | Number of units to deliver |
curl -X POST https://your-panel.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=add" \
-d "service=1234" \
-d "link=https://instagram.com/yourpage" \
-d "quantity=1000"{ "order": 87654 }Order status
Get the current status of a single order.
| key | Your API key |
| action | "status" |
| order | Order ID |
curl -X POST https://your-panel.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=status" \
-d "order=87654"{
"charge": "1.0800",
"start_count": "1200",
"status": "In progress",
"remains": "157",
"currency": "USD"
}Multiple orders status
Check up to 100 orders in one request. Comma-separated IDs.
| key | Your API key |
| action | "status" |
| orders | id1,id2,id3 |
curl -X POST https://your-panel.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=status" \
-d "orders=87654,87655,87656"{
"87654": { "charge": "0.90", "status": "Completed", "remains": "0", "start_count": "1000", "currency": "USD" },
"87655": { "charge": "1.40", "status": "In progress", "remains": "230", "start_count": "1200", "currency": "USD" }
}Create refill
Request a refill for a single order (where supported).
| key | Your API key |
| action | "refill" |
| order | Order ID |
curl -X POST https://your-panel.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=refill" \
-d "order=87654"{ "refill": "1234" }Multiple refills
Request refills for multiple orders at once.
| key | Your API key |
| action | "refill" |
| orders | id1,id2,id3 |
curl -X POST https://your-panel.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=refill" \
-d "orders=87654,87655"[
{ "order": 87654, "refill": "1234" },
{ "order": 87655, "refill": { "error": "Incorrect order ID" } }
]Refill status
Check the status of a single refill request.
| key | Your API key |
| action | "refill_status" |
| refill | Refill ID |
curl -X POST https://your-panel.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=refill_status" \
-d "refill=1234"{ "status": "Completed" }Multiple refill status
Check status for up to 100 refills.
| key | Your API key |
| action | "refill_status" |
| refills | id1,id2,id3 |
curl -X POST https://your-panel.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=refill_status" \
-d "refills=1234,1235"[
{ "refill": 1234, "status": "Completed" },
{ "refill": 1235, "status": "Pending" }
]Cancel orders
Request cancellation for one or more orders (where supported).
| key | Your API key |
| action | "cancel" |
| orders | id1,id2,id3 |
curl -X POST https://your-panel.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=cancel" \
-d "orders=87654,87655"[
{ "order": 87654, "cancel": { "status": "Pending" } },
{ "order": 87655, "cancel": { "error": "Incorrect order ID" } }
]User balance
Get your current account balance.
| key | Your API key |
| action | "balance" |
curl -X POST https://your-panel.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=balance"{ "balance": "120.45", "currency": "USD" }Errors
Errors are returned as JSON with an error field. Common errors:
| Incorrect API key | Key missing, revoked, or invalid. |
| Not enough funds | Add funds to your account before placing the order. |
| Incorrect service ID | Service no longer active. Refetch /services. |
| Quantity out of range | Adjust quantity to fit the service's min/max. |
| Incorrect order ID | Order doesn't exist or doesn't belong to your account. |
Rate limits & idempotency
- Reasonable use is allowed; sustained bursts above ~10 req/s per key may be throttled.
- Duplicate
addrequests with the sameservice+link+quantityreceived within seconds are de-duplicated server-side to prevent double-charging — the same order ID is returned. - Always store the returned
orderID and use it for subsequentstatus/refill/cancelcalls.
Code samples
PHP
<?php
$api = new Api();
$api->set_url('https://your-panel.com/api/v2');
$api->set_key('YOUR_API_KEY');
print_r($api->order(['service' => 1234, 'link' => 'https://...', 'quantity' => 1000]));
print_r($api->status(87654));
print_r($api->balance());
class Api {
private $api_url, $api_key;
public function set_url($u){ $this->api_url = $u; }
public function set_key($k){ $this->api_key = $k; }
public function order($d){ return $this->connect(array_merge(['action'=>'add'], $d)); }
public function status($id){ return $this->connect(['action'=>'status','order'=>$id]); }
public function balance(){ return $this->connect(['action'=>'balance']); }
private function connect($post){
$_post = ['key'=>$this->api_key];
foreach($post as $n=>$v) $_post[$n]=$v;
$ch = curl_init($this->api_url);
curl_setopt_array($ch, [
CURLOPT_POST=>true, CURLOPT_RETURNTRANSFER=>true,
CURLOPT_POSTFIELDS=>http_build_query($_post),
]);
$r = curl_exec($ch); curl_close($ch);
return json_decode($r, true);
}
}Python
import requests
API_URL = "https://your-panel.com/api/v2"
API_KEY = "YOUR_API_KEY"
def call(action, **kwargs):
return requests.post(API_URL, data={"key": API_KEY, "action": action, **kwargs}).json()
print(call("balance"))
print(call("add", service=1234, link="https://instagram.com/yourpage", quantity=1000))
print(call("status", order=87654))Node.js
const API_URL = "https://your-panel.com/api/v2";
const API_KEY = "YOUR_API_KEY";
async function call(action, params = {}) {
const body = new URLSearchParams({ key: API_KEY, action, ...params });
const res = await fetch(API_URL, { method: "POST", body });
return res.json();
}
console.log(await call("balance"));
console.log(await call("add", { service: 1234, link: "https://instagram.com/yourpage", quantity: 1000 }));
console.log(await call("status", { order: 87654 }));Who can use this API
The API is language-agnostic. Any client that can make an HTTPS POST with application/x-www-form-urlencoded or application/json body works — no SDK required.
- Server languages: PHP (cURL / Guzzle), Python (requests / httpx), Node.js (fetch / axios), Go, Ruby, Java, .NET, Rust, Deno, Bun.
- No-code / low-code: Make, Zapier, n8n, Pipedream — use their generic HTTP module.
- SMM panel scripts: Perfectpanel, SmartPanel, YoyoMedia, MedanPanel and any Perfectpanel-compatible fork can add us as an upstream provider.
- Mobile apps: Android (OkHttp / Retrofit), iOS (URLSession), Flutter (http), React Native (fetch).
No IP whitelisting is required — the API key is the only credential. Rotate it from your dashboard the moment you suspect a leak.
Connect from another SMM panel (as a provider)
Almost every SMM panel script supports adding an external "API provider". Paste these fields exactly — the same values work on Perfectpanel, SmartPanel, YoyoMedia, MedanPanel, Rocket-Panel, and any Perfectpanel fork.
| Name | Any label you like (e.g. our brand) |
| API URL | https://your-panel.com/api/v2 |
| API Key | Your key from Dashboard → API |
| HTTP Method | POST |
| Response format | JSON |
| Service ID field | service — use our numeric public ID, NOT your local service ID |
Importing services into your panel
- Save the provider, then click Sync or Import services. Your panel calls
action=servicesand stores every service with its ID, rate, min, max, refill/cancel flags, and description. - Set your markup before publishing. Common resellers use rate × 1.2 to rate × 2 (20–100% margin). If you sell in a different currency, apply your FX rate first, then the markup.
- Enable auto-sync every 1–6 hours. Rates, min/max, and default descriptions can change on our side — a stale local copy causes failed orders or thin margins.
- Map order status labels 1:1 — we already return
Pending / Processing / In progress / Completed / Partial / Canceled / Refunded, matching the Perfectpanel spec.
Recommended sync cadence
- Service list: every 3–6 hours (or on-demand via a Sync button).
- Order status: every 2–5 minutes for non-terminal orders; stop polling once status is
Completed / Canceled / Refunded / Partial. - Balance: check before every batch — abort the batch if balance is insufficient instead of firing failing orders one by one.
Mandatory integration checklist
Anyone building a new app, panel, or bot against this API must follow every point below. Skipping any of these is how duplicate orders, double refunds, and support tickets happen.
- Store our returned
orderID immediately, in the same transaction that debits your user. Never re-issue anaddcall for the same user action, even if your HTTP client times out. If you don't have the order ID back, use the idempotency flow (next point) — never blindly retry. - Send an idempotency key on every
addrequest.Includeidempotency_key(any unique string ≤ 64 chars, e.g. a UUID v4 you generate per order). If the same key is retried within 24 hours, we return the original order ID and do not charge again — safe to retry on network errors, 5xx, or client crashes.curl -X POST https://your-panel.com/api/v2 \ -d "key=YOUR_API_KEY" \ -d "action=add" \ -d "service=1234" \ -d "link=https://instagram.com/yourpage" \ -d "quantity=1000" \ -d "idempotency_key=your-uuid-v4-here" - Validate
service,link, andquantityclient-side first.Check quantity against the service'smin/maxand confirm the link matches the platform (Instagram URL for Instagram services, etc.). A rejected order is instant on our side but local validation saves a round trip. - Never poll
statusmore than once every 60 seconds for a single order.Prefer the batchstatusendpoint (up to 100 IDs per call). Stop polling as soon as the order reaches a terminal state. - Handle refunds exactly once.A canceled / refunded / partial order returns the money to your balance on our side. It is your job to credit your end-user only if you haven't already. Track a
refund_processedflag per order in your DB and check it before crediting. This is the #1 cause of double refunds. - Show real error messages.Business errors come back as HTTP 200 with an
errorfield (Perfectpanel spec). Read the JSON body, not just the status code. HTTP 401 means the key is invalid — surface it as "API disconnected" in your admin panel. - Use HTTPS. Never log the
keyparameter to files, analytics, or third-party error trackers. - Respect rate limits. Sustained bursts above ~10 req/s per key may be throttled. Batch where possible —
status,refill, andcancelall support multi-ID variants.
How we prevent duplicate orders & double refunds
The API is designed so a badly-written client cannot easily double-charge or double-refund an end user. Guarantees on our side:
- Idempotency by key: passing
idempotency_keyreturns the sameorderID for 24h. Balance is debited exactly once. - Fingerprint dedup: even without a key, two identical
addcalls (same service + link + quantity) from the same account within a short window are collapsed into one order. - Atomic charge: balance debit and order row are written in a single DB transaction — a network failure at our end never leaves you charged with no order.
- One-shot refunds: a canceled / refunded / partial order can only credit balance once. Re-checking
statusafter refund never re-issues funds. - Terminal status is final: once
Completed / Canceled / Refunded / Partial, the status does not change — safe to archive on your side.
Your side: mirror this. Wrap the add call + user debit + local order insert in one transaction, and never credit a user for a refund without checking an "already refunded" flag.
SMM panel manager basics
If you run an SMM panel and this is your first upstream integration, this checklist covers the operational side the API alone can't do for you.
- Services are imported, not automatic. After adding us as a provider, click Import services once, then run Sync on a schedule. New services we add later appear only after a sync — automation is your responsibility.
- Set markup per category, not globally. Instagram followers and YouTube watch-time have very different competitive rates. A flat 2× markup will overprice one and underprice the other.
- Keep a floor balance with us. If your balance hits zero, every new order fails instantly. Set a low-balance email alert at, say, 10% of your daily volume.
- Descriptions matter. Import our
descriptionfield verbatim (start count, refill window, drip rules). Rewriting them causes the most refund disputes. - Test with 1 unit first. When you add a new service to your storefront, place a real order at the minimum quantity from a test account before enabling it publicly.
- Cancel is best-effort. Some services support cancel only before delivery starts. Show your users "Cancel requested", never "Canceled", until we return the terminal status.
- Refill ≠ refund. Refill re-tops up dropped counts on the same order; refund returns money. Don't wire them to the same button.
- Log every API response. When a customer disputes an order, the raw JSON we returned at
addandstatustime is the fastest way to resolve it.
Need help? Contact support from your dashboard.