Automating Social Media Growth via REST API v2 with Python: A Developer's Practical Guide
*## How SMM panel APIs work, where most integrations break, and how to build a reliable order pipeline in Python with the open-source Resimi SDK * If you run a reseller panel, a growth agency, or an internal marketing
*## How SMM panel APIs work, where most integrations break, and how to build a reliable order pipeline in Python with the open-source Resimi SDK
*
If you run a reseller panel, a growth agency, or an internal marketing tool, sooner or later you have to integrate an SMM (social media marketing) panel API. Nearly all of them speak the same dialect, informally called API v2: one endpoint, form-encoded POST requests, an API key and an action.
The protocol is simple. Building a reliable integration on top of it is not. This guide covers where these integrations usually break, what a sound API v2 contract looks like, and how to build a working order pipeline in Python with the open-source Resimi Python SDK.
The real-world problem: why panel integrations break
Most developers who have integrated an SMM panel have hit the same three walls.
1. Catalogs full of dead and duplicate IDs
Many panels re-list every service their upstream suppliers offer, so a single "Instagram Likes" category can hold dozens of near-identical rows. Some of those IDs point at services that were disabled upstream months ago. Your sync job imports them, a customer orders one, and the order fails or sits in Pending forever.
A large catalog is not the same as a usable one. What you want is a curated list where each service ID maps to something that actually delivers, with stable min/max limits and a rate you can price against.
2. Funding friction and network fees
SMM panels are overwhelmingly funded with cryptocurrency. That is convenient until you top up $5 over Ethereum mainnet and a meaningful share of it goes to gas. High deposit minimums make it worse: you end up parking more money on a panel than you planned to just to test it.
3. No push notifications, so polling is your job
The API v2 convention has no webhooks. Once you place an order, the only way to learn it finished, partially delivered or was cancelled is to ask. Naive integrations poll each order individually in a tight loop, burn through rate limits, and still miss state changes when the network hiccups.
None of these are reasons to avoid panel APIs. They are reasons to choose a provider with a clean contract and to write your client defensively.
Architecture: what a clean API v2 contract looks like
Resimi is a crypto-native SMM panel built around the standard v2 format, so existing integrations port over with minimal changes. Several of its design choices fix the problems above at the protocol level. The full API documentation is public, but these are the parts that matter for reliability:
One endpoint, predictable errors. Every call is a POST to https://resimismm.com/api/v2. Failures come back as JSON in the form {"error": "..."}, never as HTML error pages. That means a client can turn every failure into a typed exception.
Batch status. action=status accepts either a single order or an orders list of up to 100 comma-separated IDs, which return one object keyed by order ID. One request replaces a hundred, and that is what makes polling viable without webhooks.
Idempotent order creation. The add action accepts an optional client_request_id (1 to 36 letters, digits or hyphens). If your request times out and you retry it with the same ID, the API returns the original order instead of charging you twice. This is the most important reliability feature any order API can have, and few panels offer it.
Server-side money safety. Every charge and refund is written to a ledger. Partially delivered orders are refunded automatically for the undelivered remainder, and refunds are idempotent per order, so a retried cancel can never refund twice.
A curated catalog. At the time of writing (October 2026), Resimi lists 2,147 active services, filtered from its upstream providers rather than mirrored wholesale. Every price is public on the live services and pricing page, no signup required.
Low-friction crypto deposits. Top-ups go through NOWPayments (300+ coins) with a $2 minimum deposit, and the balance never expires. To keep fees negligible on small top-ups, choose a low-fee network such as Litecoin (LTC) or USDC on Base instead of Ethereum mainnet. Each coin has its own network minimum, which is shown at checkout.
Step-by-step tutorial
Step 1: Install the SDK
The SDK is MIT-licensed, depends only on requests, and supports Python 3.8+. Install the tagged release straight from GitHub:
pip install "git+https://github.com/Vetom1k/[email protected]"
Step 2: Initialize the client
Create an account at resimismm.com and copy your key from the API page in your dashboard. Keep it out of source control:
export RESIMI_API_KEY="your-api-key"
import os
from resimi import ResimiClient, ResimiAuthError
client = ResimiClient(
api_key=os.environ["RESIMI_API_KEY"],
timeout=20, # seconds; the default is 30
)
try:
balance = client.get_balance()
except ResimiAuthError as exc:
raise SystemExit(f"API key rejected: {exc}")
print(f"Balance: {balance['balance']} {balance['currency']}")
# Balance: 12.4800 USD
ResimiAuthError is raised for a missing, invalid or unauthorized key, so you can fail fast at startup instead of discovering the problem on the first order.
Step 3: Query the catalog
get_services() returns every active service. Each item has service (the ID), category, name, type, rate (USD per 1,000), min, max, and the dripfeed, refill and cancel flags.
In a real integration you rarely hardcode service IDs. You search the catalog for what you need and pick the best match on price and limits:
from typing import Any, Dict, List, Optional
def find_services(
services: List[Dict[str, Any]],
keyword: str,
quantity: int,
require_refill: bool = False,
) -> List[Dict[str, Any]]:
"""Services whose name or category matches `keyword` and accept `quantity`, cheapest first."""
keyword = keyword.lower()
matches = [
s for s in services
if keyword in f"{s['category']} {s['name']}".lower()
and int(s["min"]) <= quantity <= int(s["max"])
and (s.get("refill") or not require_refill)
]
return sorted(matches, key=lambda s: float(s["rate"]))
def order_cost(service: Dict[str, Any], quantity: int) -> float:
"""Rates are quoted per 1,000 units."""
return round(float(service["rate"]) * quantity / 1000, 4)
services = client.get_services()
print(f"{len(services)} active services") # 2147 active services (October 2026)
candidates = find_services(services, "instagram likes", quantity=500, require_refill=True)
for svc in candidates[:5]:
print(f"[{svc['service']}] {svc['name']} | ${svc['rate']}/1K | "
f"min {svc['min']} max {svc['max']} | cost for 500: ${order_cost(svc, 500)}")
Cache the catalog (an hour is plenty) rather than downloading it before every order. Then compare against the public pricing page if you want to sanity-check what your sync imported.
Step 4: Create an order
from resimi import ResimiAPIError
service = candidates[0]
try:
order = client.create_order(
service_id=int(service["service"]),
link="https://www.instagram.com/p/YOUR_POST_ID/",
quantity=500,
)
except ResimiAPIError as exc:
# Business-rule failures land here: insufficient balance, quantity
# outside min/max, invalid link, inactive service...
raise SystemExit(f"Order rejected: {exc} (HTTP {exc.code}) {exc.response}")
order_id = order["order"]
print(f"Placed order #{order_id}")
For drip-feed delivery (for example 100 units a day for a week), pass runs and interval in minutes together. Here quantity is the amount per run, and only services with dripfeed enabled accept it:
client.create_order(int(service["service"]), "https://www.instagram.com/p/YOUR_POST_ID/",
quantity=100, runs=7, interval=1440)
Retry-safe ordering with client_request_id
If a request times out, you cannot tell whether the order was created. Blindly retrying risks a double charge. The API solves this with client_request_id, but SDK v1.0.0 does not forward that parameter yet. For workloads where a retry must never double-charge, call the endpoint directly for this one action and keep using the SDK for everything else:
import uuid
import requests
def place_order_idempotent(api_key: str, service_id: int, link: str, quantity: int,
attempts: int = 3) -> int:
"""Place an order that is safe to retry: same request id, at most one charge."""
request_id = str(uuid.uuid4()) # 36 chars: letters, digits, hyphens
payload = {
"key": api_key,
"action": "add",
"service": service_id,
"link": link,
"quantity": quantity,
"client_request_id": request_id,
}
last_error = None
for _ in range(attempts):
try:
body = requests.post("https://resimismm.com/api/v2", data=payload, timeout=20).json()
except (requests.RequestException, ValueError) as exc:
last_error = exc
continue # same request id, so the retry cannot create a second order
if "error" in body:
raise RuntimeError(body["error"])
return int(body["order"])
raise RuntimeError(f"Order not confirmed after {attempts} attempts: {last_error}")
Persist request_id alongside your own job record before the first attempt, and a crash-and-restart can retry safely too.
Step 5: Poll order status, in batches and defensively
This is where most integrations go wrong. Three rules:
-
Batch.
get_multiple_orders_status()takes up to 100 IDs per call. -
Normalize status strings. Different panels spell them differently (
In progress,In_progress,Canceled,Cancelled), so compare normalized lowercase values and never raw strings. - Separate transport failures from API failures. A timeout means "try again later"; an auth error means "stop".
import os
import time
from typing import Dict, Iterable, List
from resimi import ResimiAPIError, ResimiAuthError, ResimiClient, ResimiError
TERMINAL = {"completed", "partial", "canceled", "cancelled", "refunded", "error"}
def normalize(status: str) -> str:
return status.strip().lower().replace("_", " ")
def chunks(ids: List[int], size: int = 100) -> Iterable[List[int]]:
for i in range(0, len(ids), size):
yield ids[i:i + size]
def wait_for_orders(client: ResimiClient, order_ids: List[int],
poll_every: int = 60, max_backoff: int = 900,
deadline_s: int = 24 * 3600) -> Dict[int, dict]:
"""Poll until every order reaches a terminal state or the deadline passes."""
pending = set(order_ids)
results: Dict[int, dict] = {}
delay = poll_every
started = time.monotonic()
while pending and time.monotonic() - started < deadline_s:
try:
for batch in chunks(sorted(pending)):
for oid, info in client.get_multiple_orders_status(batch).items():
oid = int(oid)
if "error" in info: # e.g. "Order not found": stop tracking it
results[oid] = info
pending.discard(oid)
continue
status = normalize(info["status"])
if status in TERMINAL:
results[oid] = info
pending.discard(oid)
print(f"#{oid}: {status} (remains {info['remains']}, charge ${info['charge']})")
delay = poll_every # healthy round trip: reset backoff
except ResimiAuthError:
raise # a bad key will not fix itself; surface it
except ResimiAPIError as exc:
print(f"API error while polling: {exc}; backing off")
delay = min(delay * 2, max_backoff)
except ResimiError as exc: # network failure or timeout
print(f"Transport error: {exc}; backing off")
delay = min(delay * 2, max_backoff)
if pending:
time.sleep(delay)
return results
client = ResimiClient(os.environ["RESIMI_API_KEY"])
final = wait_for_orders(client, [order_id])
What the status payload tells you:
| Field | Meaning |
|---|---|
status |
Pending → Processing / In progress → Completed, Partial or Cancelled
|
start_count |
The counter on the target (likes, followers...) when delivery began |
remains |
Units still to deliver; quantity - remains is your delivered count |
charge |
What the order cost, in USD |
For a progress bar, compute delivered = quantity - remains, but only once the provider has reported a start. While an order is still Pending, remains can legitimately be 0, and a naive calculation shows a brand-new order as 100% delivered. Treat Pending and Processing with start_count == 0 as "queued".
Step 6: Cancel and refill
# Cancel where the service allows it; refundable amounts go back to your balance.
client.cancel_order(order_id)
# Refill a completed order on a refill-enabled service (one request per 24 hours).
refill = client.request_refill(order_id)
print(refill) # {'refill': '5512'}
Putting it together
A production pipeline built from the steps above looks like this:
- Sync the catalog hourly into your own database and map your products to Resimi service IDs.
-
Validate each customer order locally: quantity within
min/max, link format, balance headroom. -
Place the order with a persisted
client_request_id, so retries are safe. - Poll all open orders in batches of 100 every minute or two, with exponential backoff on errors.
-
Settle on terminal states: mark completed, handle
Partialby refunding the undelivered share to your own customer, and alert onCancelled.
That is a few hundred lines of code, most of it your own business logic. The transport, error typing and status batching come from the SDK.
Resources
- Resimi: resimismm.com, a crypto-funded SMM panel with a $2 minimum deposit
- API v2 documentation: resimismm.com/api-docs
- Live services and pricing: resimismm.com/services
- Python SDK (MIT, open source): github.com/Vetom1k/Resimi. Issues and pull requests are welcome.
If you build something with it (a reseller dashboard, a Discord bot, or a scheduled growth job), I'd like to hear how you handled polling at scale.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.