Skip to content

The plan catalog, with list prices

GET
/reseller/v1/plans
curl --request GET \
--url https://api.nsin.cloud/reseller/v1/plans \
--header 'Authorization: Bearer <token>'

The reseller sees list prices here — he needs them to know what he is being charged, and to set his own retail price on his own website.

His clients never see a price: the panel’s plan endpoints strip every price field for a user with a reseller_id, and show plan name and features only.

Active plans

Media type application/json
Array<object>
object
id
required
integer
name
required
string
title_fa
string
slug
required
string
description
string
max_domains

Null = unlimited

integer
nullable
max_records
integer
nullable
max_traffic_gb

Included traffic per quota period.

For a direct NSIN customer, usage beyond this is billed pay-as-you-go and may drive the balance negative.

For a reseller’s client it is a hard cap, not a meter. He has no pay-as-you-go: traffic billing skips him entirely, and when he reaches this number the quota sweep suspends his domain. He is never charged for overage, and his wallet is never driven negative by traffic. Buy him a bigger plan to lift the cap.

The cap is enforced at the next sweep, not at the byte, so a client can overshoot by whatever he serves within one ticker interval.

integer
nullable
max_bandwidth_gb
integer
nullable
max_requests_per_month
integer
nullable
logs_enabled
boolean
monitoring_enabled
boolean
rules_enabled
boolean
cache_purge_enabled
boolean
custom_ssl_enabled
boolean
ws_enabled
boolean
host_header_edit_enabled
boolean
dedicated_support_enabled
boolean
max_rules_per_set
integer
nullable
max_cache_cap_mb

The largest per-domain cache cap this plan permits. Never null — the floor is 128.

integer
is_recommended
boolean
auto_renew_allowed

When false, wallet auto-renew cannot be enabled and the renew job skips the subscription.

boolean
sort_order

Catalog display order.

integer
platform_features_total

How many platform capability flags exist, so platform_features_enabled reads as “N of M”.

integer
platform_features_enabled

How many of them this plan turns on.

integer
terms
required

Purchase targets. POST /services takes a term id, not a plan id.

Always present, and always an array. A plan with no active term comes back as [] rather than as a missing key or null — it is a plan you cannot currently sell, not a plan without the field. Ordered shortest duration first, which is also the baseline discount_percent is measured against.

Array<object>
object
id
required
integer
duration_days
required
integer
price_rials
required

NSIN list price — what the client’s wallet is debited. The reseller sees it; the client never does. NSIN does not know, store, or ask for the reseller’s own retail price.

integer format: int64
is_default
boolean
grace_days

Days after expiry before the subscription is treated as lapsed.

integer
sort_order
integer
discount_percent

Saving against the shortest active term’s daily rate, as a whole percent. 0 on that shortest term itself, and on any term that is not actually cheaper per day. Computed, not stored — it is what a reseller prices a longer commitment against.

integer
Example
[
{
"terms": [
{
"duration_days": 30
}
]
}
]

Missing, malformed, expired, or revoked credential.

Media type application/json

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
error
required
string
code

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 from POST /services and POST /services/{serviceId}/change-plan. Your wallet, not the client’s: funding the purchase would take you past your overdraft (default 0, 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 until POST /domains/{domainId}/enable.
  • unknown_rule_type — 404 from the rules paths when ruleType is 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.
string
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.

Media type application/json

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
error
required
string
code

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 from POST /services and POST /services/{serviceId}/change-plan. Your wallet, not the client’s: funding the purchase would take you past your overdraft (default 0, 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 until POST /domains/{domainId}/enable.
  • unknown_rule_type — 404 from the rules paths when ruleType is 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.
string
Example
{
"error": "forbidden",
"code": "credit_limit_reached"
}

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.

Media type application/json

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
error
required
string
code

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 from POST /services and POST /services/{serviceId}/change-plan. Your wallet, not the client’s: funding the purchase would take you past your overdraft (default 0, 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 until POST /domains/{domainId}/enable.
  • unknown_rule_type — 404 from the rules paths when ruleType is 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.
string
Example
{
"error": "forbidden",
"code": "credit_limit_reached"
}