Tapir is browser-only. Every entry point sits behind an OIDC session, so a chat session or agent cannot submit a video or read a transcript without a human driving a browser. This blocks the API and MCP work in the follow-up issues.
Scope
Per-user API tokens, resolved to the same user identity as an OIDC session, so downstream handlers keep working against RLS unchanged.
New migration 017_api_tokens: id, user_id, name, token_hash, created_at, last_used_at, revoked_at. RLS on, user-scoped.
Store only a hash (argon2id or bcrypt). Plaintext shown once at creation, never retrievable.
Middleware: Authorization: Bearer <token> resolves to the user identity. Fail-closed — an invalid, revoked, or malformed token is 401 with no user context, never a fallthrough to anonymous.
Account page: create, list (name + created + last used, never the token), revoke.
Session auth and token auth are alternatives on the same handler chain, not layered.
Non-goals
Scopes/permissions. One token = full access for that user. Revisit when a second consumer exists.
Machine/service accounts.
Token expiry (revocation only for now).
Acceptance
caveman: me authenticate API caller as a user, not grant anonymous access
Red-first: middleware tests for valid, invalid, revoked, missing, and malformed tokens before implementation
RLS tests confirm a token-authenticated request sees exactly the same rows as that user's session
Token plaintext appears in exactly one response body and is never logged
Account page flows covered
docs/use-cases/api_access.feature added
Handoff
Branch from main, one file per commit, squash-merge PR back to main, human review gate.
## Problem
Tapir is browser-only. Every entry point sits behind an OIDC session, so a chat session or agent cannot submit a video or read a transcript without a human driving a browser. This blocks the API and MCP work in the follow-up issues.
## Scope
Per-user API tokens, resolved to the same user identity as an OIDC session, so downstream handlers keep working against RLS unchanged.
- New migration `017_api_tokens`: `id`, `user_id`, `name`, `token_hash`, `created_at`, `last_used_at`, `revoked_at`. RLS on, user-scoped.
- Store only a hash (argon2id or bcrypt). Plaintext shown once at creation, never retrievable.
- Middleware: `Authorization: Bearer <token>` resolves to the user identity. Fail-closed — an invalid, revoked, or malformed token is 401 with no user context, never a fallthrough to anonymous.
- Account page: create, list (name + created + last used, never the token), revoke.
- Session auth and token auth are alternatives on the same handler chain, not layered.
## Non-goals
- Scopes/permissions. One token = full access for that user. Revisit when a second consumer exists.
- Machine/service accounts.
- Token expiry (revocation only for now).
## Acceptance
- [ ] `caveman: me authenticate API caller as a user, not grant anonymous access`
- [ ] Red-first: middleware tests for valid, invalid, revoked, missing, and malformed tokens before implementation
- [ ] RLS tests confirm a token-authenticated request sees exactly the same rows as that user's session
- [ ] Token plaintext appears in exactly one response body and is never logged
- [ ] Account page flows covered
- [ ] `docs/use-cases/api_access.feature` added
## Handoff
Branch from `main`, one file per commit, squash-merge PR back to `main`, human review gate.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
Tapir is browser-only. Every entry point sits behind an OIDC session, so a chat session or agent cannot submit a video or read a transcript without a human driving a browser. This blocks the API and MCP work in the follow-up issues.
Scope
Per-user API tokens, resolved to the same user identity as an OIDC session, so downstream handlers keep working against RLS unchanged.
017_api_tokens:id,user_id,name,token_hash,created_at,last_used_at,revoked_at. RLS on, user-scoped.Authorization: Bearer <token>resolves to the user identity. Fail-closed — an invalid, revoked, or malformed token is 401 with no user context, never a fallthrough to anonymous.Non-goals
Acceptance
caveman: me authenticate API caller as a user, not grant anonymous accessdocs/use-cases/api_access.featureaddedHandoff
Branch from
main, one file per commit, squash-merge PR back tomain, human review gate.