When a panel owner thinks about security, they think about an attack from outside: load, password guessing, vulnerability scanners. But a panel with an open API has a second door, and it looks entirely legitimate: requests arrive with a valid key, from your own registered customer, and are indistinguishable from normal traffic. Here is what a reseller can do to your panel with a single curl command, which checks stop it, and why hiding buttons achieves nothing.
An attack that holds your key
The difference from a regular attack is fundamental, and everything else follows from it. External attacks are repelled at the perimeter: traffic filtering, login attempt limits, a captcha - there is a separate breakdown of panel security for that. Here it is the other way round: the traffic is legitimate, the key is real, the customer exists, and no perimeter filter will reject that request.
Malice is rarely involved. In practice there are three sources:
- A reseller who automated carelessly. Their script polls the status of every order every ten seconds, retries in a loop on any error, and sends a duplicate whenever a response arrives late. They are not attacking you, they simply did not think it through.
- A customer probing the edges. An order for one unit, an order for a million, an empty link, somebody else's order number in
status. Usually curiosity, occasionally reconnaissance. - Whoever ended up with a key that was not theirs. Keys leak from a reseller's repository, from a screenshot, from a former employee. To the panel it is still "your customer".
The main rule: the interface protects nothing
This is the most common stumble. Hiding a button, pulling a service from the storefront, disabling a section - all of that changes only what a browser sees. A customer with a key never opens a browser.
In our core the rule is enforced by three separate server-side checks, and the comment on each of them carries the same thought: without it the restriction could be bypassed by one line of curl.
- A hidden service is still orderable. Geo restrictions, removal from the storefront, a "hidden" flag - those are about display. The check belongs on the server at the moment the order is placed, otherwise a service you pulled from the catalog for a given country will go straight through on a direct request.
- A disabled action is still callable. If you forbade customers to cancel or request refills, the button disappears from the account, but the API method does not. Permissions have to be checked where the action actually happens.
- A suspended customer still has their key. Suspension closes the login, but the key in their hands is unchanged. If the API does not check account status, suspension is bypassed in a second.
And a fourth, more general one. Every order check - service availability, quantity limits, email confirmation, service type, sufficient balance - has to live in one place that all entry points pass through: the storefront form, the API and guest checkout alike. Splitting them across callers is not an option: sooner or later one entry point will accept what another rejects, and they will drift apart silently.
Fake and junk orders
The second layer is orders that are formally valid but bring you nothing. Each one goes out to a provider, burns their limits and costs you the time it takes to sort out.
- Duplicates on one link. The classic symptom of a sloppy reseller script: the response arrived late, the script assumed the order was not created, and sent it again. The cure is a per-service flag: refuse a second order on the same link for the same service while the previous one is still running. One detail matters - completed and cancelled orders must not block anything, because ordering again after delivery is entirely legitimate, and forbidding it just produces a ticket.
- Quantities out of range. The minimum and maximum of a service have to be enforced server-side. Separately, the step: if the provider only accepts hundreds, an order for 250 gets rejected, and the money has already left the customer's balance.
- Drip-feed portions. Even careful integrators get this wrong. The service limits apply to the portion, meaning the volume of a single run, not to the order total. Checking the total will let through an order the provider breaks on its very first run.
- The wrong service type. For custom comments the quantity is the number of lines, package services are priced per order, and subscriptions are not priced by volume at all. If the API accepts
quantityfor every type indiscriminately, your charge will drift from what the provider bills. Which types exist and what parameters they take is in the API methods reference.
Burning through rate limits
The third layer is the nastiest, because it hits everyone at once rather than one customer. A script polling statuses in a tight loop eats the shared request budget, and every other reseller stalls with it.
Some reference numbers. Our customer API is capped at a little over a hundred requests per minute and the admin API at roughly three times that, which is not an inconsistency: a customer key is normally used by a person or a small script, while the admin API is where a robot comes in batches. Panels on the widespread engine are stricter still - around five requests per second per panel, and a real integration runs into that more easily than you would expect.
Two consequences people rarely plan for:
- The budget is shared, the culprit is one. If the counter is keyed by address rather than by key, several resellers on one network share a single budget. One careless script stalls the rest, and all of them come to complain.
- Somebody else's limits are also yours. Your panel talks to the provider under their rules, and a flood of orders from one customer burns your allowance there. Their API then starts refusing everything, and you are the one who has to untangle it.
The right answer is not a ban but batch methods and clear documentation: polling statuses in batches of up to a hundred numbers solves the same problem as a loop over individual orders, at a hundredth of the cost to both sides.
What to do about keys
- Keys must be long and random. Sixty-odd characters from a cryptographic generator. A key derived from a customer id or an email is guessable.
- Rotation is a button, not a support ticket. Leaks are not rare, and if fixing one requires correspondence, the customer will simply do nothing.
- A key must never come back out. Not in API responses, not in exports, not in the customer list in your admin panel. Shown once, and after that only reissued.
- HTTPS only. The key travels as a request parameter, so over plain HTTP it is visible in full to every intermediate hop.
How to see any of this
The core problem with abuse from inside is that it does not look like a problem: orders are created, money moves, the panel works. The minimum worth having:
- A source marker on orders. An order placed with a key should be distinguishable from one placed in the storefront. Without that you cannot even answer what share of your turnover goes through the API.
- A breakdown by customer. A single reseller generating ninety per cent of your requests and two per cent of your revenue is only visible in that cut. What else is worth watching regularly is in the breakdown of panel metrics.
- Failures, not just successes. A spike of "incorrect API key" is somebody guessing. A spike of "incorrect service" usually means a script is working from a stale copy of your catalog.
Wrap-up
An open API is not a hole, it is the main way a panel grows: a reseller with a key brings volume you will never gather by hand. It just has to be defended differently from a website. Every rule lives on the server, every entry point runs the same checks, limits are published up front, and a key is reissued in one click. A hidden button protects nothing. Who should get a key and why is covered in the piece on panel APIs for resellers.
You can see how this works on a live panel without spending anything: orders routed through our own catalog are not metered at all, and with third-party providers the first 100 orders a month are free. The key is issued immediately, so you can run your own script against it on day one.