feat(agent-api): API keys + read endpoints so agents can use boop over HTTP - #212
Merged
Conversation
Key format/prefix/SHA-256-hash/scope helpers plus an assert-based test following the scripts/*.test.mjs esbuild pattern. Only hashes are stored. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the apiKeys schema table (by_hash/by_owner), Convex CRUD functions, a Convex-side copy of the pure helpers, and JWT-only HTTP handlers to create/list/revoke keys. Raw key is returned once; only the hash persists. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds resolveActor/requireScope (X-API-Key or JWT) and swaps the inline requireAuth+getUserByTurnkeyId block in item/list write handlers for it. Writes require items:write; JWT sessions resolve to scopes ["*"]. Drops legacyDid threading from the new path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GET /api/v1/lists and GET /api/v1/lists/items (query-param listId, since the Convex router has no :param segments), plus route wiring for the /api/v1/keys and read endpoints with OPTIONS pairs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the X-API-Key auth mode and the real /api/v1/keys + read endpoints; removes the unbuilt Mission Control REST v1 section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…egacyDid, CORS - Read endpoints: gate GET /api/v1/lists/items behind a new authorized query getListWithItemsForViewer (canUserViewList: owner/legacy or active publication), 404 on denied — fixes IDOR letting any items:read key read arbitrary lists. - API keys: sanitizeScopes() strips "*"/unknown scopes at creation so a caller cannot mint a wildcard key. - Migrated users: resolveActor carries legacyDid on the JWT path only and write handlers forward it again; API keys stay legacy-free. - CORS: allow GET/DELETE methods and the X-API-Key header for browser callers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ot agent Second Macroscope review round: - Make convex/apiKeys.ts fns internalQuery/internalMutation — they were public, so a client could call createKey directly with any ownerDid (mint a key for any user) or probe getByHash. Reached only via internal.* from the JWT-authed layer. Same for the server-only getListWithItemsForViewer read query. - Use actor.did (owner) for ownership/authorization in createList + all item/list writes; drop actorDid from ResolvedActor so an agent key can't own/authorize as the agent identity. Distinct attribution is deferred to Step 5 (needs mutation changes to split authz from attribution). - CORS: fix convex/http.ts's OWN getCorsHeaders (used by the OPTIONS/corsHandler path) — the earlier fix only touched lib/httpResponses.ts. - Tidy a pre-existing unused-args lint error in the health-check handler. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- revokeKey returns boolean; revokeApiKey answers 404 for a missing/non-owned key instead of surfacing it as a 500 (Macroscope medium). - corsHandler (the OPTIONS preflight handler the v1 routes actually use) hardcoded its own POST,OPTIONS headers separate from getCorsHeaders — the real reason preflight was still broken. Reuse getCorsHeaders so methods/headers live in one place (GET/POST/DELETE + X-API-Key). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eaking stack traces Two issues that would have broken landing this branch: - `convex deploy` failed schema validation: deployments still hold orphaned rows from the Mission Control `apiKeys` table removed in 90b6d7b (old shape has `keyPrefix`, no `prefix`). The new table is now `agentApiKeys`; the dead rows stay unvalidated and can be dropped separately. - `convex/_generated/api.d.ts` was stale, so `npx tsc -b` and `bun run build` failed on `internal.apiKeys.*`. Regenerated and committed. Also: every *Http.ts catch echoed `error.message`, so a mutation authorization throw returned 500 with internal file paths. `handlerErrorResponse()` maps those to a clean 403 and everything else to a generic 500 (details still logged). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
boop repositioned agent-first (business plan + agent landing page), but agents couldn't actually run against the stack: the only credential was a short-lived email-OTP JWT (no durable auth for unattended agents), and every
/api/*route was a POST mutation (no way to read the queue). The full Mission Control layer that once covered this was deliberately removed in90b6d7b.This PR adds the minimal agent-access slice — API keys + read endpoints — so agents can use the current stack without resurrecting ~5,900 lines of Mission Control. It also makes
API.mdhonest.What's included
convex/apiKeys.ts,convex/apiKeysHttp.ts,apiKeystable): JWT-onlyPOST/GET/DELETE /api/v1/keys. Long-livedpa_live_…keys; only the SHA-256 hash is stored, the raw key is returned exactly once on creation. Revoke enforces ownership.convex/lib/actor.ts):resolveActor()accepts either anX-API-Keyheader or the existing JWT session and resolves both to a current DID + scope set. The JWT path is unchanged (sessions getscopes: ["*"]).convex/itemsHttp.ts,convex/listsHttp.ts): every write handler now goes throughresolveActor+requireScope("items:write"). Mutation authorization logic is untouched.convex/agentReadHttp.ts):GET /api/v1/lists,GET /api/v1/lists/items?listId=— wrap existing queries, require read scopes.src/lib/apiKeys.ts,scripts/api-keys.test.mjs): key gen/hash/scope logic, tested in the repo'sscripts/*.test.mjsassert style.API.md): documents the realX-API-Keymode + v1 endpoints; removes the documented-but-nonexistent Mission Control section.plans/): the advisor plan this was built from, for context.tsc -bandbun run buildrequireconvex codegento regenerate_generated/api.d.tswith the newapiKeysmodule first. Without it there are exactly 4Property 'apiKeys' does not existerrors (stale generated types) — no other type errors. Runnpx convex dev --once(or ensure the build pipeline runs codegen with Convex credentials) before/at merge. Verified locally: eslint clean, unit tests pass, allapi.*references resolve to real exports.Not in this PR (deliberate follow-ups)
apiKeys.agentDidcolumn +actor.actorDidplumbing exist, but item mutations don't yet stamp a distinct agent DID — so agent actions currently record the owner's DID. Needed to make the "prove human-vs-AI" claim real.convex/items.ts), a separate DID-signing follow-up.GET/DELETE/X-API-Keyroutes aren't in the preflight allowlist — fine for server-side agents, needs updating for browser calls.API.mddrift: an older section still documents/api/agent/*routes that don't exist.Test plan
bun scripts/api-keys.test.mjs→ assertions passbun testfull suite → pass; eslint clean on changed filesnpx convex dev --once && npx tsc -b && bun run build→ green (needs Convex creds)🤖 Generated with Claude Code
Note
Add API key management and read endpoints so agents can authenticate over HTTP
agentApiKeystable and CRUD endpoints (POST/GET/DELETE /api/v1/keys) for minting, listing, and revoking API keys; raw key is returned once and never stored — only a SHA-256 hash and prefix are persisted.GET /api/v1/listsandGET /api/v1/lists/items?listId=...read endpoints gated bylists:readanditems:readscopes.resolveActorin lib/actor.ts to unify auth: handlers now accept either a JWT session or anX-API-Keyheader, withrequireScopeenforcing per-endpoint scope requirements./api/items/*,/api/lists/*) to also accept API keys withitems:writescope, replacing the previous JWT-only flow.GET,DELETE, andX-API-Keyacross all routes, which may affect security posture for browser clients.📊 Macroscope summarized 2a6c93b. 9 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
🗂️ Filtered Issues
No issues evaluated.