Rules & filters
Read and manage the rules and filters on your own addresses.
The same objects the Protection → Firewall page edits, driven from a script. Typical uses: open a port as part of a deploy, drop a source during an incident, attach a stateful filter to a game port before a launch, or reconcile a rule set against a config file you keep in version control.
Requires the Firewall API flag on your organisation — see
Activation — plus firewall.read to read and
firewall.manage to write.
Addressed by IP
Every endpoint is addressed by IP address, not by a prefix or a service id:
/api/v1/firewall/ips/{ip}An address assigned to one of your services works here as well as one inside a prefix you announce — see Ownership.
Discover filter slugs, do not guess them
The filter catalogue is per address: an address assigned to a service excludes the default-protection filters, which we manage rather than you. The same slug can be available on one of your addresses and not on another.
curl https://one.terabit.io/api/v1/firewall/ips/203.0.113.10/filters/available \
-H "Authorization: Bearer tbk_..."{
"data": [
{ "type": "synproxy", "name": "SYN Proxy", "category": "protocols" },
{ "type": "minecraft", "name": "Minecraft", "category": "games" }
]
}A type that does not exist and one that is not available on this address return
the same 422, so read this endpoint rather than inferring a catalogue from
failures.
Opening a port
curl -X POST https://one.terabit.io/api/v1/firewall/ips/203.0.113.10/rules \
-H "Authorization: Bearer tbk_..." \
-H "Content-Type: application/json" \
-d '{"action":"allow","protocol":"udp","destination_port":"7000-7002","source_ip":"198.51.100.0/24"}'Port expressions take a single port, a range, or a comma-separated list of either:
"80", "7000-7010", "80,443". One rule is created per expanded port.
Reading state back
{
"data": {
"ip": "203.0.113.10",
"rules": [
{
"id": 11, "action": "allow", "protocol": "tcp",
"source_ip": "198.51.100.5", "destination_ip": "203.0.113.10",
"destination_port": 443, "managed_by_group": null
}
],
"filters": [
{ "key": "synproxy:25565", "type": "synproxy", "name": "SYN Proxy", "port": 25565, "managed_by_group": null }
]
},
"meta": { "rules_count": 1, "filters_count": 1, "limits": { "rules_per_ip": 100, "filters_per_ip": 25, "max_ports_per_request": 101 } }
}Two nulls that mean "any" rather than "unset":
source_ip: null— any sourcedestination_port: null— any port. It is reported asnulland never0, so it cannot be mistaken for a literal port zero
Allow rules sort ahead of deny rules, matching the order the web page shows.
On a filter, key is the value the delete endpoint takes. Use it rather than
assembling type:port by hand — see
Conventions.
Writes are not instant, and not atomic
A 201 means the change was accepted, not that it is live everywhere. Propagation
is usually 5-10 seconds, and can take up to 5 minutes — a read straight
after a write may not reflect it yet, and a script that adds a rule and then
asserts on traffic has to allow for the slow case.
Poll the read endpoint until the change appears rather than sleeping for a fixed interval. Sleeping five minutes wastes almost all of them; sleeping ten seconds is right most of the time and wrong exactly when it matters.
Because one rule is created per expanded port, "7000-7010" is eleven rules and
they are applied one at a time. The response reports both numbers:
{
"data": { "ids": [101, 102, 103], "created": 3, "requested": 3 },
"message": "Accepted. Changes take 3-5 minutes to propagate globally."
}That message is the API's own wording and states the worst case rather than the
usual one. It is not a per-request estimate — the same string comes back whether
the change lands in eight seconds or four minutes.
If created is lower than requested, some ports did not land. ids names the
ones that did, so you can reconcile rather than assume. A 502 means none did.
Deleting
Rules go by id, filters go by key, at most 100 per request:
curl -X DELETE https://one.terabit.io/api/v1/firewall/ips/203.0.113.10/rules \
-H "Authorization: Bearer tbk_..." \
-H "Content-Type: application/json" \
-d '{"ids":[101,102]}'An id or key that is not on this address is skipped, not reported — deleted
is simply lower than what you asked for:
{ "data": { "deleted": 2, "ids": [101, 102] }, "message": "..." }Check deleted rather than treating a 200 as "everything named was removed".
Limits
| Limit | Default |
|---|---|
| Rules per IP | 100 |
| Filters per IP | 25 |
| Ports per request | 101 |
The live values travel in meta.limits on every read, so check them rather than
hardcoding these numbers. Exceeding one is a 422, not a truncation. A rule
identical to one already on the address is also a 422 rather than a duplicate.
Objects a firewall group owns
A rule or filter managed by a firewall group cannot be changed through this API. The group is the source of truth, and the next reconcile would put back whatever you removed.
Those objects carry a managed_by_group object on read, so you can tell before
you try:
{ "id": 12, "action": "deny", "managed_by_group": { "id": 3, "name": "Minecraft" } }Attempting to delete one is a 422 naming the group. Edit the group in the portal
instead — group management is not exposed through this API.
Stateful filters disconnect clients
Applying a stateful filter forces everything currently connected through that port to reconnect through it. On a live game server that means every player drops. Prefer a maintenance window.
The same warning travels back in the response body, because a scripted caller is more likely to do this mid-session than someone clicking through the portal.
What is not here
- Geo rules — blocking by source country or ASN. Use the portal, or our MCP server.
- Firewall groups — cannot be created, edited or assigned here. Objects a group owns are read-only through this API.
- Rule notes and tags — private to your account in the portal, not returned.