All HTTP APIs are served from https://app.threadline.to (self-hosted deployments use your own origin).
Authentication is via a Bearer API key tied to an agent, unless noted otherwise.
Download the Postman collection:
threadline.postman_collection.json
Every request must include:
Authorization: Bearer tl_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Content-Type: application/jsonKeys are created in the developer dashboard. Threadline stores a SHA-256 hash of each key and never returns the key again after creation.
If authentication fails, you'll receive:
{ "error": "Invalid API key.", "code": "UNAUTHORIZED" }Grants and extraction use these spec scope names (not storage column names). Each scope controls one slice of context an agent may read or write:
preferences — user tooling, environment, and workflow choices (editor, deploy target, branching strategy).goals — current projects, tasks, blockers, and priorities.knowledge — skills, tools, languages, and frameworks.history — past decisions, completed actions, and durable conversation-history signals.relationships — named people and relationship context.communication_style — standing instructions for how the assistant should reply (tone, format, verbosity).general — core identity only: name, location, and timezone.On first inject or update, Threadline auto-creates a grant for the agent using the scopes allowed on that agent's API key.
GET
/api/context/[userId]
Fetch the structured context object for a user. Requires an active grant between your agent and the user.
curl -sS https://app.threadline.to/api/context/USER_UUID \
-H "Authorization: Bearer tl_live_..."{
"userId": "USER_UUID",
"communication_style": "terse, bullet points",
"ongoing_tasks": [{ "value": "Ship onboarding flow", "source_type": "observed" }],
"key_relationships": [{ "value": "PM: Sam", "source_type": "observed" }],
"domain_expertise": [{ "value": "TypeScript", "source_type": "observed" }],
"raw_history": [],
"preferences": { "editor": { "value": "VS Code", "source_type": "observed" } },
"general": { "location": { "value": "Delhi", "source_type": "observed" } },
"last_updated": "2026-03-12T10:00:00.000Z"
}DELETE
/api/context/[userId]
Permanently delete a user's stored context and revoke all grants for that user.
curl -sS -X DELETE https://app.threadline.to/api/context/USER_UUID \
-H "Authorization: Bearer tl_live_..."{ "deleted": true }POST
/api/context/inject
Returns a prompt string with context intelligently injected for a given user.
curl -sS https://app.threadline.to/api/context/inject \
-H "Authorization: Bearer tl_live_..." \
-H "Content-Type: application/json" \
-d '{"userId":"USER_UUID","basePrompt":"You are a helpful assistant."}'{
"userId": "USER_UUID",
"basePrompt": "You are a helpful assistant.",
"agentId": "AGENT_UUID"
}agentId is optional.
{
"injectedPrompt": "System:\nRespond in terse answers, suitable for skimming.\n\nYou are a helpful assistant.",
"cacheHint": {
"recommended": true,
"retention": "24h",
"reason": "Prompt contains persistent user context — cache for 24h to reduce token costs by ~50%",
"openaiParam": { "prompt_cache_retention": "24h" }
}
}{
"injectedPrompt": "You are a helpful assistant.",
"cacheHint": {
"recommended": false,
"retention": "24h",
"reason": "No Threadline context was merged into this prompt; caching is optional and may have less benefit.",
"openaiParam": { "prompt_cache_retention": "24h" }
},
"reason": "empty",
"notice": "No context stored for this userId yet. Call update() first."
}Inject response fields:
| Field | Meaning / what to do |
|---|---|
| injectedPrompt | Use as your system prompt. When context exists, Threadline prepends System:\n... before your basePrompt. |
| cacheHint | OpenAI prompt-cache hint. Apply cacheHint.openaiParam when cacheHint.recommended is true. |
| reason | Present on non-enriched responses. See values below. |
| notice | Human-readable explanation of reason. Safe to log or surface in dev tools. |
| seeded | true when demo facts were injected before the developer's first write. Call update() to replace them. |
reason values:
no_grant — no grant for this user/agent. Create a grant or call inject/update to auto-create one.no_scopes — grant exists but effective scopes are empty. Check the agent's allowed scopes.empty — no stored context yet. Call update() after the user shares facts.empty_capsule — context exists but nothing matched granted scopes. Check scopes or run another update().seeded_demo — demo context shown before first write (seeded: true). Expected on first inject for new developers.POST
/api/context/update
Updates a user's context based on a single interaction.
curl -sS https://app.threadline.to/api/context/update \
-H "Authorization: Bearer tl_live_..." \
-H "Content-Type: application/json" \
-d '{"userId":"USER_UUID","userMessage":"Keep answers terse, I skim.","agentResponse":"Understood."}'{
"userId": "USER_UUID",
"userMessage": "Keep answers terse, I skim.",
"agentResponse": "Understood.",
"agentId": "AGENT_UUID"
}agentId is optional.
{
"updated": true,
"delta": {
"communication_style": "keeps answers terse, prefers skimming"
}
}{ "updated": true, "delta": {} }{
"updated": true,
"delta": {},
"denied_scopes": ["knowledge"],
"notice": "Facts were extracted for scopes this agent was not granted. They were not stored. Grant the scope to store them."
}Update response fields:
updated — always true on HTTP 200 (including when delta is empty).delta — storage-keyed facts written this turn. Empty when Stage 1 filtered the message or nothing new was extracted.denied_scopes — spec scope names the model extracted but your agent is not granted. Facts were dropped.notice — human-readable explanation when scopes were denied.limitReached — on HTTP 200 with monthly quota exceeded: { "updated": false, "limitReached": true }.POST
/api/grants
Create a scoped, user-held grant allowing an agent to read and write that user's context. Grants do not auto-expire; revoke them from the user's trust dashboard or via DELETE /api/grants/[grantId].
Auth: Authorization: Bearer ... (API key must belong to agentId), or a signed-in dashboard session.
curl -sS https://app.threadline.to/api/grants \
-H "Authorization: Bearer tl_live_..." \
-H "Content-Type: application/json" \
-d '{"userId":"USER_UUID","agentId":"AGENT_UUID","scopes":["communication_style","goals"]}'{
"userId": "USER_UUID",
"agentId": "AGENT_UUID",
"scopes": ["communication_style", "goals"]
}{
"grant": {
"id": "GRANT_UUID",
"user_id": "USER_UUID",
"agent_id": "AGENT_UUID",
"scopes": ["communication_style", "goals"],
"expires_at": null,
"revoked": false
}
}DELETE
/api/grants/[grantId]
Revoke an existing grant by ID. Requires the end user's signed-in session (trust dashboard), not an agent API key.
curl -sS -X DELETE https://app.threadline.to/api/grants/GRANT_UUID \
-H "Cookie: ..."{ "revoked": true }GET
/api/health
Returns service health. No authentication required.
curl -sS https://app.threadline.to/api/health{
"status": "ok",
"checks": { "supabase": "ok", "redis": "ok", "openai": "ok" },
"timestamp": "2026-07-29T10:00:00.000Z"
}Threadline returns structured errors:
{ "error": "Message", "code": "ERROR_CODE" }Common codes:
| Code | HTTP | What it means / fix |
|---|---|---|
| BAD_REQUEST | 400 | Invalid params or payload. Fix your JSON/body types and required fields. |
| UNAUTHORIZED | 401 | Missing/invalid API key. Ensure Authorization: Bearer ... is set. |
| FORBIDDEN | 403 | No active grant for this user/agent, or the API key does not match the requested agent. |
| NOT_FOUND | 404 | Resource not found. Verify IDs (userId, agentId, grantId) and that the resource exists. |
| RATE_LIMITED | 429 | Too many requests. Back off and retry after X-RateLimit-Reset. |
| OPENAI_ERROR | 502 | Model integration failed. Retry, then check your OpenAI key / model availability. |
| INTERNAL_ERROR | 500 | Unexpected server error. Retry; if persistent, contact support@threadline.to. |
Threadline applies soft rate limits per API key:
If you hit a limit, you'll receive:
{ "error": "Rate limit exceeded.", "code": "RATE_LIMITED" }Enterprise and high-throughput workloads can request higher limits and dedicated capacity.