तकनीकी
SMM पैनल API इंटीग्रेशन: चालू कोड के साथ क़दम-दर-क़दम
आख़िरी अपडेट , PanelCompare की संपादकीय टीम द्वारा · 7 मिनट में पढ़ें
कोई भी कोड लिखने से पहले क्या चाहिए?
पैनल पर अकाउंट, भरा हुआ बैलेंस, और पैनल के डैशबोर्ड से API की। इस बाज़ार में कहीं भी न सैंडबॉक्स है, न टेस्ट की, न स्टेजिंग एनवायरनमेंट, इसलिए डेवलपमेंट में पहला सफल ऑर्डर एक असली ऑर्डर है, जिसमें असली पैसा लगता है और जो उस लिंक पर असल में डिलीवर होता है जो आप उसमें डालते हैं। ऐसा लिंक इस्तेमाल करें जो आपका हो और जिसकी आपको परवाह न हो।
कैटलॉग के आकार का भी हिसाब रखें। असली कैटलॉग करीब 3,000 से 8,000 पंक्तियों के होते हैं, और services एक्शन सबको एक ही, पन्नों में न बँटे जवाब में लौटाता है, इसलिए यह महँगी कॉल है जिसकी जगह रिक्वेस्ट के रास्ते में नहीं, तय शेड्यूल पर है। PanelCompare इंडेक्स में सिंक होने वाले दो पैनल क्रमशः 5,558 और 2,196 जोड़ी गई पंक्तियाँ लौटाते हैं (PanelCompare प्राइस इंडेक्स, 2026-09-10)।
ऑथेंटिकेट करने से पहले कैसे पक्का करें कि एंडपॉइंट मौजूद है?
बिना किसी की के services एक्शन भेजें। असली API v2 एंडपॉइंट स्ट्रक्चर्ड एरर से जवाब देता है, जो साबित करता है कि वह मौजूद है। 404 या HTML बॉडी उस API दावे को झूठा साबित करती है जो लगभग हर पैनल अपने होम पेज पर करता है। इसी तरह किसी पैनल की लॉगिन जानकारी हाथ में लिए बिना उसे जाँचा जा सकता है।
# A real endpoint answers with JSON, even without a key.
curl -s -X POST https://example-panel.com/api/v2 \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "action=services"
# Endpoint present:
# {"error":"Invalid API key"}
# Endpoint absent (HTTP 404, or an HTML login page):
# <!DOCTYPE html> ...
# Panels vary on the path. Try these three, in order:
# /api/v2 the convention
# /api/v2.php older builds
# /api a minorityकभी-कभार बिना की वाली कॉल एरर की जगह array लौटाती है। यह बग नहीं, खुला कैटलॉग है: पैनल अपनी पूरी सर्विस लिस्ट हर किसी के लिए प्रकाशित करता है। यह इतना कम होता है कि मिल जाए तो दर्ज करने लायक है।
कौन-से चार व्यवहार पहला इंटीग्रेशन तोड़ देते हैं?
| व्यवहार | कच्चा कोड क्या करता है | सही कोड क्या करता है |
|---|---|---|
| एरर पर HTTP 200 आता है | res.ok जाँचता है, true देखता है, और error object को नतीजा मान लेता है | पहले बॉडी पार्स करता है और बाकी किसी भी चीज़ से पहले error की देखता है |
| कुछ पैनल JSON error के साथ 4xx लौटाते हैं | सिर्फ़ स्टेटस देखकर अगले पाथ पर चला जाता है, और गलत की को ग़ायब API बताता है | 4xx बॉडी भी पार्स करता है; error की वाली बॉडी का मतलब है चालू एंडपॉइंट |
| संख्याएँ string के रूप में आती हैं | rate, min, max या remains की संख्या की तरह तुलना करता है और चुपचाप गलत चलता है | पार्स करते समय ही हर संख्या वाले फ़ील्ड को साफ़ तौर पर संख्या में बदलता है |
| कई ऑर्डरों का स्टेटस बहुवचन पैरामीटर है, एक्शन नहीं | हर ऑर्डर पर एक बार status कॉल करता है और रेट लिमिट में फँसता है | उसी status एक्शन के orders पैरामीटर में कॉमा से अलग 100 तक id भेजता है |
स्रोत: व्यवहार justanotherpanel.com/api से मिलाया गया और PanelCompare के सिंक क्लाइंट (src/lib/sync/panel-api.ts) में लागू किया गया, 2026-09-11।
401 वाला मामला ही आपसे सपोर्ट टिकट करवाता है
JustAnotherPanel गलत की पर HTTP 401 और {"error":"Invalid API key"} जैसी JSON बॉडी लौटाता है — यह स्ट्रक्चर्ड API जवाब है, जिस पर बस एरर स्टेटस लगा है। जो क्लाइंट हर 4xx को “यहाँ कोई API नहीं” मानता है, वह पैनल के मालिक को बताएगा कि उसके पैनल में कोई एंडपॉइंट नहीं, जबकि असली समस्या की है। स्टेटस से नतीजा निकालने से पहले बॉडी पार्स करें।
Node में सही क्लाइंट कैसा दिखता है?
const PATHS = ["/api/v2", "/api/v2.php", "/api"];
async function call(domain, key, params, path = PATHS[0]) {
const res = await fetch("https://" + domain + path, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ key, ...params }).toString(),
});
const text = await res.text();
let body;
try {
body = JSON.parse(text);
} catch {
// HTML here usually means the login page: the path is wrong, not the key.
throw new Error("Not JSON at " + path + " (HTTP " + res.status + ")");
}
// Errors arrive as HTTP 200 with an error field, and sometimes as 4xx with
// the same body. Either way the body is what decides.
if (body && typeof body === "object" && "error" in body) {
throw new Error("API error: " + body.error);
}
if (res.status >= 400) throw new Error("HTTP " + res.status);
return body;
}
const num = (v) => (v === null || v === undefined ? null : Number(v));
async function services(domain, key) {
const rows = await call(domain, key, { action: "services" });
// Every numeric field is a string on the wire. Coerce at the boundary.
return rows.map((r) => ({
id: String(r.service),
name: r.name,
type: r.type,
rate: num(r.rate),
min: num(r.min),
max: num(r.max),
refill: Boolean(r.refill),
cancel: Boolean(r.cancel),
}));
}
async function addOrder(domain, key, { service, link, quantity, runs, interval }) {
const params = { action: "add", service: String(service), link };
if (quantity != null) params.quantity = String(quantity);
// Drip-feed: quantity is PER RUN. Total delivered and charged is quantity x runs.
if (runs != null) params.runs = String(runs);
if (interval != null) params.interval = String(interval);
const body = await call(domain, key, params);
return String(body.order);
}
async function statusMany(domain, key, orderIds) {
const out = {};
// The plural parameter is capped at 100 ids. There is no multi_status action.
for (let i = 0; i < orderIds.length; i += 100) {
const chunk = orderIds.slice(i, i + 100);
const body = await call(domain, key, { action: "status", orders: chunk.join(",") });
for (const [id, row] of Object.entries(body)) {
out[id] = row && row.error
? { error: row.error }
: {
status: row.status,
charge: num(row.charge),
startCount: num(row.start_count),
remains: num(row.remains),
currency: row.currency,
};
}
}
return out;
}इस कोड की तीन बारीकियाँ ही असली बात हैं। स्टेटस देखने से पहले बॉडी पार्स होती है। 4xx वाली शाखा error की की जाँच से पहले नहीं, बाद में चलती है। और हर संख्या वाला फ़ील्ड साफ़ तौर पर संख्या में बदला जाता है, क्योंकि rate, min, max, charge, start_count और remains सब quoted string के रूप में आते हैं।
Python और PHP में यही क्लाइंट कैसा दिखता है?
import requests
PATHS = ["/api/v2", "/api/v2.php", "/api"]
class PanelError(Exception):
pass
def call(domain, key, params, path=PATHS[0], timeout=30):
res = requests.post(
"https://" + domain + path,
data={"key": key, **params},
headers={"Content-Type": "application/x-www-form-urlencoded"},
timeout=timeout,
)
try:
body = res.json()
except ValueError:
raise PanelError("Not JSON at %s (HTTP %s)" % (path, res.status_code))
# 200 with an error field is the normal failure shape. Some panels use 401
# with the same body, so the body is checked before the status.
if isinstance(body, dict) and "error" in body:
raise PanelError(body["error"])
if res.status_code >= 400:
raise PanelError("HTTP %s" % res.status_code)
return body
def services(domain, key):
rows = call(domain, key, {"action": "services"})
return [
{
"id": str(r["service"]),
"name": r["name"],
"rate": float(r["rate"]),
"min": int(r["min"]),
"max": int(r["max"]),
"refill": bool(r.get("refill")),
}
for r in rows
]
def status_many(domain, key, order_ids):
out = {}
for i in range(0, len(order_ids), 100): # the plural parameter caps at 100
chunk = ",".join(str(o) for o in order_ids[i : i + 100])
out.update(call(domain, key, {"action": "status", "orders": chunk}))
return out<?php
function panel_call(string $domain, string $key, array $params, string $path = "/api/v2") {
$ch = curl_init("https://" . $domain . $path);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query(array_merge(["key" => $key], $params)),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$text = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$body = json_decode($text, true);
if ($body === null) {
throw new RuntimeException("Not JSON at {$path} (HTTP {$code})");
}
// Body before status: errors come back as 200, and sometimes as 401.
if (is_array($body) && isset($body["error"])) {
throw new RuntimeException("API error: " . $body["error"]);
}
if ($code >= 400) {
throw new RuntimeException("HTTP {$code}");
}
return $body;
}
// Placing an order. Numeric fields come back as strings; cast what you compare.
$order = panel_call($domain, $key, [
"action" => "add",
"service" => "1",
"link" => "https://example.com/p/abc",
"quantity" => "1000",
]);
$orderId = (string) $order["order"];कैसे पता करें कि किसी ऑर्डर को कौन-से पैरामीटर चाहिए?
कैटलॉग की पंक्ति के type फ़ील्ड से, जो इकलौती जगह है जहाँ यह ज़रूरत लिखी होती है। हर add कॉल को service और link चाहिए; इसके अलावा क्या चाहिए, इसका अंदाज़ा नहीं लगाया जा सकता, और गलत सेट भेजने पर कोई समझदार डिफ़ॉल्ट नहीं, एरर आता है। फ़ॉर्म बनाने से पहले type पढ़ें।
function paramsForType(row, input) {
switch (row.type) {
case "Default":
case "Drip-feed":
// runs and interval are optional; quantity is PER RUN when runs is set.
return { quantity: input.quantity, runs: input.runs, interval: input.interval };
case "Custom Comments":
case "Custom Comments Package":
return { comments: input.comments.join("\n") };
case "Mentions User Followers":
return { quantity: input.quantity, username: input.username };
case "Mentions Hashtag":
return { quantity: input.quantity, hashtag: input.hashtag };
case "Mentions Media Likers":
return { quantity: input.quantity, media: input.mediaUrl };
case "Poll":
return { quantity: input.quantity, answer_number: input.answerNumber };
case "Subscriptions":
return {
username: input.username,
min: input.min,
max: input.max,
posts: input.posts,
delay: input.delay,
expiry: input.expiry,
};
case "Package":
return {}; // link only; the quantity is fixed by the package
default:
throw new Error("Unhandled order type: " + row.type);
}
}default शाखा जितनी दिखती है, उससे ज़्यादा मायने रखती है। पैनल नए ऑर्डर टाइप जोड़ते रहते हैं, और जो क्लाइंट चुपचाप सिर्फ़-मात्रा वाले ढाँचे पर गिर जाता है, वह ऐसे बिगड़े ऑर्डर देगा जो वॉलेट से पैसा कटने के बाद फ़ेल होंगे। अनजान type पर एरर फेंकना उस बग का सस्ता रूप है।
ऑर्डर स्टेटस की पोलिंग कैसे करनी चाहिए?
- 1.बहुवचन orders पैरामीटर से बैच में करें, एक बार में 100 id। मूल स्पेसिफ़िकेशन में कोई multi_status एक्शन नहीं है, और हर ऑर्डर पर एक रिक्वेस्ट ही इंटीग्रेशन को रेट लिमिट में फँसाती है।
- 2.पार्शियल को एरर नहीं, बराबर का एक नतीजा मानें। यह remains के आँकड़े और वॉलेट में अपने-आप क्रेडिट के साथ आता है, और इस बाज़ार में सबसे आम ग़ैर-अंतिम नतीजा है।
- 3.तय छोटे इंटरवल पर नहीं, बताए गए स्टार्ट टाइम के दायरे के अनुपात में शेड्यूल पर पोल करें। ज़्यादातर पंक्तियाँ दायरा प्रकाशित करती हैं; स्पीड लगभग कोई नहीं।
- 4.हर ऑर्डर id के साथ पैनल की पहचान सेव करें। ऑर्डर id सिर्फ़ एक पैनल के भीतर अनोखी होती हैं, और कहीं नहीं।
- 5.तय समय पर कैटलॉग दोबारा सिंक करें। सर्विस id हर पैनल की अपनी होती हैं और बदल सकती हैं, इसलिए सेव की गई id चुपचाप दूसरे स्टॉक की ओर इशारा करने लग सकती है।
- 6.रिक्वेस्ट बॉडी कभी लॉग न करें। की उसी में होती है, और उसकी कोई मियाद नहीं जो आपको बचा ले।
स्टेटस की शब्दावली छोटी और स्थिर है: पेंडिंग (Pending), इन प्रोग्रेस (In progress) या प्रोसेसिंग (Processing), कम्प्लीट (Completed), पार्शियल (Partial), कैंसल (Canceled)। इस सेट से बाहर की किसी भी चीज़ को सबसे करीबी जाने-पहचाने मान से जोड़ने की जगह सामने लाना चाहिए, क्योंकि अनपेक्षित स्टेटस लौटाने वाला पैनल आम तौर पर कुछ ऐसा बता रहा होता है जिसे वह मैपिंग छिपा देगी।
स्पेसिफ़िकेशन सुरक्षा की कौन-सी बंदिशें लगाता है?
की रिक्वेस्ट बॉडी में जाती है, बिना साइनिंग, बिना nonce और बिना टाइमस्टैम्प के, और स्पेसिफ़िकेशन कोई मियाद तय नहीं करता। इससे यह लंबे समय तक चलने वाला bearer सीक्रेट बन जाती है: जिसके पास भी यह है, वह वॉलेट बैलेंस खर्च कर सकता है और अकाउंट पर ऑर्डर किया गया हर लिंक पढ़ सकता है। नुकसान सीमित करने के लिए रीप्ले से कोई सुरक्षा नहीं, और डैशबोर्ड में की बदलने के अलावा उसे रद्द करने का कोई तरीका नहीं।
- ब्राउज़र से कभी की स्वीकार न करें, और क्लाइंट-साइड कोड से कभी उसे आगे न भेजें।
- रिक्वेस्ट बॉडी कभी लॉग न करें, और जाँच लें कि आपकी HTTP क्लाइंट लाइब्रेरी आपकी जगह उसे लॉग तो नहीं कर रही।
- की को ऐसे एनवायरनमेंट वेरिएबल से बाहर रखें जो बिल्ड लॉग तक पहुँचते हों, और डेटाबेस से भी बाहर।
- हर थर्ड-पार्टी इंटीग्रेशन में बदलाव के बाद, और स्टाफ़ में किसी भी बदलाव के बाद की बदलें।
- जिस पल की किसी स्क्रीनशॉट, टिकट या साझा दस्तावेज़ में दिख जाए, उसी पल उसे लीक हुई मान लें।
फटाफट जवाब
SMM पैनल API एरर पर भी 200 क्यों लौटाता है?
क्योंकि स्पेसिफ़िकेशन HTTP स्टेटस कोड का कोई सार्थक इस्तेमाल नहीं करता। गड़बड़ी आम तौर पर HTTP 200 के साथ लौटती है और बॉडी में error object होता है, और कुछ पैनल गलत की पर 401 और वैसा ही JSON लौटाते हैं, इसलिए हर क्लाइंट को गड़बड़ी पकड़ने के लिए बॉडी पार्स करनी पड़ती है। सिर्फ़ res.ok जाँचने से कुछ पता नहीं चलता।
क्या SMM पैनल API के लिए कोई सैंडबॉक्स या टेस्ट की है?
नहीं। इस बाज़ार में कहीं भी सैंडबॉक्स नहीं है, इसलिए पहली सफल add कॉल असली बैलेंस पर असली ऑर्डर है। पंक्ति जितनी छोटी मात्रा होने दे, उसी पर, अपने ही किसी लिंक पर डेवलपमेंट करें।
एक साथ कई ऑर्डरों का स्टेटस कैसे देखें?
उसी status एक्शन को बहुवचन orders पैरामीटर के साथ इस्तेमाल करें, जिसमें कॉमा से अलग की गई 100 तक id हों। मूल स्पेसिफ़िकेशन में अलग से कोई multi_status एक्शन नहीं है।
मेरा ड्रिप-फ़ीड ऑर्डर उम्मीद से दस गुना पैसा क्यों काट रहा है?
क्योंकि ड्रिप-फ़ीड ऑर्डर पर मात्रा हर रन की होती है। कुल डिलीवरी और कुल रकम = मात्रा × रन, इसलिए runs=10 के साथ 1,000 डालने का मतलब है 10,000 यूनिट का ऑर्डर और उन्हीं का भुगतान।
क्या एक ही क्लाइंट अलग-अलग पैनलों पर इस्तेमाल कर सकते हैं?
हाँ, बेस URL और की बदलकर — पूरे बाज़ार के एक ही स्पेसिफ़िकेशन लागू करने का यही व्यावहारिक नतीजा है। जो साथ नहीं आता, वह है सर्विस id की मैपिंग, क्योंकि id हर पैनल की अपनी होती हैं और दूसरी ओर मोड़ी जा सकती हैं।
यहाँ का हर आँकड़ा स्रोत और तारीख़ के साथ है
इस बाज़ार में कीमतें हर हफ़्ते बदलती हैं, इसलिए जिस संख्या के साथ उसे लेने की तारीख़ न हो, वह सिर्फ़ सजावट है। यह गाइड जहाँ भी कोई आँकड़ा देती है, वहाँ उसका स्रोत और जाँच का समय बताती है। अगर इनमें से कोई ग़लत है, तो हमारे बारे में पेज पर दी गई सुधार की प्रक्रिया में दो कामकाजी दिनों में जवाब देने का लक्ष्य है, और सुधार चुपचाप नहीं किए जाते, बल्कि तारीख़ वाले नोट के साथ प्रकाशित किए जाते हैं।