If you run social campaigns for more than a handful of clients, at some point you stop clicking through a dashboard and start talking to an API. That switch looks trivial until you actually build it.
I maintain the reseller API for Boostero, a social media marketing panel that has been running since 2020 and has processed 11.9 million orders for over 209,000 users. Most of what follows applies to any panel API in this space, since they share a common ancestry. The code examples are in our GitLab repository in PHP, Python and Node.
Here are the five things that trip people up, in the order they usually hit.
1. It is form-encoded, not JSON
Modern API habits say Content-Type: application/json. This family of APIs predates that reflex and takes form-encoded POST bodies.
import requests
API_URL = "https://boostero.com/api/v2"
API_KEY = "YOUR_API_KEY"
def call(**params):
params["key"] = API_KEY
response = requests.post(API_URL, data=params, timeout=30)
response.raise_for_status()
return response.json()
Note data= rather than json=. Sending a JSON body gets you a confusing failure rather than a clear one, because the server is looking for form fields that are not there.
2. Errors arrive with HTTP 200
This is the one that costs people an afternoon. A failed request does not necessarily come back as a 4xx. It comes back as HTTP 200 with an error field in the body.
data = call(action="add", service=1, link=url, quantity=100)
if isinstance(data, dict) and "error" in data:
raise RuntimeError(data["error"])
If your client only checks response.ok, every rejected order looks like a success and you find out later when nothing was delivered. Check the body, always.
There is a wider lesson here that I have hit in other systems too: a transport-level success is not an application-level success. Anywhere those two get conflated, you end up with a monitor that reports green while the thing it monitors is broken.
3. Partial is a normal state, not a failure
Order statuses in this space are Pending, In progress, Processing, Completed, Partial and Canceled.
Partial means some of the quantity was delivered and the remainder was refunded to your balance. It is not an error, and it is not something to retry. If your state machine only models success and failure, partial orders will either get retried into double delivery or flagged as incidents that need no action.
Model it as its own terminal state and reconcile the refund against your own ledger.
4. Poll in batches, with backoff
The obvious status loop is one request per order. That does not scale past a few dozen orders and it is unkind to the API you depend on.
$multi = boostero([
'action' => 'status',
'orders' => implode(',', $orderIds),
]);
Batch the IDs, and back off as orders age. Something like: every minute for the first ten minutes, every five minutes for the first hour, then hourly. Most orders in our data begin within minutes, so the early window is where polling actually earns its cost. After that you are mostly asking the same question repeatedly.
5. There is no idempotency key, so bring your own
If your worker crashes between sending add and persisting the returned order ID, you have an order you cannot see. Retry naively and you place it twice.
There is no native idempotency key, so generate one yourself, write it to your own store before the call, and record the returned order ID against it after:
job_id = uuid4().hex
db.save(job_id, status="submitting", order_id=None)
result = call(action="add", service=service_id, link=url, quantity=qty)
db.update(job_id, status="submitted", order_id=result["order"])
Now a crash leaves a row in submitting that a reconciliation pass can investigate, rather than an invisible order and a guess.
What the demand data says about all this
One more thing worth knowing if you are building tooling in this space, because it changes what your code should optimise for.
We analysed just over a million orders placed between January 2025 and June 2026. Orders for followers fell to 9 percent of all demand, while shares and saves doubled from 5 percent to 10 percent, and views now account for 42 percent of everything ordered. Instagram and TikTok together take roughly three quarters of the volume.
The practical consequence for a client library: your hot path is high-volume view and engagement orders on two platforms, not follower orders spread evenly across twenty. Batch sizing, polling intervals and retry budgets should be tuned for that shape. The full dataset is published and free to cite.
One honest caveat
Worth stating plainly, since it affects what you should promise the people using your tooling: purchased engagement supplies signal types. Platforms decide what they do with those signals. No API, ours included, can promise a distribution or ranking outcome, and any client library that implies otherwise is writing cheques its backend cannot cash.
Build for the mechanics you control: correct orders, honest state, clean reconciliation.
Code examples in PHP, Python, Node and curl are in the GitLab repo, MIT licensed. Full API reference at boostero.com/api. Happy to answer questions about the endpoints in the comments.