Skip to content
Reference

HTTP API

REST API for managing your workspace, agents, content, and mailbox. All endpoints require authentication via bearer token.

Authentication

All requests require an Authorization header with a bearer token. LeftFold accepts two token types:

  • API keysk_ prefix, created in the dashboard or via CLI
  • OAuth JWT — from Supabase Auth (magic link, Google, or GitHub)
curl https://your-project.supabase.co/functions/v1/http-api/workspace \
  -H "Authorization: Bearer sk_your_api_key"

The API key resolves to a user, which resolves to a workspace. All queries are scoped to that workspace.


Workspace

GET/workspace

Returns the authenticated user's workspace.

GET/projects

List all projects in the workspace.

POST/projects

Create a new project.

{
  "name": "Blog",
  "slug": "blog",
  "description": "Company blog content"
}

Aggregates

POST/aggregates/define

Register a new aggregate type with traits and field definitions.

{
  "type_name": "treatment",
  "display_name": "Treatment",
  "traits": ["content-bearing", "publishable"],
  "fields": [
    { "name": "name", "type": "string", "required": true },
    { "name": "duration_minutes", "type": "number" }
  ]
}
GET/aggregate-types/:type_name/schema

Fetch the full schema for an aggregate type (fields, traits, event schemas).

POST/aggregates/query

Run a fold pipeline query. Supports $match, $fold, $project (with computed expressions), $group, $sort, $limit, $lookup.

{
  "pipeline": [
    { "$match": { "aggregate_type": "article" } },
    { "$fold": {} },
    { "$project": {
      "title": 1,
      "days_since_update": { "$daysSince": "$_last_event_at" }
    }},
    { "$sort": { "_last_event_at": -1 } },
    { "$limit": 10 }
  ]
}
GET/aggregates/:aggregate_id/events

Fetch the raw event stream for an aggregate (not folded).

POST/aggregates/:aggregate_id/events

Append an event to an aggregate. Triggers the enrichment pipeline and knowledge graph projection.

{
  "aggregate_type": "article",
  "event_type": "article.created",
  "project_id": "p1r2o3j-...",
  "payload": {
    "title": "Getting Started with LeftFold",
    "body": "# Introduction\n...",
    "slug": "getting-started"
  },
  "metadata": { "source": "lab" }
}

POST/aggregates/search

Semantic search across the knowledge graph. Searches aggregate labels by text similarity.

{
  "query": "ACL rehabilitation",
  "type": "article",
  "project_id": "p1r2o3j-...",
  "limit": 5
}

Agents

GET/agents

List all agents. Optionally filter by status with ?status=active.

POST/agents

Register a new external agent.

{
  "name": "lead",
  "description": "Lead agent for orchestrating research tasks",
  "subscribed_traits": ["content-bearing"],
  "capabilities": { "research": true }
}
GET/agents/:agent_id/card

Fetch an agent's card metadata (name, description, capabilities, traits).

GET/agents/roster

Returns pending, unread, and in-progress task counts per agent. Useful for building agent dashboards and monitoring workload.


Relay

These endpoints power the @leftfold/agents package. You do not need to call them directly unless building a custom agent client.

POST/agents/connect

Exchange an API key for Realtime connection credentials. Returns the WebSocket URL, anon key, channel names for each agent, and heartbeat interval.

Response
{
  "url": "wss://your-project.supabase.co/realtime/v1",
  "anon_key": "eyJ...",
  "channels": [
    {
      "agent_id": "a1b2c3d4-...",
      "name": "summarizer",
      "channel": "agent:a1b2c3d4-..."
    }
  ],
  "heartbeat_interval": 30000
}
POST/agents/heartbeat

Update last_seen_at for connected agents. The @leftfold/agents package calls this every 30 seconds.

{
  "agent_ids": ["a1b2c3d4-...", "e5f6g7h8-..."]
}

Articles

GET/articles

List articles. Supports ?project_id=, ?limit=, and ?cursor= for pagination.

GET/articles/:aggregate_id

Get a single article by aggregate ID.

POST/articles

Create an article. This appends a creation event to the event store, projects it, updates the knowledge graph, and dispatches to matching agents.

{
  "project_id": "p1r2o3j-...",
  "title": "Getting Started with LeftFold",
  "body": "# Introduction\n\nLeftFold is a content operations platform...",
  "slug": "getting-started-with-leftfold",
  "tags": ["tutorial", "onboarding"]
}
POST/articles/:aggregate_id/publish

Publish an article. Triggers publishable-trait agents (structured_data, linker).


Files

POST/files

Upload a file (base64-encoded in JSON body). Validates against workspace storage quota. Optionally associates with an aggregate.

{
  "filename": "hero.png",
  "data": "iVBORw0KGgo...",
  "mime_type": "image/png",
  "aggregate_id": "a1b2c3d4-...",
  "aggregate_type": "article",
  "project_id": "p1r2o3j-..."
}
GET/files/:path

Generate a signed download URL (valid for 1 hour).

GET/files

List files. Filter by ?prefix= (workspace/project/type/aggregate path).

POST/files/delete

Delete a file by path. Pass { "path": "..." } in the body.


Mailbox

GET/mailbox

List mailbox tasks. Filter with query parameters: ?status=pending, ?assignee_id=, ?assignee_type=agent, ?project_id=, ?source_type=.

POST/mailbox

Create a new mailbox task. Supports idempotency — if idempotency_key matches an existing task, returns the existing task instead of creating a duplicate.

{
  "title": "Review this draft",
  "assignee_type": "agent",
  "assignee_id": "a1b2c3d4-...",
  "priority": "high",
  "project_id": "p1r2o3j-...",
  "metadata": { "type": "review_request", "draft_id": "..." },
  "idempotency_key": "review:draft_abc:v1"
}
GET/mailbox/:task_id

Get a task with its messages and artifacts.

Actions

Eight interaction endpoints are available on each task:

EndpointAction
POST /mailbox/:id/approveAccept the agent's output
POST /mailbox/:id/rejectReject with optional reason
POST /mailbox/:id/editAccept with modifications
POST /mailbox/:id/deferSet aside for later
POST /mailbox/:id/askAsk the agent a follow-up question
POST /mailbox/:id/assignReassign to a different agent or user
POST /mailbox/:id/readMark as read
POST /mailbox/:id/unreadMark as unread

Errors

All errors return a JSON body with an error field:

404 Not Found
{
  "error": "agent 'conductor' not found in this workspace"
}
StatusMeaning
400Bad request — missing required fields or invalid input
401Missing or invalid bearer token
402Payment required — scope needs an active subscription
403Forbidden — token does not have the required scope
404Resource not found
409Conflict — task not in expected state
500Internal server error