GroupsBeta

Apply one set of firewall rules across many addresses, and keep them in step.

A group is a named set of firewall rules — the same objects Firewall writes one address at a time — that you apply to many addresses at once and then edit in one place. Change a rule in the group and it reaches every address the group is applied to.

The alternative is replaying every rule onto every address yourself and reconciling the result by hand. That is the drift groups exist to prevent: after a few weeks of incident edits, twenty addresses that were meant to be identical no longer are, and nothing tells you which.

Requires the Firewall API flag on your organisation — see Activation — plus firewall-groups.read to read and firewall-groups.manage to write.

firewall.manage does not grant firewall-groups.manage. A per-address write reaches the one address it names; editing a group re-applies it across every address the group holds, from a single call. Same addresses, far more leverage — so the abilities are separate and neither implies the other.

Applying is asynchronous

This is the one thing to design around. Every write that changes what a group applies answers with an operation id, not a finished state:

{ "data": { "operation_id": 1043, "revision": 4 }, "message": "Accepted. ..." }

The work is one reconcile per address, queued. Poll until it finishes:

curl https://one.terabit.io/api/v1/firewall/groups/operations/1043 \
  -H "Authorization: Bearer tbk_..."
{
  "data": {
    "id": 1043, "type": "assign", "status": "completed", "finished": true,
    "total": 20, "processed": 20, "succeeded": 19, "failed": 1,
    "results": [ ... ], "error": null
  }
}

A finished operation with a non-zero failed is not a success. Check failed, not just finished. results names the addresses that did not take, and each can be retried on its own.

Once an operation finishes, the changes still take 3–5 minutes to propagate globally, exactly as they do on the per-address endpoints.

Building a group

Create it, then add rules. A new group holds nothing and reaches nothing, so neither step changes any traffic.

curl -X POST https://one.terabit.io/api/v1/firewall/groups \
  -H "Authorization: Bearer tbk_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Minecraft edge","description":"Standard posture for MC servers"}'
curl -X POST https://one.terabit.io/api/v1/firewall/groups/42/rules \
  -H "Authorization: Bearer tbk_..." \
  -H "Content-Type: application/json" \
  -d '{"kind":"ip","action":"allow","protocol":"udp","destination_port":"25565"}'

kind is ip or filter. A filter names its slug in filter_type and must name a port — the mitigation network binds a filter to one port, so "all ports" is not a choice a filter has:

curl -X POST https://one.terabit.io/api/v1/firewall/groups/42/rules \
  -H "Authorization: Bearer tbk_..." \
  -H "Content-Type: application/json" \
  -d '{"kind":"filter","filter_type":"minecraft","destination_port":"25565"}'

Read the slugs from /firewall/groups/filters/available rather than guessing. Note this is a wider catalogue than the per-address one: a group is not bound to an address until you assign it, so a filter listed here may still be unavailable on a particular address. Pre-flight tells you, per address.

Geo rules are readable, not writable

A group can hold geo rules and they read back in full — but they cannot be created or edited over the API, matching the per-address endpoints. Author those in Protection → Firewall Groups in the portal. Everything else about a group holding one works normally over the API.

Footprint: what a group costs per address

footprint is what the group becomes on each address, per kind — the expanded count, not the rule count:

{ "rule_count": 3, "footprint": { "ip": 12, "geo": 0, "filter": 1 } }

One rule covering 7000-7010 is eleven objects on every address it lands on. That is what the per-address ceilings in meta.limits are measured against, so read those rather than hardcoding them — staff can raise them per organisation.

Pre-flight before you assign

Pre-flight writes nothing. It reports, per address, what would be created, what is already there and would be adopted rather than duplicated, and whether the result still fits:

curl -X POST https://one.terabit.io/api/v1/firewall/groups/42/preflight \
  -H "Authorization: Bearer tbk_..." \
  -H "Content-Type: application/json" \
  -d '{"ips":["203.0.113.10","203.0.113.11"]}'

Adoption matters: a rule already on an address that the group also wants is taken over, not duplicated, so applying a group to an address you had configured by hand does not double its rules.

Called with no body, pre-flight reports on the addresses the group already reaches — which is how you find one that has drifted past its limits.

Assigning

Addresses only. You know the address you want to protect; you should not have to learn our prefix and service ids to name it.

curl -X POST https://one.terabit.io/api/v1/firewall/groups/42/assignments \
  -H "Authorization: Bearer tbk_..." \
  -H "Content-Type: application/json" \
  -d '{"ips":["203.0.113.10","203.0.113.11"]}'

Two things can go wrong, and they have separate switches because they are separate problems:

ProblemDefaultSwitch
An address is not yours, or you may not manage itthe whole request failspartial: true applies the rest and reports the remainder in skipped
The group would not fit on an addressnothing is queued, and the response carries the pre-flight reportforce: true applies it as far as it goes and leaves that address out of sync

The default on the second is deliberate. A group half-applied across an estate is worse than one not applied at all — it is the drift you adopted a group to avoid.

Re-assigning an address the group already holds is not an error; it re-queues it.

Knowing whether an edit has landed

Every group carries a revision, incremented on each rule change. Every assignment carries the applied_revision last written to that address:

{
  "revision": 7,
  "assignments": [
    { "ip": "203.0.113.10", "status": "synced",  "applied_revision": 7 },
    { "ip": "203.0.113.11", "status": "failed",  "applied_revision": 5,
      "last_error": "The mitigation network refused rule 4." }
  ]
}

Equal means in sync. Behind means the reconcile has not reached that address, or did not succeed there.

Read last_error before retrying. A rejection names the rule it refused, and a blind retry queues the same failure again:

curl -X POST \
  https://one.terabit.io/api/v1/firewall/groups/42/assignments/203.0.113.11/retry \
  -H "Authorization: Bearer tbk_..."

Removing a group, and what happens to the rules

Unassigning and deleting both ask the same question: do the rules go with it?

  • Default — the group's rules are removed from each address.
  • preserve_rules: true — the rules stay in place, unmanaged, and only the group's claim on them is dropped.
curl -X DELETE https://one.terabit.io/api/v1/firewall/groups/42/assignments \
  -H "Authorization: Bearer tbk_..." \
  -H "Content-Type: application/json" \
  -d '{"ips":["203.0.113.11"],"preserve_rules":true}'

Preserving is the safe move mid-incident: you stop managing an address centrally without dropping its protection in the same call.

Deleting a group that reaches addresses returns an operation and the group stays visible until that finishes — it still holds the rule definitions that say what to remove. A group reaching no addresses is deleted immediately, with operation_id: null.

Rate limits

The group endpoints share the /firewall/** bucket with the per-address ones: 30 requests/minute, keyed by bearer token. Polling an operation is cheap but not free — a one-second poll spends half that budget on its own. Once a second while an operation is running, then stopping, is a reasonable shape.

A note on ids

Groups and rules are addressed by id; everything else is addressed by IP. A group belonging to another organisation answers 404, never 403 — telling those apart would confirm the id is real.

On this page