Batches & ephemeral rules

Many rules per call, queued jobs, and rules that expire unless bumped, at /api/v2/firewall.

/api/v2/firewall takes the same organisation key as v1 and adds what v1 does not do: many rules in one call with a result per item, work you can queue and poll, and rules that remove themselves after a set time unless you keep them alive. For one rule on one address, v1 is still the simpler call.

https://one.terabit.io/api/v2/firewall
Authorization: Bearer tbk_...

Needs the Firewall API flag on your organisation (Activation), firewall.read for every GET and firewall.manage for everything else.

What changes from v1

  • The rule shape. An IP rule has a destination, an optional source, a protocol and an action of deny or allow, as in v1. Filters go by the names listed below, not v1's slugs.
  • One envelope. Every response is {"data": ...} or {"error": {"code", "message", "details"}}.
  • Addresses are checked first. A request naming an address that is not yours is refused whole with 403, and details.indexes says which items. A rule that already exists is reported as exists, not as a failure.
  • Its own rate limit of 500 requests a minute per source address, in RateLimit-* headers.
  • Rules made here show under Protection → Firewall like any other, but carry no note and do not count towards the per-address limits v1 enforces.
  • A short delay on key and address changes. A revoked key stops working here within thirty seconds; a newly assigned address becomes usable within five minutes. v1 sees both at once.

The rule

One shape; kind says which kind.

{
  "kind": "ip",
  "destination": { "ip": "203.0.113.10", "port": 27015 },
  "source": { "ip": "0.0.0.0", "port": 0 },
  "protocol": "udp",
  "action": "deny"
}

destination.port 0 means every port. source defaults to any address and any port; protocol to any; action to deny.

{
  "kind": "filter",
  "filter": "mcje",
  "destination": { "ip": "203.0.113.10", "port": 25565 }
}

A filter rule attaches a stateful filter to a port. synproxy is a filter here too, with an optional filter_id.

Every rule you read back carries a ref, which is how you address it afterwards: ip:<id>, or filter:<name>:<ip>:<port> for a filter rule, which has no id of its own.

Filter names

synproxy, dns, raknet, fivem, fivem-strict, openvpn, wireguard, scpsl, generic-sip-udp, litenetlib, source-engine, arma3reforger, palworld, conan-exiles, mcje, mcbe, unrealengine5, teamspeak3, udpadaptive, monoamp, dayz, rdp, cs2, path-of-titans, rust, vrising, a2s, zomboid, squad, ottwwi, arkse, tcp2, teraclassic, bercon.

Older names work as aliases and read back under the canonical name: a2s_udp, adaptive_udp, cs2_udp, fivem_enet, fivem_strict, openvpn_udp, raknet_udp, sip_udp, ue5. tcp2 covers the whole address and ignores destination.port.

This list is not narrowed per address the way v1's filters/available is. On an address assigned to a service, let what v1 offers there guide you.

One rule

curl -X POST https://one.terabit.io/api/v2/firewall/rules \
  -H "Authorization: Bearer tbk_..." \
  -H "Content-Type: application/json" \
  -d '{"kind":"ip","destination":{"ip":"203.0.113.10","port":27015},"protocol":"udp"}'

201 with the rule and its ref, or 200 with the existing rule when an identical one was already there. Read it back with GET /rules/ip:42, delete with DELETE /rules/ip:42.

Listing

GET /rules lists what you hold, narrowed with ip=, kind=ip|filter or filter=<name>. It is paged: limit (default 100, at most 1,000) and offset, with meta beside data.

{
  "data": [ { "kind": "ip", "ref": "ip:42", "...": "..." } ],
  "meta": { "total": 1362, "limit": 100, "offset": 0, "next_offset": 100 }
}

next_offset is absent on the last page. The order is stable — IP rules by id, then filters by name, address and port — so walking the pages sees each rule once. GET /leases pages the same way.

Batches

Send rules instead of one rule. Items are applied in order and each reports its own outcome; the call answers 201 if anything was created, 200 otherwise.

Two limits. A batch of more than 4 items must be sent with "mode": "async" (below), or it is refused with 400 async_required; up to 4 may be applied synchronously. A request carries at most 1,000 items, which is also the most one job holds — split larger sets across requests.

{
  "atomic": false,
  "rules": [
    { "kind": "ip", "destination": { "ip": "203.0.113.10", "port": 27015 }, "protocol": "udp" },
    { "kind": "filter", "filter": "mcje", "destination": { "ip": "203.0.113.10", "port": 25565 } }
  ]
}
{
  "data": {
    "results": [
      { "index": 0, "op": "create", "status": "created", "ref": "ip:42", "rule": { "...": "..." } },
      { "index": 1, "op": "create", "status": "exists", "ref": "filter:mcje:203.0.113.10:25565", "rule": { "...": "..." } }
    ],
    "summary": { "created": 1, "exists": 1, "deleted": 0, "absent": 0, "error": 0, "rolled_back": 0 }
  }
}

"atomic": true makes the batch all-or-nothing: one failure rolls every item back, reported as rolled_back. Without it a failing item is error and the rest still land.

Batch deletes go to POST /rules/delete with refs, or match (rule shapes, deleting whatever matches each), or both, under the same two limits. A ref you cannot see is reported absent.

Jobs

"mode": "async" on a batch, or ?mode=async on a single create or delete, answers 202 at once. It is required for any batch of more than 4 items, and worth using for anything you would not want to wait on:

{ "data": { "job": { "id": "3f9c...", "status": "queued" } } }

Poll GET /jobs/{id}: queued, active, then completed or failed, with the same results and summary a synchronous batch answers with. DELETE /jobs/{id} cancels a job that has not started (409 not_cancellable once it has). Jobs are kept for seven days; GET /jobs?status= lists yours.

Ephemeral rules

A ttl in seconds, on a batch or as ?ttl= on a single create, makes the rules it creates temporary. Each read carries an expires_at; when it passes, the rule is removed within about fifteen seconds.

# block a source for ten minutes
curl -X POST "https://one.terabit.io/api/v2/firewall/rules?ttl=600" \
  -H "Authorization: Bearer tbk_..." \
  -H "Content-Type: application/json" \
  -d '{"kind":"ip","destination":{"ip":"203.0.113.10","port":0},"source":{"ip":"198.51.100.7"}}'

POST /rules/{ref}/bump resets the clock to now plus the rule's ttl; a body of {"ttl": 1800} sets a new interval that later bumps reuse. DELETE /rules/{ref}/lease makes the rule permanent. GET /leases lists what is due to expire, soonest first.

Re-creating a temporary rule without a ttl does not make it permanent — it is reported as exists and its clock is untouched. Release the lease if you mean it to stay.

Errors

CodeStatusMeaning
validation_failed400The body or query did not match the schema; details.issues lists the paths
bad_request400An invalid address or an unknown filter
async_required400A batch of more than 4 items without mode: async; details carries both limits
unauthorized401No key, or a key that is not valid
ability_required403The key lacks firewall.read or firewall.manage for this method
forbidden403An address is not yours; details.indexes names the items
not_found404No such rule, job or lease, including one that is not yours
not_cancellable409The job has already started
rate_limited429Over 500 requests in the minute; Retry-After says when
auth_unavailable503Your key could not be verified right now; retry shortly

Every endpoint, with its full schema and a playground, is under API Reference → Batches & ephemeral rules.

On this page