Skip to content

Team attribution

To show LLM token spend broken down by owning team/department, every run is tagged with a team — used for the vectorstep_pipeline_tokens_total metric (see Observability) and the GET /runs?team= filter.

Team comes from the Bearer token that authenticated the webhook, not from a field in the payload. A self-reported team in a JSON body is spoofable and easy to get wrong; tying team to the auth credential makes attribution authoritative, and “onboarding a team” becomes synonymous with “issuing them a token” — a natural gate.

team is a field on a webhook-role token under auth.tokens — see Security for the full token/role model:

auth:
tokens:
- name: payments-webhook
token: ${VECTORSTEP_WEBHOOK_TOKEN_PAYMENTS}
role: webhook
team: payments
- name: platform-webhook
token: ${VECTORSTEP_WEBHOOK_TOKEN_PLATFORM}
role: webhook
team: platform

team is only valid on a webhook-role token — VectorStep refuses to start if it’s set on admin/operator/viewer. A webhook token with no team stays valid and attributes its runs to None (unattributed), the same as the legacy single-token form below.

Generate each team’s token with openssl rand -hex 24 (or any other source of cryptographically random bytes) — a 48-character hex string. There’s no token issuance endpoint; this is a plain shared secret, handled the same way as executors.gateway.token and the Telegram bot_token elsewhere in config.yaml: either resolved from an environment variable via ${ENV_VAR} as shown above, or written directly into config.yaml if you’re not using env vars for secrets. Regenerate it yourself locally rather than reusing a value that’s appeared anywhere else (a chat transcript, an issue tracker, etc.), since a real secret should only ever exist in the one place it’s actually used.

  • If auth.tokens is set, each webhook-role entry’s token is checked on POST /webhook; a recognized token resolves the run’s team from that entry’s team field, an unrecognized or missing token 401s.
  • auth.teams and auth.token are deprecated — VectorStep still parses them (logging a warning naming the replacement) if auth.tokens isn’t set, mapping each auth.teams entry to a webhook-role token with that name as its team, and auth.token to a single unattributed webhook-role token. If both auth.tokens and a legacy form are present, auth.tokens wins.
  • VectorStep will not start with no tokens configured at all unless auth.allow_unauthenticated: true is set explicitly — see Security.

Non-webhook runs don’t have a caller/token to resolve team from:

  • Scheduled (cron) runs declare team: directly on the pipeline’s schedule: block — trusted because it’s git-controlled config, not external input.
  • Sub-pipeline calls (executor: pipeline) inherit the parent run’s team automatically, the same way they inherit labels/metadata, and it can be overridden per-call via context: {team: "..."} like any other field.
  • Run now (the pipeline detail page’s manual trigger) is always unattributed (team: None), regardless of which token/role is logged in — there’s no per-run team concept in a UI session, unlike a webhook’s Bearer-token attribution.
  • Rerun (POST /runs/{run_id}/rerun) is also always unattributed, even when the original run had a real team — rerun reconstructs its context from the original run’s persisted data, but team is not one of the fields it carries forward. This is easy to miss if you’re relying on rerun to reproduce a run faithfully for cost-attribution purposes: the new run’s spend lands in “Unattributed,” not the original team’s bucket.

Converting tokens to a dollar figure, and setting an advisory monthly budget per team, is covered in Cost accounting. The openclaw executor’s lack of token reporting still applies there too — a team running mostly openclaw steps will undercount regardless, which is why every cost aggregate carries an “unpriced steps” annotation rather than silently showing a possibly-partial total as if it were complete.

If one platform team runs VectorStep for several other teams, see Multi-team config ownership for a GitOps repo layout — per-team CODEOWNERS on pipelines/steps, credential minting kept centralized to the platform team.