Information
- OpenAPI version:
3.1.0
The API a reseller uses to manage his own clients on NSIN.
This document is the contract. The Go handlers are generated from it
(oapi-codegen), and the reseller dashboard’s TypeScript client is generated
from it (openapi-typescript). Nothing is hand-written on either side. If a
behaviour is not described here, it does not exist.
NSIN is the provider, not a reseller. There is no “reseller #1”.
A direct NSIN customer has reseller_id = null.
A reseller tops up his own wallet at face value through the existing
Zarinpal flow (not in this API), and everything his clients consume is paid
for out of that wallet. A plan purchase debits the client’s wallet at
NSIN list price, but the money is the reseller’s: POST /services moves the
shortfall from his wallet into the client’s inside the same transaction —
both ledger rows carry ref_type: reseller_autofund — and only then buys
the plan. Pre-funding a client with POST /clients/{clientId}/credit is
therefore optional; it makes that automatic top-up smaller, or a no-op.
There is no reseller discount at purchase time, ever.
Traffic is not billed to a reseller’s client at all. He has no
pay-as-you-go: usage beyond the plan’s included GB is recorded with
charged_rials = 0, no wallet is debited, and the quota sweep suspends his
domain instead. max_traffic_gb is a cap, not a meter, so a reseller’s cost
for a client is exactly the plans he bought him — nothing arrives later.
Because the reseller is the one paying, a 402 on a purchase is about his
wallet, not his client’s (credit_limit_reached). An admin can grant him an
overdraft — reseller_max_negative_rials, default 0, so by default a
purchase he cannot afford simply fails — and a reseller who stays below zero
past the configured grace window has every domain in his tenancy, his own
and all his clients’, suspended until he settles.
The client’s wallet is real, but the client can never see or reach it: every
wallet, invoice, price and ticket endpoint returns 403 for a user with a
reseller_id.
All business SMS and email about a reseller’s client (domain down, plan expiring, balance negative, …) is redirected to the reseller, not the client. Two things are deliberately exempt and still go to the client: login OTP and password reset — both are account recovery, and suppressing either would lock the client out of the panel permanently.
Every monetary field is an integer count of Rials (*_rials), matching
the backend. The UI divides by 10 to display Toman. Never send a float.
Every operation in this document is throttled. One limiter is mounted on
the whole /reseller/v1 group, so all 83 routes share the same budget:
300 requests per minute by default, per deployment
(RESELLER_RATE_LIMIT). Over budget is 429 with
{"error": "Rate limit exceeded. Slow down and retry shortly.", "code": "rate_limited"}.
The budget is counted per credential, not per reseller. Each nsin_live_
token has its own, and a dashboard session has another. That is deliberate: a
runaway WHMCS cron must not be able to lock its owner out of his own panel,
and revoking that one token must be enough to stop it.
Two mechanics to build against rather than discover:
The limiter is mounted after authentication and the reseller check, so a
rejected credential never spends anyone’s budget — and a 401 or 403 tells
you nothing about how much of yours is left.
Every response the limiter lets through carries X-RateLimit-Limit,
X-RateLimit-Remaining and X-RateLimit-Reset (seconds until the window
resets). The 429 itself does not carry those; it carries Retry-After,
in seconds. Sleep for that rather than retrying immediately.
Only the operations most likely to reach the limit — the bulk and list reads
an integration walks in a loop, and the money writes — declare 429
individually. See the TooManyRequests response component; it applies to
every operation here, listed or not.
Authorization: Bearer <credential> — two kinds of credential, one header.
The middleware distinguishes them by prefix:
| Credential | Used by | How it’s checked |
|---|---|---|
nsin_live_<keyid>_<secret> | machine clients (WHMCS, the reseller’s own site) | look up key_id, verify the secret against its bcrypt hash |
| anything else | the reseller dashboard | parsed as a session JWT, exactly like the admin panel |
Either way the handler ends up with the same callerID, and every handler
then calls mustOwnClient / mustOwnDomain before touching a row.
Token endpoints are the one exception: they require the JWT form, so a leaked API token cannot mint another one.
Security scheme type: http