Terminate a service
const url = 'https://api.nsin.cloud/reseller/v1/services/1';const options = {method: 'DELETE', 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 DELETE \ --url https://api.nsin.cloud/reseller/v1/services/1 \ --header 'Authorization: Bearer <token>'Sets the subscription to cancelled, after closing the current billing
period and invoicing the traffic already charged on it. No refund is
issued for the unused remainder of the term — NSIN has no proration
anywhere.
⚠️ This is NOT the off switch.
Cancelling leaves the domain serving normally, with no plan behind it. It is a billing act, not a stop.
It does not, however, run up a pay-as-you-go bill. A reseller’s client has no PAYG at all — traffic billing skips him entirely, and the quota sweep stops a domain that has exhausted its allowance. A domain with no subscription has an allowance of zero, so it is stopped as soon as it serves a byte.
So cancelling gives you a domain that stops shortly, at the next sweep, with no bill. It does not give you a clean, immediate stop.
To stop a client immediately and unambiguously:
POST /domains/{domainId}/disable.
WHMCS TerminateAccount should disable the domain first, then call
this.
No Idempotency-Key here, deliberately. It is the one write in the
Services family that does not take one, because it cannot benefit from
one: cancelling is already idempotent — a subscription that is already
cancelled short-circuits and answers 204 again without touching
anything — so a retry after a dropped connection can neither cancel twice
nor invoice twice. Requiring a key would be ceremony that buys nothing.
Send one anyway if your client always does; it is ignored, not rejected.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “Path Parameters ”Must belong to a client of the caller, or the response is 403.
Responses
Section titled “ Responses ”Cancelled. The domain is still being served until the next quota sweep stops it.
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"}Authenticated, but the target does not belong to you — or you are not a reseller at all.
This is the response the tenant-isolation matrix asserts on. Every route in this document is called with reseller B’s credential against reseller A’s ids, and must answer 403 or 404 with none of A’s data in the body.
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"}No such resource.
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"}