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.
Configuration
Section titled “Configuration”auth.teams replaces the single auth.token:
auth: teams: - name: payments token: ${VECTORSTEP_WEBHOOK_TOKEN_PAYMENTS} - name: platform token: ${VECTORSTEP_WEBHOOK_TOKEN_PLATFORM} # token: ${VECTORSTEP_WEBHOOK_TOKEN} # legacy single-token form, still supportedGenerate 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 — config.yaml is gitignored either way. 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.teamsis set, each entry’s token is checked onPOST /webhook; a recognized token resolves the run’steam, an unrecognized or missing token still 401s exactly as before — no separate rejection path is needed for “no team supplied,” since an unattributed/unauthenticated call already fails auth. - If
auth.teamsis absent and the legacyauth.tokenis set, behaviour is unchanged — single shared token, every run’steamisNone(unattributed). If both are set,auth.teamswins silently. - If neither is set,
POST /webhookis unauthenticated, same as today.
Non-webhook runs
Section titled “Non-webhook runs”Non-webhook runs don’t have a caller/token to resolve team from:
- Scheduled (cron) runs declare
team:directly on the pipeline’sschedule:block — trusted because it’s git-controlled config, not external input. - Sub-pipeline calls (
executor: pipeline) inherit the parent run’steamautomatically, the same way they inheritlabels/metadata, and it can be overridden per-call viacontext: {team: "..."}like any other field.
Team budgets
Section titled “Team budgets”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.