Mint, list, and revoke API tokens, read a token’s scope back from the server, and run the two modes that replace tokens: no-auth and header-trust.
Every route on the /v1 API except GET /v1/capabilities and /config.json requires an
API token in an Authorization: Bearer header. A token has one role: ingest tokens write
records, heartbeats, and capture policy; read tokens query. GET /v1/whoami is the one
authenticated route either role can call. The relay daemon holds an ingest token. The UI, the
MCP server, and any script that reads each hold a read token. Rate-limit numbers are on
Conventions.Flags are spelled with a single dash, as -h prints them; the binary accepts both spellings.
A token is vxlo_<role>_<token_id>_<secret>: role is ing or read; token_id is 13
lowercase base32 characters, the non-secret lookup key; secret is 43 base64url characters, 256
random bits. The server stores a SHA-256 hash of the secret and compares in constant time. The
full string is printed once, at vexlo token create, and cannot be read back: vexlo token list
shows every column but the hash.Team is derived from the token, never declared by a client. An ingest token binds exactly one
team, and every row its traffic produces inherits that team. A read token spans one or more
teams (its team-set); ?team= on a read route narrows the set and never widens it. Team ids
match ^[a-z0-9-]{1,64}$.
vexlo token works directly on the server storage directory: -data-dir (or
VEXLO_DATA_DIR) names it, default ~/.vexlo/data. vexlo serve falls back to the same
default, so with no flags anywhere both sides use one directory. serve never reads the
-data-dir flag itself: a token minted under an explicit -data-dir reaches the server only
when serve names that directory too, through VEXLO_DATA_DIR or through -db-path and
-value-store-dir. The server does not have to be running. These runs name a scratch
directory, data.
$ vexlo token create -data-dir data -role ingest -team delivery \ -service delivery-service -environment dev -label "relay on host-a"token_id: wfxuppcwaepj4token: vxlo_ing_wfxuppcwaepj4_DndgZ4yW0K3MpEwq-OMzyO7ZCGVXVvPs5r8DWkaeMl0This token is shown once and cannot be recovered — store it now.
$ vexlo token create -data-dir data -role read -team delivery,platform -label "mcp read token"token_id: dp35s4pf6hydqtoken: vxlo_read_dp35s4pf6hydq_Vlx7QuCaF_uFLsPaPcqDJpVR0EzcA2CmiOxN2sjJcx8This token is shown once and cannot be recovered — store it now.
Two things the command refuses at mint time: an ingest token with more than one team, and a team
id outside the charset.
$ vexlo token create -data-dir data -role ingest -team a,bvexlo: token create: auth: create token: an ingest token's --team accepts exactly one value, got 2
$ vexlo token create -data-dir data -role ingest -team Team-Checkoutvexlo: token create: auth: create token: auth: team id "Team-Checkout" contains 'T', want only [a-z0-9-]
The flags, as vexlo token create -h prints them:
Usage of token create: -data-dir string server storage directory (env VEXLO_DATA_DIR, default ~/.vexlo/data) -environment string optional comma-separated environment-name pin -label string optional free-text label, never used in auth logic -role string token role: ingest or read (required) -service string optional service-name pin -team string team id(s), required — a single value for an ingest token, comma-separated for a read tokenvexlo: flag: help requested
-environment accepts a comma-separated list at mint time, but a token pinned to more than one
environment cannot ingest today: every POST /v1/records, POST /v1/heartbeat, and
PUT /v1/policy it sends is answered 500 internal_error. Pin one value.
An ingest token carries two optional pins. -service <name> restricts the token to one declared
service: a batch whose records declare another service, or a PUT /v1/policy?service= naming
another service, is 422 service_scope_mismatch. -environment <name> restricts it to one
environment: a POST /v1/records whose X-Vexlo-Environment header carries another value is
422 environment_scope_mismatch, and so is a batch that sends no header at all:
A relay daemon forwarding under an environment-pinned token therefore runs with
-environment <that value>; see The relay daemon. POST /v1/heartbeat
enforces neither pin. The pins restrict what a token can write; they say nothing about reads.
vexlo token revoke <token_id> sets revoked_at. Revocation is checked on every request, so it
takes effect on the next one. Revoking a token a relay daemon still holds costs records: the
daemon drops every batch the server answers 401, rather than keeping them on disk
(What it sends).
$ vexlo token revoke -data-dir data ouy7xcixo7pmwrevoked token ouy7xcixo7pmw
GET /v1/whoami accepts either role and reflects the validated token. service_scope and
environment_scope are null when the token carries no such pin, whatever its role:
vexlo token create takes -service and -environment for a read token as well, and
whoami then reports them. The read token below was minted with neither pin:
A missing header, an unknown token, and a revoked token are each 401 unauthenticated with a
distinct message. An ingest token on a read route is 403 forbidden_role. A read token asking
for a trace outside its team-set gets 404, the same answer as a trace that does not exist.
Rate-limit headers are absent from 401 responses.
The mirror case is not a 403. A read token on an ingest route is answered 500, with code
internal_error and the message authentication failed: the same answer on
POST /v1/records, POST /v1/heartbeat, and PUT /v1/policy.
vexlo serve -no-auth disables the token system. Three things follow. The bind address is forced
to loopback whatever -api-addr says (the port is kept; -allow-insecure-http does not lift
this). Every ingested row and every query is scoped to one team, -team or VEXLO_TEAM, default
default. Rate limiting is off, so no X-Ratelimit-* headers appear on any response.
GET /v1/capabilities reports "auth_mode": "none" in this mode; the body is on
Identity and bootstrap. No-auth mode and header-trust mode together is a
startup error:
$ vexlo check -no-auth -header-trust -header-trust-secret x \ -db-path data/vexlo.db -value-store-dir data/valuesvexlo: check: vexlo serve: --no-auth and header-trust mode (-header-trust-secret/VEXLO_HEADER_TRUST_SECRET) cannot both be set: --no-auth already grants unauthenticated loopback access
Header-trust mode is for a read API behind a reverse proxy that has already authenticated a
person and knows their team. It is read-only: ingest always authenticates with a bearer token.
Start the server with -header-trust and -header-trust-secret <value> (or VEXLO_HEADER_TRUST
and VEXLO_HEADER_TRUST_SECRET). The proxy injects two headers on every request it forwards:
X-Vexlo-Team: <id>[,<id>...], the team-set, and X-Vexlo-Trust-Secret: <value>, the shared
secret, which the server checks against a stored SHA-256 hash in constant time. The headers are
consulted only when Authorization is absent; a request that carries a bearer token is validated
as a bearer token.
-header-trust without a secret is a startup error:
$ vexlo check -header-trust -db-path data/vexlo.db -value-store-dir data/valuesvexlo: check: -header-trust is set but -header-trust-secret (or VEXLO_HEADER_TRUST_SECRET) is empty — header-trust mode needs a shared secret to check the proxy's own X-Vexlo-Trust-Secret against
Rate limiting in this mode keys on the resolved X-Vexlo-Team value in place of a token id.
GET /v1/capabilities reports "auth_mode": "header_trust".
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Vexlo","colors":{"primary":"#7c3aed","light":"#a78bfa","dark":"#7c3aed"},"logo":"https://mintcdn.com/vexlo-d15ac36e/Hrqh5lNqx79rXIqZ/logo.png?fit=max\u0026auto=format\u0026n=Hrqh5lNqx79rXIqZ\u0026q=85\u0026s=259148237c8a46e08eca0a01ee57b99f","favicon":"https://mintcdn.com/vexlo-d15ac36e/Hrqh5lNqx79rXIqZ/favicon.png?fit=max\u0026auto=format\u0026n=Hrqh5lNqx79rXIqZ\u0026q=85\u0026s=93bd27815f95c074f09a66fbbf314d4a","appearance":{"default":"system"},"styling":{"codeblocks":"system","latex":false},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"vexlo-d15ac36e","hasAudioNarration":false,"actualSubdomain":"vexlo-d15ac36e","customDomains":[],"gitSource":{"type":"github","owner":"vexlo-ai","repo":"vexlo-docs","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":"","_id":"6aeec7f4b122980c4ed87819"},"entitlements":{"AI_CHAT":{"status":"ENABLED"},"WEB_MCP":{"status":"ENABLED"},"NARRATION":{"status":"ENABLED"}},"buildId":"6aeec996b122980c4ed8ed2f","clientVersion":"0.0.4076","searchFilterCounts":[{"language":"default","tag":"MCP server","count":8},{"language":"default","tag":"Python SDK","count":7},{"language":"default","tag":"API reference","count":6},{"language":"default","tag":"Concepts","count":4},{"language":"default","tag":"Server","count":4},{"language":"default","tag":"Get started","count":2},{"language":"default","tag":"Reference","count":1}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":true,"hasWebMcpPermissions":true,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"rbac-expansion","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-metadata","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-serve","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"live-agents","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"comment-mode","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[],"page":{"pagePath":"server/authentication","slug":"/server/authentication","pageMetadata":{"title":"Authentication and tokens","description":"Mint, list, and revoke API tokens, read a token's scope back from the server, and run the two modes that replace tokens: no-auth and header-trust.","href":"/server/authentication"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"Mint, list, and revoke API tokens, read a token’s scope back from the server, and run the two modes that replace tokens: no-auth and header-trust.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,p:`p`,pre:`pre`,span:`span`,..._provideComponents(),...props.components},{CodeBlock,Heading,Note}=_components;if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Note)_missingMdxReference(`Note`,true);return _jsxs(_Fragment,{children:[_jsxs(_components.p,{children:[`Every route on the `,_jsx(_components.code,{children:`/v1`}),` API except `,_jsx(_components.code,{children:`GET /v1/capabilities`}),` and `,_jsx(_components.code,{children:`/config.json`}),` requires an\nAPI token in an `,_jsx(_components.code,{children:`Authorization: Bearer`}),` header. A token has one role: `,_jsx(_components.code,{children:`ingest`}),` tokens write\nrecords, heartbeats, and capture policy; `,_jsx(_components.code,{children:`read`}),` tokens query. `,_jsx(_components.code,{children:`GET /v1/whoami`}),` is the one\nauthenticated route either role can call. The relay daemon holds an ingest token. The UI, the\nMCP server, and any script that reads each hold a read token. Rate-limit numbers are on\n`,_jsx(_components.a,{href:`/api/conventions`,children:`Conventions`}),`.`]}),`\n`,_jsxs(_components.p,{children:[`Flags are spelled with a single dash, as `,_jsx(_components.code,{children:`-h`}),` prints them; the binary accepts both spellings.`]}),`\n`,_jsx(Heading,{level:`2`,id:`token-shape`,children:`Token shape`}),`\n`,_jsxs(_components.p,{children:[`A token is `,_jsx(_components.code,{children:`vxlo_\u003crole>_\u003ctoken_id>_\u003csecret>`}),`: `,_jsx(_components.code,{children:`role`}),` is `,_jsx(_components.code,{children:`ing`}),` or `,_jsx(_components.code,{children:`read`}),`; `,_jsx(_components.code,{children:`token_id`}),` is 13\nlowercase base32 characters, the non-secret lookup key; `,_jsx(_components.code,{children:`secret`}),` is 43 base64url characters, 256\nrandom bits. The server stores a SHA-256 hash of the secret and compares in constant time. The\nfull string is printed once, at `,_jsx(_components.code,{children:`vexlo token create`}),`, and cannot be read back: `,_jsx(_components.code,{children:`vexlo token list`}),`\nshows every column but the hash.`]}),`\n`,_jsxs(_components.p,{children:[`Team is derived from the token, never declared by a client. An ingest token binds exactly one\nteam, and every row its traffic produces inherits that team. A read token spans one or more\nteams (its team-set); `,_jsx(_components.code,{children:`?team=`}),` on a read route narrows the set and never widens it. Team ids\nmatch `,_jsx(_components.code,{children:`^[a-z0-9-]{1,64}$`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`mint-a-token`,children:`Mint a token`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`vexlo token`}),` works directly on the server storage directory: `,_jsx(_components.code,{children:`-data-dir`}),` (or\n`,_jsx(_components.code,{children:`VEXLO_DATA_DIR`}),`) names it, default `,_jsx(_components.code,{children:`~/.vexlo/data`}),`. `,_jsx(_components.code,{children:`vexlo serve`}),` falls back to the same\ndefault, so with no flags anywhere both sides use one directory. `,_jsx(_components.code,{children:`serve`}),` never reads the\n`,_jsx(_components.code,{children:`-data-dir`}),` flag itself: a token minted under an explicit `,_jsx(_components.code,{children:`-data-dir`}),` reaches the server only\nwhen `,_jsx(_components.code,{children:`serve`}),` names that directory too, through `,_jsx(_components.code,{children:`VEXLO_DATA_DIR`}),` or through `,_jsx(_components.code,{children:`-db-path`}),` and\n`,_jsx(_components.code,{children:`-value-store-dir`}),`. The server does not have to be running. These runs name a scratch\ndirectory, `,_jsx(_components.code,{children:`data`}),`.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`6`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`6`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ vexlo token create -data-dir data -role ingest -team delivery \\\\`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` -service delivery-service -environment dev -label \"relay on host-a\"`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`token_id: wfxuppcwaepj4`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`token: vxlo_ing_wfxuppcwaepj4_DndgZ4yW0K3MpEwq-OMzyO7ZCGVXVvPs5r8DWkaeMl0`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`This token is shown once and cannot be recovered — store it now.`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`5`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`5`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ vexlo token create -data-dir data -role read -team delivery,platform -label \"mcp read token\"`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`token_id: dp35s4pf6hydq`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`token: vxlo_read_dp35s4pf6hydq_Vlx7QuCaF_uFLsPaPcqDJpVR0EzcA2CmiOxN2sjJcx8`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`This token is shown once and cannot be recovered — store it now.`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(_components.p,{children:`Two things the command refuses at mint time: an ingest token with more than one team, and a team\nid outside the charset.`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`3`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`3`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ vexlo token create -data-dir data -role ingest -team a,b`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`vexlo: token create: auth: create token: an ingest token's --team accepts exactly one value, got 2`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`3`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`3`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ vexlo token create -data-dir data -role ingest -team Team-Checkout`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`vexlo: token create: auth: create token: auth: team id \"Team-Checkout\" contains 'T', want only [a-z0-9-]`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsxs(_components.p,{children:[`The flags, as `,_jsx(_components.code,{children:`vexlo token create -h`}),` prints them:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`15`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`15`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Usage of token create:`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` -data-dir string`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` \tserver storage directory (env VEXLO_DATA_DIR, default ~/.vexlo/data)`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` -environment string`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` \toptional comma-separated environment-name pin`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` -label string`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` \toptional free-text label, never used in auth logic`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` -role string`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` \ttoken role: ingest or read (required)`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` -service string`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` \toptional service-name pin`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` -team string`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` \tteam id(s), required — a single value for an ingest token, comma-separated for a read token`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`vexlo: flag: help requested`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(Note,{children:_jsxs(_components.p,{children:[_jsx(_components.code,{children:`-environment`}),` accepts a comma-separated list at mint time, but a token pinned to more than one\nenvironment cannot ingest today: every `,_jsx(_components.code,{children:`POST /v1/records`}),`, `,_jsx(_components.code,{children:`POST /v1/heartbeat`}),`, and\n`,_jsx(_components.code,{children:`PUT /v1/policy`}),` it sends is answered `,_jsx(_components.code,{children:`500 internal_error`}),`. Pin one value.`]})}),`\n`,_jsx(Heading,{level:`3`,id:`pins`,children:`Pins`}),`\n`,_jsxs(_components.p,{children:[`An ingest token carries two optional pins. `,_jsx(_components.code,{children:`-service \u003cname>`}),` restricts the token to one declared\nservice: a batch whose records declare another service, or a `,_jsx(_components.code,{children:`PUT /v1/policy?service=`}),` naming\nanother service, is `,_jsx(_components.code,{children:`422 service_scope_mismatch`}),`. `,_jsx(_components.code,{children:`-environment \u003cname>`}),` restricts it to one\nenvironment: a `,_jsx(_components.code,{children:`POST /v1/records`}),` whose `,_jsx(_components.code,{children:`X-Vexlo-Environment`}),` header carries another value is\n`,_jsx(_components.code,{children:`422 environment_scope_mismatch`}),`, and so is a batch that sends no header at all:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`10`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`10`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ curl -s -i -X POST -H \"Authorization: Bearer $INGEST_TOKEN\" \\\\`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` -H \"Content-Type: application/json\" -d '[]' http://127.0.0.1:18100/v1/records`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`HTTP/1.1 422 Unprocessable Entity`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Type: application/json`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Request-Id: c189b228fd6477c9`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Date: Mon, 14 Sep 2026 08:50:16 GMT`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Length: 116`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`{\"error\":{\"code\":\"environment_scope_mismatch\",\"message\":\"token's environment_scope pin excludes this environment\"}}`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsxs(_components.p,{children:[`A relay daemon forwarding under an environment-pinned token therefore runs with\n`,_jsx(_components.code,{children:`-environment \u003cthat value>`}),`; see `,_jsx(_components.a,{href:`/server/relay-daemon`,children:`The relay daemon`}),`. `,_jsx(_components.code,{children:`POST /v1/heartbeat`}),`\nenforces neither pin. The pins restrict what a token can write; they say nothing about reads.`]}),`\n`,_jsx(Heading,{level:`2`,id:`revoke-and-list`,children:`Revoke and list`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`vexlo token revoke \u003ctoken_id>`}),` sets `,_jsx(_components.code,{children:`revoked_at`}),`. Revocation is checked on every request, so it\ntakes effect on the next one. Revoking a token a relay daemon still holds costs records: the\ndaemon drops every batch the server answers `,_jsx(_components.code,{children:`401`}),`, rather than keeping them on disk\n(`,_jsx(_components.a,{href:`/server/relay-daemon#what-it-sends`,children:`What it sends`}),`).`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`3`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`3`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ vexlo token revoke -data-dir data ouy7xcixo7pmw`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`revoked token ouy7xcixo7pmw`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`9`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`9`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ curl -s -i -H \"Authorization: Bearer $REVOKED_TOKEN\" http://127.0.0.1:18100/v1/whoami`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`HTTP/1.1 401 Unauthorized`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Type: application/json`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Request-Id: 79cd03ef8e7636f6`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Date: Mon, 14 Sep 2026 08:53:23 GMT`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Length: 63`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`{\"error\":{\"code\":\"unauthenticated\",\"message\":\"token revoked\"}}`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`vexlo token list`}),` prints every token, active or revoked. `,_jsx(_components.code,{children:`last_used_at`}),` is written at most once\nper 60 seconds per token.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`21`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`21`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ vexlo token list -data-dir data`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`token_id: wfxuppcwaepj4`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` role: ingest`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` teams: delivery`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` service_scope: delivery-service`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` environment_scope: dev`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` label: relay on host-a`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` created_at: 2026-09-14T08:49:05Z`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` last_used_at: 2026-09-14T08:51:10Z`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` revoked_at: (active)`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`... 18 lines omitted`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`token_id: ouy7xcixo7pmw`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` role: ingest`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` teams: delivery`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` service_scope: (unpinned)`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` environment_scope: prod,staging`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` label: (none)`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` created_at: 2026-09-14T08:49:05Z`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` last_used_at: 2026-09-14T08:50:16Z`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` revoked_at: 2026-09-14T08:53:23Z`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(Heading,{level:`2`,id:`read-a-token’s-scope-back`,children:`Read a token’s scope back`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`GET /v1/whoami`}),` accepts either role and reflects the validated token. `,_jsx(_components.code,{children:`service_scope`}),` and\n`,_jsx(_components.code,{children:`environment_scope`}),` are `,_jsx(_components.code,{children:`null`}),` when the token carries no such pin, whatever its role:\n`,_jsx(_components.code,{children:`vexlo token create`}),` takes `,_jsx(_components.code,{children:`-service`}),` and `,_jsx(_components.code,{children:`-environment`}),` for a read token as well, and\n`,_jsx(_components.code,{children:`whoami`}),` then reports them. The read token below was minted with neither pin:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`12`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`12`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ curl -s -i -H \"Authorization: Bearer $READ_TOKEN\" http://127.0.0.1:18100/v1/whoami`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`HTTP/1.1 200 OK`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Type: application/json`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Ratelimit-Limit: 20`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Ratelimit-Remaining: 39`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Ratelimit-Reset: 1`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Request-Id: 4417f20c1e331770`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Date: Mon, 14 Sep 2026 08:49:33 GMT`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Length: 97`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`{\"role\":\"read\",\"team_ids\":[\"delivery\",\"platform\"],\"service_scope\":null,\"environment_scope\":null}`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`12`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`12`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ curl -s -i -H \"Authorization: Bearer $INGEST_TOKEN\" http://127.0.0.1:18100/v1/whoami`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`HTTP/1.1 200 OK`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Type: application/json`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Ratelimit-Limit: 20`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Ratelimit-Remaining: 39`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Ratelimit-Reset: 1`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Request-Id: 011f47d461150737`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Date: Mon, 14 Sep 2026 08:49:33 GMT`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Length: 105`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`{\"role\":\"ingest\",\"team_ids\":[\"delivery\"],\"service_scope\":\"delivery-service\",\"environment_scope\":[\"dev\"]}`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(Heading,{level:`2`,id:`refusals`,children:`Refusals`}),`\n`,_jsxs(_components.p,{children:[`A missing header, an unknown token, and a revoked token are each `,_jsx(_components.code,{children:`401 unauthenticated`}),` with a\ndistinct message. An ingest token on a read route is `,_jsx(_components.code,{children:`403 forbidden_role`}),`. A read token asking\nfor a trace outside its team-set gets `,_jsx(_components.code,{children:`404`}),`, the same answer as a trace that does not exist.\nRate-limit headers are absent from 401 responses.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`9`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`9`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ curl -s -i http://127.0.0.1:18100/v1/traces`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`HTTP/1.1 401 Unauthorized`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Type: application/json`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Request-Id: 6cd923a9cdb0f4b1`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Date: Mon, 14 Sep 2026 08:49:33 GMT`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Length: 78`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`{\"error\":{\"code\":\"unauthenticated\",\"message\":\"missing Authorization header\"}}`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`10`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`10`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ curl -s -i http://127.0.0.1:18100/v1/traces \\\\`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` -H \"Authorization: Bearer vxlo_read_0000000000000_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`HTTP/1.1 401 Unauthorized`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Type: application/json`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Request-Id: d5fc38cdf489f29e`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Date: Mon, 14 Sep 2026 08:49:33 GMT`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Length: 63`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`{\"error\":{\"code\":\"unauthenticated\",\"message\":\"unknown token\"}}`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`12`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`12`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ curl -s -i -H \"Authorization: Bearer $INGEST_TOKEN\" http://127.0.0.1:18100/v1/traces`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`HTTP/1.1 403 Forbidden`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Type: application/json`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Ratelimit-Limit: 20`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Ratelimit-Remaining: 38`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Ratelimit-Reset: 1`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Request-Id: d4f4be369cb3b07d`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Date: Mon, 14 Sep 2026 08:49:33 GMT`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Length: 104`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`{\"error\":{\"code\":\"forbidden_role\",\"message\":\"token role \\\\\"ingest\\\\\" cannot call a \\\\\"read\\\\\"-only route\"}}`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsxs(_components.p,{children:[`The mirror case is not a `,_jsx(_components.code,{children:`403`}),`. A read token on an ingest route is answered `,_jsx(_components.code,{children:`500`}),`, with code\n`,_jsx(_components.code,{children:`internal_error`}),` and the message `,_jsx(_components.code,{children:`authentication failed`}),`: the same answer on\n`,_jsx(_components.code,{children:`POST /v1/records`}),`, `,_jsx(_components.code,{children:`POST /v1/heartbeat`}),`, and `,_jsx(_components.code,{children:`PUT /v1/policy`}),`.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`10`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`10`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ curl -s -i -X POST -H \"Authorization: Bearer $READ_TOKEN\" \\\\`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` -H \"Content-Type: application/json\" -d '[]' http://127.0.0.1:18100/v1/records`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`HTTP/1.1 500 Internal Server Error`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Type: application/json`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Request-Id: 6b4e3fa6ef050c2d`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Date: Mon, 14 Sep 2026 08:49:33 GMT`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Length: 70`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`{\"error\":{\"code\":\"internal_error\",\"message\":\"authentication failed\"}}`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(Heading,{level:`2`,id:`no-auth-mode`,children:`No-auth mode`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`vexlo serve -no-auth`}),` disables the token system. Three things follow. The bind address is forced\nto loopback whatever `,_jsx(_components.code,{children:`-api-addr`}),` says (the port is kept; `,_jsx(_components.code,{children:`-allow-insecure-http`}),` does not lift\nthis). Every ingested row and every query is scoped to one team, `,_jsx(_components.code,{children:`-team`}),` or `,_jsx(_components.code,{children:`VEXLO_TEAM`}),`, default\n`,_jsx(_components.code,{children:`default`}),`. Rate limiting is off, so no `,_jsx(_components.code,{children:`X-Ratelimit-*`}),` headers appear on any response.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`5`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`5`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ vexlo serve -no-auth -api-addr 0.0.0.0:18101 -db-path data-noauth/vexlo.db \\\\`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` -value-store-dir data-noauth/values`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`time=2026-09-14T01:53:41.757-07:00 level=INFO msg=\"serve: starting\" addr=127.0.0.1:18101 requires_tls=false no_auth=true header_trust=false db_path=data-noauth/vexlo.db value_store_dir=data-noauth/values shape_retention_days=7 value_budget_bytes=21474836480`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`... 3 lines omitted`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`GET /v1/whoami`}),` answers every caller identically, with role `,_jsx(_components.code,{children:`read`}),` and the configured team:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`9`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`9`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ curl -s -i http://127.0.0.1:18101/v1/whoami`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`HTTP/1.1 200 OK`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Type: application/json`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Request-Id: 37f21cbf46861210`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Date: Mon, 14 Sep 2026 08:53:43 GMT`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Length: 85`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`{\"role\":\"read\",\"team_ids\":[\"default\"],\"service_scope\":null,\"environment_scope\":null}`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`GET /v1/capabilities`}),` reports `,_jsx(_components.code,{children:`\"auth_mode\": \"none\"`}),` in this mode; the body is on\n`,_jsx(_components.a,{href:`/api/identity`,children:`Identity and bootstrap`}),`. No-auth mode and header-trust mode together is a\nstartup error:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`4`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`4`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ vexlo check -no-auth -header-trust -header-trust-secret x \\\\`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` -db-path data/vexlo.db -value-store-dir data/values`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`vexlo: check: vexlo serve: --no-auth and header-trust mode (-header-trust-secret/VEXLO_HEADER_TRUST_SECRET) cannot both be set: --no-auth already grants unauthenticated loopback access`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(Heading,{level:`2`,id:`header-trust-mode`,children:`Header-trust mode`}),`\n`,_jsxs(_components.p,{children:[`Header-trust mode is for a read API behind a reverse proxy that has already authenticated a\nperson and knows their team. It is read-only: ingest always authenticates with a bearer token.\nStart the server with `,_jsx(_components.code,{children:`-header-trust`}),` and `,_jsx(_components.code,{children:`-header-trust-secret \u003cvalue>`}),` (or `,_jsx(_components.code,{children:`VEXLO_HEADER_TRUST`}),`\nand `,_jsx(_components.code,{children:`VEXLO_HEADER_TRUST_SECRET`}),`). The proxy injects two headers on every request it forwards:\n`,_jsx(_components.code,{children:`X-Vexlo-Team: \u003cid>[,\u003cid>...]`}),`, the team-set, and `,_jsx(_components.code,{children:`X-Vexlo-Trust-Secret: \u003cvalue>`}),`, the shared\nsecret, which the server checks against a stored SHA-256 hash in constant time. The headers are\nconsulted only when `,_jsx(_components.code,{children:`Authorization`}),` is absent; a request that carries a bearer token is validated\nas a bearer token.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`5`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`5`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ vexlo serve -api-addr 127.0.0.1:18102 -header-trust -header-trust-secret proxy-shared-secret \\\\`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` -db-path data-ht/vexlo.db -value-store-dir data-ht/values`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`time=2026-09-14T01:53:44.837-07:00 level=INFO msg=\"serve: starting\" addr=127.0.0.1:18102 requires_tls=false no_auth=false header_trust=true db_path=data-ht/vexlo.db value_store_dir=data-ht/values shape_retention_days=7 value_budget_bytes=21474836480`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`... 5 lines omitted`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`13`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`13`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ curl -s -i -H \"X-Vexlo-Team: delivery\" -H \"X-Vexlo-Trust-Secret: proxy-shared-secret\" \\\\`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` http://127.0.0.1:18102/v1/whoami`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`HTTP/1.1 200 OK`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Type: application/json`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Ratelimit-Limit: 20`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Ratelimit-Remaining: 39`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Ratelimit-Reset: 1`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Request-Id: da78f28f33f41022`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Date: Mon, 14 Sep 2026 08:53:46 GMT`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Length: 86`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`{\"role\":\"read\",\"team_ids\":[\"delivery\"],\"service_scope\":null,\"environment_scope\":null}`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsxs(_components.p,{children:[`A request missing either header, or carrying neither, is `,_jsx(_components.code,{children:`401`}),`; a wrong secret is a different\n`,_jsx(_components.code,{children:`401`}),`:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`9`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`9`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ curl -s -i -H \"X-Vexlo-Team: delivery\" http://127.0.0.1:18102/v1/whoami`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`HTTP/1.1 401 Unauthorized`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Type: application/json`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Request-Id: da586c33bd4651cd`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Date: Mon, 14 Sep 2026 08:53:46 GMT`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Length: 114`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`{\"error\":{\"code\":\"unauthenticated\",\"message\":\"header-trust mode requires X-Vexlo-Team and X-Vexlo-Trust-Secret\"}}`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`10`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`10`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ curl -s -i -H \"X-Vexlo-Team: delivery\" -H \"X-Vexlo-Trust-Secret: wrong\" \\\\`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:` http://127.0.0.1:18102/v1/whoami`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`HTTP/1.1 401 Unauthorized`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Type: application/json`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`X-Request-Id: ab924771bee6b165`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Date: Mon, 14 Sep 2026 08:53:46 GMT`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Content-Length: 79`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`{\"error\":{\"code\":\"unauthenticated\",\"message\":\"X-Vexlo-Trust-Secret mismatch\"}}`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`-header-trust`}),` without a secret is a startup error:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`3`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`3`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`$ vexlo check -header-trust -db-path data/vexlo.db -value-store-dir data/values`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`vexlo: check: -header-trust is set but -header-trust-secret (or VEXLO_HEADER_TRUST_SECRET) is empty — header-trust mode needs a shared secret to check the proxy's own X-Vexlo-Trust-Secret against`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsxs(_components.p,{children:[`Rate limiting in this mode keys on the resolved `,_jsx(_components.code,{children:`X-Vexlo-Team`}),` value in place of a token id.\n`,_jsx(_components.code,{children:`GET /v1/capabilities`}),` reports `,_jsx(_components.code,{children:`\"auth_mode\": \"header_trust\"`}),`.`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"codeStyling":"system","config":{},"pageMetadata":{"title":"Authentication and tokens","description":"Mint, list, and revoke API tokens, read a token's scope back from the server, and run the two modes that replace tokens: no-auth and header-trust.","href":"/server/authentication"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Token shape","slug":"token-shape","depth":2,"children":[]},{"title":"Mint a token","slug":"mint-a-token","depth":2,"children":[{"title":"Pins","slug":"pins","depth":3,"children":[]}]},{"title":"Revoke and list","slug":"revoke-and-list","depth":2,"children":[]},{"title":"Read a token’s scope back","slug":"read-a-token’s-scope-back","depth":2,"children":[]},{"title":"Refusals","slug":"refusals","depth":2,"children":[]},{"title":"No-auth mode","slug":"no-auth-mode","depth":2,"children":[]},{"title":"Header-trust mode","slug":"header-trust-mode","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":["/_mintlify/custom/vexlo-d15ac36e/7e7ec36eaeba927b/scripts/screenshot.js"],"subdomain":"vexlo-d15ac36e","siteName":"Vexlo","title":"Authentication and tokens - Vexlo","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-11-07T06:48:04.913Z","originalFileLocation":"server/authentication.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Mint, list, and revoke API tokens, read a token\u0026#x27;s scope back from the server, and run the two modes that replace tokens: no-auth and header-trust.\">\u003cmeta data-mint-head name=\"application-name\" content=\"Vexlo\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/vexlo-d15ac36e/rpq0aLEomtD6q-3G/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/vexlo-d15ac36e/rpq0aLEomtD6q-3G/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/vexlo-d15ac36e/rpq0aLEomtD6q-3G/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/vexlo-d15ac36e/rpq0aLEomtD6q-3G/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/vexlo-d15ac36e/rpq0aLEomtD6q-3G/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/vexlo-d15ac36e/rpq0aLEomtD6q-3G/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/vexlo-d15ac36e/rpq0aLEomtD6q-3G/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/vexlo-d15ac36e/rpq0aLEomtD6q-3G/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/vexlo-d15ac36e/rpq0aLEomtD6q-3G/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Authentication and tokens - Vexlo\">\u003cmeta data-mint-head property=\"og:description\" content=\"Mint, list, and revoke API tokens, read a token\u0026#x27;s scope back from the server, and run the two modes that replace tokens: no-auth and header-trust.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Vexlo\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://vexlo-d15ac36e.mintlify.app/server/authentication\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://vexlo-d15ac36e.mintlify.site/_mintlify/api/og/v2?division=Server\u0026amp;title=Authentication+and+tokens\u0026amp;description=Mint%2C+list%2C+and+revoke+API+tokens%2C+read+a+token%27s+scope+back+from+the+server%2C+and+run+the+two+modes+that+replace+tokens%3A+no-auth+and+header-trust.\u0026amp;theme=c7fad9903839519977db3c4a\u0026amp;signature=e1f08c46b115d9b3ed6ff014707a5e1504dfc92327010ff6bfc1f44bef741f0d\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Authentication and tokens - Vexlo\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Mint, list, and revoke API tokens, read a token\u0026#x27;s scope back from the server, and run the two modes that replace tokens: no-auth and header-trust.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://vexlo-d15ac36e.mintlify.site/_mintlify/api/og/v2?division=Server\u0026amp;title=Authentication+and+tokens\u0026amp;description=Mint%2C+list%2C+and+revoke+API+tokens%2C+read+a+token%27s+scope+back+from+the+server%2C+and+run+the+two+modes+that+replace+tokens%3A+no-auth+and+header-trust.\u0026amp;theme=c7fad9903839519977db3c4a\u0026amp;signature=e1f08c46b115d9b3ed6ff014707a5e1504dfc92327010ff6bfc1f44bef741f0d\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/vexlo-d15ac36e/rpq0aLEomtD6q-3G/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Vexlo\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#7c3aed\">\u003clink data-mint-head rel=\"canonical\" href=\"https://vexlo-d15ac36e.mintlify.app/server/authentication\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/server/authentication.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://vexlo-d15ac36e.mintlify.app/#organization\",\"name\":\"Vexlo\",\"url\":\"https://vexlo-d15ac36e.mintlify.app\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/vexlo-d15ac36e/Hrqh5lNqx79rXIqZ/logo.png?fit=max\u0026auto=format\u0026n=Hrqh5lNqx79rXIqZ\u0026q=85\u0026s=259148237c8a46e08eca0a01ee57b99f\"}},{\"@type\":\"WebSite\",\"@id\":\"https://vexlo-d15ac36e.mintlify.app#website\",\"name\":\"Vexlo\",\"url\":\"https://vexlo-d15ac36e.mintlify.app\",\"publisher\":{\"@id\":\"https://vexlo-d15ac36e.mintlify.app/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://vexlo-d15ac36e.mintlify.app/server/authentication#webpage\",\"url\":\"https://vexlo-d15ac36e.mintlify.app/server/authentication\",\"name\":\"Authentication and tokens\",\"description\":\"Mint, list, and revoke API tokens, read a token's scope back from the server, and run the two modes that replace tokens: no-auth and header-trust.\",\"dateModified\":\"2026-11-07T06:48:04.913Z\",\"isPartOf\":{\"@id\":\"https://vexlo-d15ac36e.mintlify.app#website\"},\"breadcrumb\":{\"@id\":\"https://vexlo-d15ac36e.mintlify.app/server/authentication#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://vexlo-d15ac36e.mintlify.app/server/authentication#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Server\",\"item\":\"https://vexlo-d15ac36e.mintlify.app/server/install\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Authentication and tokens\",\"item\":\"https://vexlo-d15ac36e.mintlify.app/server/authentication\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://vexlo-d15ac36e.mintlify.app/server/authentication#article\",\"headline\":\"Authentication and tokens\",\"name\":\"Authentication and tokens\",\"description\":\"Mint, list, and revoke API tokens, read a token's scope back from the server, and run the two modes that replace tokens: no-auth and header-trust.\",\"url\":\"https://vexlo-d15ac36e.mintlify.app/server/authentication\",\"mainEntityOfPage\":{\"@id\":\"https://vexlo-d15ac36e.mintlify.app/server/authentication#webpage\"},\"dateModified\":\"2026-11-07T06:48:04.913Z\",\"publisher\":{\"@id\":\"https://vexlo-d15ac36e.mintlify.app/#organization\"},\"isPartOf\":{\"@id\":\"https://vexlo-d15ac36e.mintlify.app#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Vexlo\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Vexlo","nav":{"global":null,"pages":[{"group":"Get started","pages":[{"title":"Introduction","href":"/index"},{"title":"Quickstart","href":"/quickstart"}]},{"group":"Concepts","pages":[{"title":"The problem","href":"/concepts/the-problem"},{"title":"Logs, spans, and execution graphs","href":"/concepts/logs-spans-execution-graphs"},{"title":"The execution graph","href":"/concepts/execution-graph"},{"title":"Use cases","href":"/concepts/use-cases"}]},{"group":"Python SDK","pages":[{"title":"Install","href":"/sdk/install"},{"title":"Initialise and record a first trace","href":"/sdk/initialise"},{"title":"Mark functions","href":"/sdk/mark-functions"},{"title":"Tags and cross-service correlation","href":"/sdk/tags"},{"title":"What gets captured","href":"/sdk/what-gets-captured"},{"title":"Context across threads","href":"/sdk/context-across-threads"},{"title":"Configuration reference","href":"/sdk/configuration"}]},{"group":"Server","pages":[{"title":"Install and run the server","description":"Build the vexlo binary from a checkout, start the Vexlo server, and check its configuration and retention arithmetic before it takes traffic.","href":"/server/install"},{"title":"Authentication and tokens","href":"/server/authentication"},{"title":"The relay daemon","description":"Run vexlo relay run on each host: what it reads from the spool directory, what it sends to the server, and how to watch and reload it.","href":"/server/relay-daemon"},{"title":"Capture policy","href":"/server/capture-policy"}]},{"group":"API reference","pages":[{"title":"Conventions","href":"/api/conventions"},{"title":"Traces","href":"/api/traces"},{"title":"Functions","href":"/api/functions"},{"title":"Tags, coverage, health","href":"/api/tags-coverage-health"},{"title":"Ingest","href":"/api/ingest"},{"title":"Identity and bootstrap","href":"/api/identity"}]},{"group":"MCP server","pages":[{"title":"Install and configure","href":"/mcp/install"},{"title":"Startup checks and exit codes","href":"/mcp/startup-and-exit-codes"},{"title":"Find","href":"/mcp/tools/find"},{"title":"Shape","href":"/mcp/tools/shape"},{"title":"Values","href":"/mcp/tools/values"},{"title":"Debugging with an agent","href":"/mcp/debugging-with-an-agent"},{"title":"Errors, rate limits, retries","href":"/mcp/errors-and-rate-limits"},{"title":"Token costs","href":"/mcp/token-costs"}]},{"group":"Reference","pages":[{"title":"Glossary","href":"/glossary"}]}]},"basePath":"","title":"Authentication and tokens - Vexlo","buildId":"dpl_8LJbrAiZvr1K98WQNZXCyY5AvT34","deploymentId":"dpl_8LJbrAiZvr1K98WQNZXCyY5AvT34","dynamics":[],"chunks":[]}