EnroutiaEU

Tags and cost per automation

A call can say which automation it came from. Say it, and the panel adds the bill up per workflow, exports it as CSV, and tells you when one that used to run every day goes quiet.

What a tag is

Up to eight key–value pairs sent with the request. Keys are lowercase (letters, digits, underscore, hyphen, up to 32 characters); values are up to 64 characters. They are metadata, not content: they are stored with the usage event — the same row that keeps the token counts — and nothing else is. Five keys mean something to the panel: workflow, workflow_name, node, client and task. Anything else is kept and shown, but not interpreted.

key    ^[a-z0-9][a-z0-9_-]{0,31}$     at most 8 keys
value  up to 64 characters
known  workflow, workflow_name, node, client, task

In the request body

Put them under metadata.tags. The rest of metadata is yours: we read the tags and touch nothing else.

curl https://api.enroutia.com/v1/chat/completions \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "auto",
    "messages": [{"role": "user", "content": "Classify this lead: …"}],
    "metadata": {
      "tags": {
        "workflow": "wf_8Xk2",
        "workflow_name": "Lead scoring",
        "node": "Classify",
        "client": "acme",
        "task": "lead_classification"
      }
    }
  }'

Or as a header

When the body is not yours to change — a tool that builds it for you — send the same pairs in X-Enroutia-Tags, comma-separated. If both arrive, the body wins.

X-Enroutia-Tags: workflow=wf_8Xk2,node=Classify

From n8n

The Enroutia community node tags every call for you: the workflow's id and name and the node's name, with nothing to configure. From a plain HTTP Request node, add the tags to the JSON body:

HTTP Request node
  Method   POST
  URL      https://api.enroutia.com/v1/chat/completions
  Auth     Header Auth · Authorization: Bearer YOUR_KEY
  Body     JSON
{
  "model": "auto",
  "messages": [{"role": "user", "content": "{{ $json.text }}"}],
  "metadata": {
    "tags": {
      "workflow": "{{ $workflow.id }}",
      "workflow_name": "{{ $workflow.name }}",
      "node": "Classify"
    }
  }
}

In the panel

Usage gains a third grouping, by workflow, and under the chart a table with one row per tagged automation: calls, cost, last call and the models it used. A workflow with no call for seven days wears an Idle badge.

The workflow_idle webhook

An automation that made at least ten calls in its last thirty active days and then made none for seven is probably broken, not finished. Subscribe to workflow_idle on an account webhook and you hear about it once per workflow per week, signed like every other event:

{
  "event": "workflow_idle",
  "data": {
    "workflow": "wf_8Xk2",
    "workflow_name": "Lead scoring",
    "last_seen_at": "2026-09-14T06:10:00Z",
    "idle_days": 7,
    "calls_30d": 412,
    "cents_30d": 138
  }
}

Budget per end client

Give a client a tag under Clients — lowercase, up to 32 characters, unique among your clients — and every call whose client tag is that tag belongs to it, whichever key sent it. A call counts once: by the key assigned to the client, or by the tag, never both. The client's page shows the two halves, the statement lists the tagged part on its own line, and the client's monthly cap now bounds both.

When the cap is spent, the gateway refuses the tagged calls before they reach a provider: 402 with error.code client_tag_budget_exceeded and error.client naming the tag. The refusal is enforced from a map the gateway refreshes every minute, so a client can go up to sixty seconds past its cap before the first refusal; what went through is billed as usual. Calls on the client's assigned keys stop at those keys' own caps, as before.

HTTP/1.1 402 Payment Required

{"error": {"code": "client_tag_budget_exceeded",
           "type": "insufficient_quota",
           "message": "The monthly budget for client \"acme\" is spent.",
           "client": "acme"}}

Two account webhooks come out of it, each once per client per month: client_budget_warning when 80 % of the cap is used, client_budget_exceeded when it is spent. Both carry the same payload:

{
  "event": "client_budget_warning",
  "data": {
    "client_id": "cli_8f2c",
    "client": "Acme Retail",
    "tag": "acme",
    "spent_cents": 4830,
    "budget_cents": 6000
  }
}

Spend anomalies

Every ten minutes the gateway compares each tagged workflow's last hour with its own baseline: the median calls and cents per hour over its last 168 active hours, once it has at least twelve. A workflow that made at least 20 calls and five times its median, or spent at least 1 € and five times its median, raises spend_anomaly — at most once every six hours per workflow. A workflow too young for a baseline is flagged only past 200 calls in an hour.

The same notice appears on the panel's summary and usage screens for seven days, with a link to the workflow's row. The payload names the workflow, the hour's calls and cents, the baseline it was measured against, the factor, and the key that sent most of it:

{
  "event": "spend_anomaly",
  "data": {
    "workflow": "wf_8Xk2",
    "workflow_name": "Lead scoring",
    "window_minutes": 60,
    "calls": 1240,
    "cents": 412,
    "baseline_calls_hour": 61,
    "baseline_cents_hour": 21,
    "factor": 20.3,
    "top_key": {"id": "key_01hq2m", "name": "Producción · clasificador"}
  }
}

Monthly statement per end client

Every client under Clients has a statement per month, the one you print with your company's details at the top: calls and cost by workflow (the workflow tag) and by model (the catalogue model that answered, also under auto), what came through the keys you assigned against what arrived only by the client's tag, the month before with the change in percent, and — when you set a markup — the To invoice line. Every table adds up to the total to the cent. Pick the month on the Clients screen and open Statement, or download CSV for the same figures as one long table.

The panel reads it from the control plane with your session; month is 0 (this month so far) to 12, and 1 — the month that closed — is the default. Admins only, like the rest of Clients.

GET /api/clients/{client_id}/statement?month=1

{
  "client": {"id": "0b7e…", "name": "Acme Retail", "tag": "acme"},
  "period": {"start": "2026-09-01", "end": "2026-10-01", "label": "2026-09", "complete": true},
  "calls": 1840, "messages": 1912.5, "total_cents": 4830,
  "keyed":  {"calls": 610,  "cents": 1520},
  "tagged": {"calls": 1230, "cents": 3310},
  "by_workflow": [{"workflow": "wf_8Xk2", "name": "Lead scoring", "calls": 1230, "cents": 3310},
                  {"workflow": null, "name": null, "calls": 610, "cents": 1520}],
  "by_model": [{"model": "mistral-small", "calls": 1700, "cents": 2410},
               {"model": "qwen3-235b", "calls": 140, "cents": 2420}],
  "markup_pct": 30, "suggested_invoice_cents": 6279,
  "previous": {"label": "2026-08", "calls": 1510, "total_cents": 3960},
  "change_pct": 22
}

The first days of each month the client_statement_ready webhook carries each active client's statement for the month that closed, once per client and month, with the links to its HTML and CSV — so n8n can mail it to the client the morning the month turns. A client created after the month closed gets none for it. The statement is information: we never invoice your client and never move money between you.

{
  "event": "client_statement_ready",
  "data": {
    "client_id": "0b7e…",
    "month_label": "2026-09",
    "statement": { "…": "the object above" },
    "html_url": "https://app…/api/clients/statement?client_id=0b7e…&month=1",
    "csv_url": "https://app…/api/clients/statement?client_id=0b7e…&month=1&format=csv"
  }
}

Export

The Export CSV button on Usage downloads the window on screen, grouped the way it is grouped on screen. The same file is one GET away for a spreadsheet or a cron:

GET /api/usage/export.csv?days=30&by=workflow
Authorization: Bearer pat_…          # an account token with the read scope

date,bucket,calls,tokens_in,tokens_out,cents
2026-09-14,wf_8Xk2,61,48210,9120,21
2026-09-14,untagged,12,9800,2100,4

Which model auto chose, and why

Every answer served through the auto alias carries X-Auto-Reason: short, tools, structured, long_answer, long_prompt, code, router_off, or profile: followed by the task type when a task profile decided. Tag a call with task and the profile for that task, if the router has learnt one, is what serves it. The Limits page explains the order.

X-Requested-Model: auto
X-Resolved-Model: mistral-small
X-Auto-Reason: short

Live tail

GET /api/usage/live streams every new call of the project as Server-Sent Events, about a second after it lands: the alias asked for and the one that answered, why auto chose it, the call's tags, status, cost, latency, whether the cache answered and which kind of client sent it. Never the prompt, never the answer, never a backend id. The panel's Usage screen has it as the Live tab; the SDKs have it as tail(). It takes a signed-in session or an account token with the read scope.

curl -N https://platform.enroutia.com/api/usage/live \
  -H "Authorization: Bearer pat_…"      # an account token with the read scope

retry: 3000

event: ready
data: {"since":"2026-09-25T12:00:00+00:00","poll_seconds":1.0,"heartbeat_seconds":15.0,"max_seconds":600.0}

id: 2026-09-25T12:00:03.412+00:00|chatcmpl-8f2c
event: call
data: {"call_id":"chatcmpl-8f2c","created_at":"2026-09-25T12:00:03.412+00:00","alias":"auto",
       "served_alias":"mistral-small","route_reason":"short","tags":{"workflow":"wf_8Xk2"},
       "status":"ok","cents":0.123,"millicents":123,"latency_ms":410,"cache_hit":false,
       "client_family":"openai-sdk"}

: keepalive

A project can have three live tails open at once; a fourth gets 429 live_connections_limit with Retry-After. The server sends a keepalive comment every 15 seconds and closes each stream after ten minutes; clients reconnect after the retry of 3 seconds, sending the last event id as Last-Event-ID, and resume where they left off. cents is exact to a thousandth of a cent, and millicents carries the same amount as an integer.

See also: Limits · Aliases · SDKs for Python and JavaScript