# Cost attribution

> Tag each request with the workflow, feature, customer and team it serves, then group estimated provider cost by any of them.

Source: https://infro.io/docs/features/attribution

> **Note:** INFRO is in private early access. Running these examples requires an early-access account, an active trial or subscription entitlement, and your own provider connection. Self-serve purchasing is not generally open. See [Early access](https://infro.io/early-access).

Every request already carries its project, gateway key, environment, model, provider and route. Attribution adds the dimensions only your application knows: which workflow ran, which product feature it served, which of your customers it was for, and any team or cost code you use. Send them once and they are stored on the request row, grouped in usage reports, matched by cost-center rules and exported in FOCUS.

## Reserved metadata keys

| Key | Header | What it is |
| --- | --- | --- |
| `workflow` | `X-INFRO-Workflow` | The stable name of the work: `support-answer`, `nightly-indexer`. |
| `trace_id` | `X-INFRO-Trace-Id` | One run of that workflow. A run is one `(workflow, trace_id)` pair. |
| `step` | `X-INFRO-Step` | This call's role in the run: `retrieve`, `generate`. |
| `feature` | `X-INFRO-Feature` | The product feature the call serves: `summarize`, `search`. |
| `user_id` | — | Your end customer, as your application identifies them. |

`workflow`, `step` and `feature` are up to 64 characters of letters, digits and `_ . : -`. A malformed value is refused with `400 invalid_attribution` naming the key, rather than dropped. Body `metadata` wins over a header; the header exists for requests that cannot carry a JSON body.

```bash
curl https://api.infro.io/v1/chat/completions \
  -H "Authorization: Bearer $INFRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.6-sol",
    "messages": [{"role": "user", "content": "Summarize this ticket"}],
    "metadata": {
      "workflow": "support-agent",
      "trace_id": "run_8f2c",
      "feature": "summarize",
      "user_id": "cust_123",
      "tags": {"team": "support"}
    }
  }'
```

> **Note:** Attribution applies from the request that first carries it. Requests made before you started sending `feature` stay unattributed; nothing is backfilled or guessed.

## Promoted tags

`metadata.tags` is free-form and stays on the request. To group or match by a tag, promote its key under **Cost centers → Attribution settings** in the console. An organization can promote up to five keys. Only requests metered after a key is promoted carry it as a dimension; earlier requests are not rewritten.

Grouping by a key that is not promoted is refused with `400 tag_not_promoted` rather than answered from a partial scan.

## Grouping

`GET /v1/usage?group_by=feature` and `group_by=tag:<key>` work beside the existing groupings. Because you choose their cardinality, they return the hundred largest groups by estimated cost plus one `__other__` row with the remainder, and `groups_truncated` says so. The rows still add up. See [Usage analytics](https://infro.io/docs/observability/analytics).

For cost per run, per request and per customer, use [Unit economics](https://infro.io/docs/api/unit-economics). For booking cost to teams, use [Cost centers](https://infro.io/docs/features/cost-centers).

## Plans

Feature attribution, promoted tags and unit economics are on every plan. Cost centers, shared-cost allocation, monthly statements and revenue margin are included from the Team plan.
