Skip to content

Preview the Markdown-for-Agents conversion for one domain

GET
/reseller/v1/domains/{domainId}/analytics/markdown-tester
curl --request GET \
--url 'https://api.nsin.cloud/reseller/v1/domains/1/analytics/markdown-tester?path=%2F' \
--header 'Authorization: Bearer <token>'

Fetches one page on the domain twice — once as a browser (Accept: text/html) and once as an agent (Accept: text/markdown) — through the public edge, and returns both responses so the panel can show a before/after diff and the token saving.

This is the reseller’s answer to “will turning this on break my customer’s site”. Without it the only way to find out is to enable markdown_for_agents on a live domain and look, which is a change to someone else’s production site made in order to ask a question.

There is no plan gate, matching the customer surface: previewing the conversion must be possible before the feature is bought.

The fetch target is pinned to the authorized domain — hostname must be the domain itself or one of its subdomains, path must be a path and not a URL, redirects may not leave the domain, and bodies are capped at 256 KiB. That containment is what keeps a preview endpoint from being an SSRF hole pointed at our own network.

domainId
required
integer

Must belong to a client of the caller, or the response is 403.

hostname
string

Which host to fetch. Defaults to the domain apex. Must be the domain or one of its subdomains; anything else is a 400 rather than a fetch.

path
string
default: /

Path to fetch, starting with /. A full URL is rejected.

Both fetches, side by side

Media type application/json
object
url
required

The URL that was fetched

string
feature_enabled
required

The domain’s markdown_for_agents setting at test time. With it off, markdown.converted will be false — that is the feature not being on, not the page failing to convert, and the two must not be reported as the same thing.

boolean
html
required
object
status
required

The origin/edge status. 0 when the fetch itself failed — see error.

integer
content_type
required
string
content_length
required

Body length as returned, after the 256 KiB cap is applied.

integer
body
required
string
truncated
required

The body was longer than the cap and has been cut.

boolean
binary

The body was not valid UTF-8, so body is omitted rather than mangled.

boolean
cache_status

The edge’s Nsn-Cache-Status

string
markdown_tokens

From X-Markdown-Tokens. Absent when the edge did not count.

integer
original_tokens

From X-Original-Tokens. Absent when the edge did not count.

integer
converted
required

The response came back as text/markdown.

boolean
error

Why the fetch failed, when it did. Present with a zero status means nothing was reached at all — do not render that as a 0 response code.

string
markdown
required
object
status
required

The origin/edge status. 0 when the fetch itself failed — see error.

integer
content_type
required
string
content_length
required

Body length as returned, after the 256 KiB cap is applied.

integer
body
required
string
truncated
required

The body was longer than the cap and has been cut.

boolean
binary

The body was not valid UTF-8, so body is omitted rather than mangled.

boolean
cache_status

The edge’s Nsn-Cache-Status

string
markdown_tokens

From X-Markdown-Tokens. Absent when the edge did not count.

integer
original_tokens

From X-Original-Tokens. Absent when the edge did not count.

integer
converted
required

The response came back as text/markdown.

boolean
error

Why the fetch failed, when it did. Present with a zero status means nothing was reached at all — do not render that as a 0 response code.

string
Example generated
{
"url": "example",
"feature_enabled": true,
"html": {
"status": 1,
"content_type": "example",
"content_length": 1,
"body": "example",
"truncated": true,
"binary": true,
"cache_status": "example",
"markdown_tokens": 1,
"original_tokens": 1,
"converted": true,
"error": "example"
},
"markdown": {
"status": 1,
"content_type": "example",
"content_length": 1,
"body": "example",
"truncated": true,
"binary": true,
"cache_status": "example",
"markdown_tokens": 1,
"original_tokens": 1,
"converted": true,
"error": "example"
}
}

Malformed or invalid input.

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"
}

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"
}

No such resource.

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"
}

The analytics store (ClickHouse) is unreachable.

Deliberately an error rather than a zeroed page: a page of zeros is indistinguishable from “your customers’ traffic stopped”, and someone would act on it.

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"
}