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

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 reporteddeleted 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