Skip to content
nsin docs

Create a bot route rule

POST
/domains/{domain}/rules/bot-route/
curl --request POST \
--url https://api.nsin.ir/domains/example.com/rules/bot-route/ \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "record_id": 1, "record_ids": [ 1 ], "enabled": true, "priority": 100, "host_pattern": "example", "host_match_type": "", "host_includes": [ "example" ], "host_excludes": [ "example" ], "ip_includes": [ "example" ], "ip_excludes": [ "example" ], "action_mode": "enforce", "bot_kinds": [ "*" ], "require_verified": true, "action": "block", "status": 200, "body": "example", "alt_dest": "example", "alt_port": 1, "alt_scheme": "http" }'

Acts on classified bot traffic — block it, serve alternative content, send it to a different origin, or just tag it in telemetry.

Requires rules.edit.

If the domain’s plan does not include this rule type, the rule is still created — but switched off (enabled: false), whatever the body says.

domain
required
string

The domain name (for example example.com) — not a numeric id.

Example
example.com
Media type application/json
object
record_id

Deprecated single-record scope. Prefer record_ids.

integer | null
record_ids

Scope the rule to these proxied records. Omit or send an empty array for a zone-wide rule. Every id must belong to this domain.

Array<integer>
enabled
boolean
default: true
priority
integer
default: 100
host_pattern
string
host_match_type

How the host filter — host_pattern, host_includes and host_excludes — is matched. One strategy covers all three, exactly as one path_match_type covers both path lists.

The empty string means “no host filter”, and is the only valid value when the pattern and both lists are empty. Set a list without a match type and the API defaults it to wildcard.

A wildcard entry matches subdomains, not the label itself: *.example.com covers shop.example.com but not example.com — the same reading as the DNS wildcard. Add the bare name as its own entry to include it.

string
Allowed values: "" exact wildcard regex
host_includes

Hostnames the rule applies to; empty or omitted means every host. Sending an explicit empty array clears an existing list.

Array<string>
<= 200 items
host_excludes

Hostnames excluded from the rule; excludes beat includes.

Array<string>
<= 200 items
ip_includes

Scope the rule to these client addresses. Empty or omitted means every address; sending an explicit empty array clears an existing list.

Together with ip_excludes these two lists express all four client-IP semantics, so there is no separate match-type field:

  • IP is in a list — put the addresses in ip_includes.
  • IP is not in a list — put them in ip_excludes and leave ip_includes empty.
  • IP equals X — a one-entry ip_includes.
  • IP does not equal X — a one-entry ip_excludes.

Each entry is one of three forms:

  • a bare address, IPv4 or IPv6 — 203.0.113.7, 2001:db8::1
  • a CIDR prefix — 203.0.113.0/24, 2001:db8::/32
  • an inclusive low-high range — 203.0.113.10-203.0.113.40. Both endpoints must be the same IP version.

Entries are canonicalised and de-duplicated on save (CIDR host bits masked off, range endpoints ordered, IPv4-mapped IPv6 unmapped), so the stored value is exactly what the edge will match. An entry that is not a valid address, CIDR or range is a 400.

An empty ip_includes means every address, subject to ip_excludes; an exclude always beats an include. Matching is against the TCP peer at the edge, never X-Forwarded-For, so the scope cannot be spoofed with a request header.

Not accepted on cache and optimize rules — a non-empty list on either type is a 400. One cached copy is shared by every visitor and the cache key carries no client-IP dimension, so an IP-scoped cache rule would serve one visitor’s response to another.

On a drop rule, ip_excludes may not be used on its own: exclude-only would blackhole every visitor who is not named, so ip_includes must list at least one address.

Array<string>
<= 256 items
ip_excludes

Client addresses excluded from the rule; excludes beat includes. Same three entry forms, same canonicalisation and same cache / optimize refusal as ip_includes.

Array<string>
<= 256 items
action_mode
  • enforce — the rule acts (block, redirect, challenge, …).
  • dry_run — the rule matches and is logged as “would have acted”, but the request reaches the origin unchanged. Use it to test a rule safely.

Not every rule type honours this; cache ignores it.

string
Allowed values: enforce dry_run
bot_kinds

Which bots this rule matches. Must not be empty.

Array<string>
Allowed values: * gptbot oai-searchbot chatgpt-user claudebot claude-user perplexitybot perplexity-user googlebot google-extended bingbot ccbot bytespider meta-externalagent amazonbot applebot duckduckbot yandexbot ahrefsbot semrushbot mj12bot generic-bot
require_verified

Only match bots whose identity was verified (by reverse DNS or published IP ranges), not merely self-declared in the user agent.

boolean
action
  • block — refuse the request.
  • alt_content — serve body with status instead of the origin.
  • alt_origin — proxy to alt_dest:alt_port over alt_scheme.
  • tag — let it through, but tag it in telemetry.
string
Allowed values: block alt_content alt_origin tag
status

Status code for alt_content.

integer
default: 200
body

Response body for alt_content.

string
alt_dest

Origin address for alt_origin.

string
alt_port

Origin port for alt_origin.

integer
alt_scheme

Scheme used to reach alt_dest.

string
Allowed values: http https

Rule created.

Media type application/json
object
id
integer
domain_id
integer
record_id

Deprecated single-record scope. Prefer record_ids. Absent for zone-wide rules.

integer
record_ids

The proxied DNS records this rule applies to. Empty or absent means zone-wide — every proxied record of the domain.

Array<integer>
type
string
Allowed values: cache drop redirect rewrite waf captcha rate_limit bot_route origin_pool origin_route fingerprint error_page optimize basic_auth header
enabled
boolean
priority

Evaluation order; lower runs first. Defaults to 100.

integer
host_pattern

Legacy single-hostname filter, kept for rules written before host_includes existed. It is evaluated as one more entry of host_includes; prefer the lists.

string
host_match_type

How the host filter — host_pattern, host_includes and host_excludes — is matched. One strategy covers all three, exactly as one path_match_type covers both path lists.

The empty string means “no host filter”, and is the only valid value when the pattern and both lists are empty. Set a list without a match type and the API defaults it to wildcard.

A wildcard entry matches subdomains, not the label itself: *.example.com covers shop.example.com but not example.com — the same reading as the DNS wildcard. Add the bare name as its own entry to include it.

string
Allowed values: "" exact wildcard regex
host_includes

Hostnames the rule applies to. Empty or absent means every host the scoped record(s) serve — which on a wildcard-proxied zone (*.example.com) is every subdomain.

Array<string>
host_excludes

Hostnames carved back out of host_includes. An exclude always wins over an include, so “everything except staging” is an empty include list plus one exclude.

Array<string>
ip_includes

Client addresses the rule applies to. Empty or absent means every address — the rule is not scoped by IP at all unless ip_excludes is set.

Entries are returned canonicalised, which may differ from what was sent: CIDR host bits are masked off (10.0.0.5/8 reads back as 10.0.0.0/8), range endpoints are ordered low-to-high, IPv4-mapped IPv6 is unmapped, and duplicates are dropped. The order of the remaining entries is preserved.

The address matched is the TCP peer seen by the edge. X-Forwarded-For is never consulted, so a visitor cannot spoof a header to put themselves inside or outside a scope.

Array<string>
<= 256 items
ip_excludes

Client addresses carved back out of ip_includes. An exclude always wins over an include. Canonicalised on the way in, exactly like ip_includes.

Array<string>
<= 256 items
action_mode
  • enforce — the rule acts (block, redirect, challenge, …).
  • dry_run — the rule matches and is logged as “would have acted”, but the request reaches the origin unchanged. Use it to test a rule safely.

Not every rule type honours this; cache ignores it.

string
Allowed values: enforce dry_run
created_at
string format: date-time
updated_at
string format: date-time
bot_kinds

Which bots this rule matches. Must not be empty.

Array<string>
Allowed values: * gptbot oai-searchbot chatgpt-user claudebot claude-user perplexitybot perplexity-user googlebot google-extended bingbot ccbot bytespider meta-externalagent amazonbot applebot duckduckbot yandexbot ahrefsbot semrushbot mj12bot generic-bot
require_verified

Only match bots whose identity was verified (by reverse DNS or published IP ranges), not merely self-declared in the user agent.

boolean
action
  • block — refuse the request.
  • alt_content — serve body with status instead of the origin.
  • alt_origin — proxy to alt_dest:alt_port over alt_scheme.
  • tag — let it through, but tag it in telemetry.
string
Allowed values: block alt_content alt_origin tag
status

Status code for alt_content.

integer
default: 200
body

Response body for alt_content.

string
alt_dest

Origin address for alt_origin.

string
alt_port

Origin port for alt_origin.

integer
alt_scheme

Scheme used to reach alt_dest.

string
Allowed values: http https
Example
{
"type": "cache",
"host_match_type": "",
"action_mode": "enforce",
"bot_kinds": [
"*"
],
"action": "block",
"status": 200,
"alt_scheme": "http"
}

Malformed body, an invalid field value, or record_ids containing a record that does not belong to this domain. Cache and header rules also refuse record_ids naming a gateway record.

Media type application/json

The single error shape used by every endpoint.

object
error
required

Human-readable description of what went wrong.

string
Examples
Example foreignRecords
{
"error": "record_ids do not belong to this domain"
}

Missing, malformed, revoked or expired API key. (A valid key whose account has been deactivated gets 403 account is inactive instead.)

Media type application/json

The single error shape used by every endpoint.

object
error
required

Human-readable description of what went wrong.

string
Examples
Example invalidKey
{
"error": "invalid API key"
}

The domain has no active plan at all, so no plan feature is available on it. Buy or renew a plan for the domain. (A plan that is active but does not include the feature answers 403 instead.)

Media type application/json

The single error shape used by every endpoint.

object
error
required

Human-readable description of what went wrong.

string
Examples
Example noPlan
{
"error": "This domain does not have an active plan."
}

The key is read-only, or the domain already holds as many rules of this type as its plan allows (max_rules_per_set). A rule type the plan does not include is not refused here — the rule is saved switched off.

Media type application/json

The single error shape used by every endpoint.

object
error
required

Human-readable description of what went wrong.

string
Examples
Example atCap
{
"error": "You have reached the maximum number of rules for this domain's plan. Upgrade the plan for this domain to add more."
}

No such domain, or your role on it does not permit this operation. The rules endpoints deliberately answer 404 rather than 403 for an insufficient role, so they never confirm that a domain exists to someone who cannot use it.

Media type application/json

The single error shape used by every endpoint.

object
error
required

Human-readable description of what went wrong.

string
Examples
Example notFound
{
"error": "not found"
}

The key exceeded its request budget (300 requests per minute by default).

Media type application/json

The single error shape used by every endpoint.

object
error
required

Human-readable description of what went wrong.

string
Examples
Example limited
{
"error": "rate limit exceeded"
}