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 source
  • destination_port: null — any port. It is reported as null and never 0, 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

LimitDefault
Rules per IP100
Filters per IP25
Ports per request101

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.

On this page