REST API for programmatic access to boop lists and items. Designed for agents, scripts, and integrations to interact without using a browser.
https://<convex-deployment>.convex.site
Two auth modes are supported:
JWT session (for interactive/browser use) via the Authorization header:
Authorization: Bearer <your-jwt-token>
To obtain a JWT token, use the standard auth flow:
POST /auth/initiatewith{ "email": "your@email.com" }POST /auth/verifywith{ "sessionId": "...", "code": "..." }(OTP from email)- Use the returned
tokenin subsequent requests
API key (for unattended agents) via the X-API-Key header:
X-API-Key: pa_live_...
API keys are long-lived credentials scoped to specific actions. Mint one with a
JWT session (see Agent API v1). When an X-API-Key header is
present it takes precedence over the Authorization header. Keys carry scopes
(lists:read, items:read, items:write); a request missing the required
scope returns 403. JWT sessions have full access.
The authenticated-boundary cutover distinguishes 401 (missing, invalid, expired,
or revoked credentials) from 403 (valid credentials without the required scope
or resource access). API-key consumers that previously treated every denial as
401 must handle both. Missing and inaccessible resources behind the HTTP write boundary return the
same 403 response to avoid disclosing private resource existence.
GET /api/agent/lists
Returns all lists the authenticated user has access to.
Response:
{
"lists": [
{
"_id": "abc123...",
"name": "Shopping List",
"ownerDid": "did:webvh:...",
"createdAt": 1704067200000,
"role": "owner"
}
]
}GET /api/agent/lists/:listId
GET /api/agent/lists/:listId/items
Returns a list and all its items.
Response:
{
"list": {
"_id": "abc123...",
"name": "Shopping List",
"ownerDid": "did:webvh:...",
"createdAt": 1704067200000,
"assetDid": "did:peer:..."
},
"items": [
{
"_id": "item123...",
"name": "Milk",
"checked": false,
"createdByDid": "did:webvh:...",
"createdAt": 1704067200000,
"order": 0,
"description": "2% organic",
"priority": "high"
}
],
"role": "owner"
}POST /api/agent/lists/:listId/items
Content-Type: application/json
{
"name": "Buy groceries",
"description": "From Whole Foods",
"priority": "high",
"dueDate": 1704153600000,
"url": "https://example.com"
}
Response (201 Created):
{
"itemId": "item456...",
"item": {
"_id": "item456...",
"name": "Buy groceries",
"checked": false,
"createdByDid": "did:webvh:...",
"description": "From Whole Foods",
"priority": "high",
"dueDate": 1704153600000,
"url": "https://example.com"
}
}PATCH /api/agent/items/:itemId
Content-Type: application/json
{
"checked": true,
"name": "Updated name",
"description": "Updated description",
"priority": "medium",
"dueDate": 1704240000000,
"url": "https://new-url.com"
}
All fields are optional. To clear a field, set it to null:
{
"priority": null,
"dueDate": null
}Response:
{
"success": true,
"item": {
"_id": "item123...",
"name": "Updated name",
"checked": true,
...
}
}DELETE /api/agent/items/:itemId
Response:
{
"success": true
}| Field | Type | Description |
|---|---|---|
name |
string | Item title (required for creation) |
description |
string | Optional notes/details |
checked |
boolean | Whether the item is complete |
priority |
"high" | "medium" | "low" | Priority level |
dueDate |
number | Unix timestamp in milliseconds |
url |
string | Link to PR, URL, or reference |
order |
number | Position in list (lower = higher) |
createdByDid |
string | DID of user who created the item |
checkedByDid |
string | DID of user who checked the item |
createdAt |
number | Creation timestamp |
checkedAt |
number | When item was checked |
| Role | Permissions |
|---|---|
owner |
Full access (read, write, delete, share) |
editor |
Read and write access |
viewer |
Read-only access |
Endpoints for unattended agents: mint API keys (JWT-only), then read and write
boop lists using either a JWT session or an X-API-Key.
Note: the Convex router has no
:parampath segments, so identifiers are passed as query parameters (?keyId=,?listId=) rather than path segments.
Key management always requires a JWT session — you cannot mint or revoke a key with a key.
POST /api/v1/keys— create a key.- body:
{ "label": "CI Agent", "scopes": ["lists:read","items:read","items:write"] } scopesis optional and defaults to["lists:read","items:read","items:write"].- response:
{ "id": "...", "key": "pa_live_...", "prefix": "pa_live_..." } - The raw
keyis returned once and never stored — save it immediately.
- body:
GET /api/v1/keys— list your active keys.- response:
{ "keys": [{ "id", "prefix", "label", "scopes", "createdAt" }] }(never the hash or raw key)
- response:
DELETE /api/v1/keys?keyId=<id>— revoke a key you own.- response:
{ "success": true }
- response:
GET /api/v1/lists— list the caller's lists (lists:read).- response:
{ "lists": [...] }
- response:
GET /api/v1/lists/items?listId=<id>— get a list and its items (items:read).- response:
{ "list": {...} | null, "items": [...] }
- response:
The existing mutation endpoints now also accept an X-API-Key. All require the
items:write scope (there is no lists:write scope yet):
POST /api/lists/create,POST /api/lists/deletePOST /api/items/add,POST /api/items/check,POST /api/items/uncheck,POST /api/items/remove,POST /api/items/reorder
Item writes made via an API key are attributed to the key's owner DID (or its
agentDid, if set). The item-authorship credential remains an unsigned
placeholder — cryptographic signing is out of scope for now.
lists:read— read listsitems:read— read itemsitems:write— create/modify/delete lists and items
All errors return JSON with an error field:
{
"error": "Error message here"
}| Status | Description |
|---|---|
| 400 | Bad request (missing/invalid parameters) |
| 401 | Unauthorized (missing/invalid token) |
| 403 | Forbidden (no access to resource) |
| 404 | Not found |
| 405 | Method not allowed |
| 500 | Server error |
# Get all lists
curl -H "Authorization: Bearer $TOKEN" \
https://your-deployment.convex.site/api/agent/lists
# Get a specific list with items
curl -H "Authorization: Bearer $TOKEN" \
https://your-deployment.convex.site/api/agent/lists/abc123xyz
# Add an item
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "New task", "priority": "high"}' \
https://your-deployment.convex.site/api/agent/lists/abc123xyz/items
# Check off an item
curl -X PATCH \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"checked": true}' \
https://your-deployment.convex.site/api/agent/items/item123xyz
# Delete an item
curl -X DELETE \
-H "Authorization: Bearer $TOKEN" \
https://your-deployment.convex.site/api/agent/items/item123xyzconst BASE_URL = "https://your-deployment.convex.site";
const TOKEN = "your-jwt-token";
// Get all lists
const lists = await fetch(`${BASE_URL}/api/agent/lists`, {
headers: { Authorization: `Bearer ${TOKEN}` }
}).then(r => r.json());
// Add an item
const newItem = await fetch(`${BASE_URL}/api/agent/lists/${listId}/items`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ name: "New task", priority: "high" })
}).then(r => r.json());
// Check off an item
await fetch(`${BASE_URL}/api/agent/items/${itemId}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ checked: true })
});Authenticated direct operations require authToken (the JWT from login) or an appropriately scoped apiKey. Browser/native clients first call actorSession.establish({ authToken }) so logout and expiry invalidate reactive subscriptions. HTTP clients continue sending Bearer/cookie JWT or X-API-Key; the HTTP adapter establishes existing valid sessions automatically.
Do not supply acting DIDs. Ownership, attribution and legacy-account access are resolved from authenticated server records. Old optional identity fields are compatibility checks only and never grant access. Anonymous access is limited to explicitly public resources with active publications; shared writes require authentication.
See authentication rollout for the required deployed-version confirmation and coordinated client/backend cutover.