Skip to content

Overview

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.

Who NSIN is

NSIN is the provider, not a reseller. There is no “reseller #1”. A direct NSIN customer has reseller_id = null.

Money, in one paragraph

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.

What the reseller’s clients never receive

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.

Amounts

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.

Rate limits

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 window is fixed, not sliding. The counter resets a minute after the window’s first request, so two bursts either side of a reset put 600 requests through in seconds and are both inside the limit. Do not build a client that depends on that.
  • The counter lives in the API process’s memory and is not shared between instances, so a deployment running more than one API process gives a caller roughly one budget per process. Treat 300/min as the floor you are guaranteed, never as a ceiling you can measure.

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.

Information

  • OpenAPI version: 3.1.0

Authorization: Bearer <credential>two kinds of credential, one header.

The middleware distinguishes them by prefix:

CredentialUsed byHow 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 elsethe reseller dashboardparsed 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