List my clients
const url = 'https://api.nsin.cloud/reseller/v1/clients?page=1&per_page=25';const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url 'https://api.nsin.cloud/reseller/v1/clients?page=1&per_page=25' \ --header 'Authorization: Bearer <token>'Every user whose users.reseller_id is the caller, newest first (id DESC) — a direct NSIN customer, or another reseller’s client, is outside the query that builds the page rather than filtered out of it. q is a raw substring match against email, phone_number and name, any one of which may hit; phone numbers are stored normalized (09123456789), so searching for +98912… finds nothing. Disabled accounts come back like any other — there is no active filter — so read active per row.
Each row carries wallet_balance_rials, the number the client himself is never shown. A balance that fails to load is rendered as 0 and logged, so a zero here is not proof of an empty wallet. per_page above 100 is clamped rather than rejected: ask for 1000 and you silently get 100, so drive your loop off meta.total.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Query Parameters
Section titled “Query Parameters ”Free-text match on name, email, or phone.
Responses
Section titled “ Responses ”A page of clients
object
An end user owned by the calling reseller.
object
Whether the panel will accept this number without challenging it.
true for a freshly provisioned client — you vouched for it. See
phone_verified_by for the basis.
How the number came to be verified.
otp— the client consumed a one-time code sent to this exact number. Proof that somebody holds the handset.provider— you entered it and vouched for it. Trust in you, not proof of the handset.admin— set by NSIN staff.""— not verified.
Changing a client’s phone via PATCH /clients/{clientId} re-bases
this on provider: an OTP consumed against the old number proves
nothing about the new one, but your vouching still applies.
false = account disabled; cannot log in.
Visible to the reseller. The client is never shown this, and gets 403 from every wallet endpoint.
object
Example
{ "data": [ { "phone_number": "09123456789", "phone_verified_by": "", "national_code": "0012345678" } ]}Missing, malformed, expired, or revoked credential.
Every error body carries error, a human-readable sentence. Some also
carry code, a stable machine-readable reason — branch on that, never on
the sentence, which is prose and gets reworded.
object
Present only on the failures worth branching on, and deliberately not an exhaustive enum: treat a code you do not recognise as if it were absent and fall back to the status code.
The ones that exist today:
credit_limit_reached— 402 fromPOST /servicesandPOST /services/{serviceId}/change-plan. Your wallet, not the client’s: funding the purchase would take you past your overdraft (default0, i.e. past zero). Top up; do not credit the client.domain_disabled— 409 from a configuration write on a disabled domain (records, rules, cache, SSL, members, settings). The domain is read-only untilPOST /domains/{domainId}/enable.unknown_rule_type— 404 from the rules paths whenruleTypeis not one of the documented values. You get this rather than an empty array, so a typo cannot read as a rule set that happens to be empty.rate_limited— 429 from any route. Keyed per credential, so one runaway integration cannot lock its owner out of the dashboard.session_check_failed— 503, and only on calls made with a dashboard session JWT. The session could not be verified, which is not the same as knowing it is revoked, so it is retryable and the session survives. Machine tokens never see this.
Example
{ "error": "forbidden", "code": "credit_limit_reached"}The caller’s request budget is spent. 300 requests per minute by
default (RESELLER_RATE_LIMIT), and the same limiter covers every one of
the 83 operations in this document.
Counted per credential, not per reseller: each nsin_live_ token has
its own budget and a dashboard session has another, so one runaway
integration cannot lock its owner out of his own panel, and revoking that
token is enough to stop it.
Branch on "code": "rate_limited" in the body. The message beside it is
written for a human and may be reworded.
Retry-After is set, in seconds until the window resets — wait that long
rather than retrying at once. The X-RateLimit-* headers are not on
this response; they appear only on the responses the limiter let through,
so a client that reads its remaining budget from the 429 alone will never
see one.
Every error body carries error, a human-readable sentence. Some also
carry code, a stable machine-readable reason — branch on that, never on
the sentence, which is prose and gets reworded.
object
Present only on the failures worth branching on, and deliberately not an exhaustive enum: treat a code you do not recognise as if it were absent and fall back to the status code.
The ones that exist today:
credit_limit_reached— 402 fromPOST /servicesandPOST /services/{serviceId}/change-plan. Your wallet, not the client’s: funding the purchase would take you past your overdraft (default0, i.e. past zero). Top up; do not credit the client.domain_disabled— 409 from a configuration write on a disabled domain (records, rules, cache, SSL, members, settings). The domain is read-only untilPOST /domains/{domainId}/enable.unknown_rule_type— 404 from the rules paths whenruleTypeis not one of the documented values. You get this rather than an empty array, so a typo cannot read as a rule set that happens to be empty.rate_limited— 429 from any route. Keyed per credential, so one runaway integration cannot lock its owner out of the dashboard.session_check_failed— 503, and only on calls made with a dashboard session JWT. The session could not be verified, which is not the same as knowing it is revoked, so it is retryable and the session survives. Machine tokens never see this.
Example
{ "error": "forbidden", "code": "credit_limit_reached"}Headers
Section titled “Headers ”Seconds until the current window resets.
Something failed on our side. The body carries a human sentence and never
an internal detail — a live sweep of this API once returned
dial tcp 127.0.0.1:9000: connect: connection refused, which is our
topology rather than an error message. That is now impossible.
Distinguish it from 503. A 503 means a dependency is down and the
identical request will succeed later, so retry it. A 500 means the request
hit a genuine fault: retrying it unchanged will fail the same way, and it
should be reported with the X-Request-Id from the response header.
Declared on every operation because every operation can reach it. It was previously declared on exactly one, which left a generated client with no branch for the answer it is most likely to be surprised by.
Every error body carries error, a human-readable sentence. Some also
carry code, a stable machine-readable reason — branch on that, never on
the sentence, which is prose and gets reworded.
object
Present only on the failures worth branching on, and deliberately not an exhaustive enum: treat a code you do not recognise as if it were absent and fall back to the status code.
The ones that exist today:
credit_limit_reached— 402 fromPOST /servicesandPOST /services/{serviceId}/change-plan. Your wallet, not the client’s: funding the purchase would take you past your overdraft (default0, i.e. past zero). Top up; do not credit the client.domain_disabled— 409 from a configuration write on a disabled domain (records, rules, cache, SSL, members, settings). The domain is read-only untilPOST /domains/{domainId}/enable.unknown_rule_type— 404 from the rules paths whenruleTypeis not one of the documented values. You get this rather than an empty array, so a typo cannot read as a rule set that happens to be empty.rate_limited— 429 from any route. Keyed per credential, so one runaway integration cannot lock its owner out of the dashboard.session_check_failed— 503, and only on calls made with a dashboard session JWT. The session could not be verified, which is not the same as knowing it is revoked, so it is retryable and the session survives. Machine tokens never see this.
Example
{ "error": "forbidden", "code": "credit_limit_reached"}