Firewall
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
Firewall writes are accepted synchronously but take 3-5 minutes to propagate
globally. A read immediately after a write will not reflect it, and a script
that adds a rule and then asserts on traffic needs to wait. A 201 means the
change was accepted, not that it is live everywhere.
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."
}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.