Exclusive Access · Invitation Only

Billing Integration

Endpoints in the agent so your billing system can suspend, resume, or rate-limit one of your customers — a tenant — when their invoice changes. It is part of the agent's normal API (REST plus the cenvero-str-ctl CLI); you don't build anything beyond the HTTP calls.

The intended shape is: your billing system already knows who has paid. When that answer changes, it makes one call per affected tenant. Nothing polls, and the agent never talks to your billing system.

How enforcement actually works

Worth understanding before you wire it up, because it determines what "suspended" means for a customer and what you can promise them.

Suspending cuts the tenant's traffic off, all of it, with one setting. When you suspend a tenant, the node stops forwarding and delivering the traffic of that tenant's workloads:

  • everything its virtual machines and containers send, and everything addressed to them — to and from the internet, other tenants and the node itself;
  • traffic between the tenant's own machines on the node, which never leaves the node;
  • traffic to and from the tenant's public addresses.

It is one setting for the tenant, not one per address, so it does not matter how many machines, networks or addresses the tenant has, and it uses none of your firewall rules. Resuming lifts it; your own firewall rules are never touched.

A workload you plug into one of the tenant's networks yourself (rather than a virtual machine or container Stratum runs) loses its routed traffic — to the internet, to other networks and to the node — but not what it exchanges directly with neighbours on the same network.

Suspending also stops the tenant's virtual machines. Each running machine is asked to shut down and is powered off if it has not within two minutes; none of them can be started while the suspension lasts. Resuming starts again exactly the machines that were running when you suspended — the ones that were stopped stay stopped — also after agent restarts and reboots in between. Nothing is deleted: disks, addresses and settings stay as they were. Details in Lifecycle and Restarts.

Two properties follow, and both matter in practice:

  • It is idempotent. Suspending an already-suspended tenant is harmless. Your billing system can retry a failed call, or re-send the current state on a schedule, without special-casing anything.
  • Existing connections are cut, not drained. A transfer in progress stops. There is no grace period. If you want one, delay the call in your billing system.

Upgrading from an earlier version. Earlier versions suspended a tenant with a firewall rule for each of its addresses in use, which stopped working once a tenant had more addresses than the firewall holds rules. When a node starts on this version it puts every suspended tenant's suspension in place first and then removes those old rules; a suspended tenant stays cut off throughout.

Rate-limiting is the bandwidth cap, the same mechanism described in Tenants & Bandwidth. It is a credit allowance that refills at the rate you set: TCP finds the new rate and settles there, UDP over the rate is dropped. Applying a limit to a running tenant takes effect immediately without disturbing established connections.

State is durable, and applies on the node you call — or on the whole cluster. The tenant's status and cap are persisted, so they survive an agent restart and a reboot: the node puts a suspension back in place when it starts, and checks every 30 seconds that it still holds. On a standalone node, the suspension and the cap apply to the node you send the call to, so when a tenant has workloads on several independent nodes, send it to each of them.

In a cluster, one call reaches every member. A tenant's status and bandwidth cap are shared settings of the cluster: suspend, resume, limit or unlimit the tenant on any member, and every member applies it: a suspension stops the tenant's machines wherever they run, and resuming restarts those that were running. A member that cannot reach the rest of the cluster — the smaller side of a network split, or a node cut off from the others — refuses the change with 503 and changes nothing (the cluster has no leader reachable from this node; changes to shared settings are paused until it has one), because the change has to reach the cluster as a whole. Treat that 503 as "try another member": send the call to a member that can reach the others, or restore the network between them and retry. Reading the tenant's state keeps working on every member. Use an operator key or token of the member you call; tenant keys stay with the node that issued them, and deleting a tenant revokes its keys on every member.

Suspension holds for everything created later. Suspending a tenant that owns nothing yet is fine: the suspension belongs to the tenant, so any machine, network or address the tenant gets afterwards is cut off from the start. The order you do things in does not matter — you can suspend an empty tenant, provision it later, and its traffic still does not flow. You do not need to re-send the call.

Get an API key

The operator mints a key on the node; your billing system sends it as a bearer token.

cenvero-str-ctl apikeys mint "billing"     # → csk_xxxxxxxxxxxxxxxx  (shown once)
cenvero-str-ctl apikeys list
cenvero-str-ctl apikeys revoke <id>
Authorization: Bearer csk_xxxxxxxxxxxxxxxx

The key is shown once, when it is minted — store it then. Mint a separate key for your billing system rather than reusing an operator key, so revoking it cannot lock you out of anything else.

Use an operator key here, not a tenant's own key. A tenant key can read its tenant's state (GET /api/v1/billing/tenants/{id}) but is refused (403) on suspend, resume, limit and unlimit, so a customer holding one cannot lift its own suspension or bandwidth cap.

Endpoints

A tenant is addressed by its id. All return JSON.

Method · PathWhat happens
POST /api/v1/billing/tenants/{id}/suspendSuspends the tenant: its workloads' traffic stops, in both directions.
POST /api/v1/billing/tenants/{id}/resumeLifts the suspension.
POST /api/v1/billing/tenants/{id}/limitCaps the tenant's bandwidth. Body: {"rate_mbps": 50}.
POST /api/v1/billing/tenants/{id}/unlimitRemoves the cap (unlimited).
GET /api/v1/billing/tenants/{id}Current state: {id, name, status, max_bandwidth_bps}.
curl -X POST https://node:7070/api/v1/billing/tenants/t-abc123/suspend \
     -H "Authorization: Bearer csk_xxxxxxxxxxxxxxxx"

Unknown tenant → 404.

A worked flow

What a billing system typically does, in order:

# Invoice went unpaid — cut the customer off
POST /api/v1/billing/tenants/t-abc123/suspend

# Confirm what the node now believes
GET  /api/v1/billing/tenants/t-abc123
# → {"id":"t-abc123","name":"acme","status":"suspended","max_bandwidth_bps":0}

# Payment arrived — restore them
POST /api/v1/billing/tenants/t-abc123/resume

# Downgraded to a slower plan instead of being cut off
POST /api/v1/billing/tenants/t-abc123/limit    {"rate_mbps": 50}

Read back after writing. GET is the node's own view, and it is the answer to "did that actually apply?" — more reliable than inferring it from a 200.

From the CLI

The same actions, for an operator or a script on the node:

cenvero-str-ctl billing suspend  <tenant-id>
cenvero-str-ctl billing resume   <tenant-id>
cenvero-str-ctl billing limit    <tenant-id> --rate-mbps 50
cenvero-str-ctl billing unlimit  <tenant-id>
cenvero-str-ctl billing status   <tenant-id>

What this is not

  • Not invoicing. The agent has no concept of an invoice, a price, or a due date. It enforces what you tell it, and your billing system remains the source of truth.
  • Not the customer panel's billing. Orders, invoices and payment in the management panel are a separate thing entirely — see Your account. These endpoints are for your system controlling your customers on your nodes.
  • Not a usage meter. For how much a tenant actually transferred, use the accounting and metrics surfaces — see Monitoring & Observability.

See also

↓ This page as JSON ↓ All documentation as JSON