Documentation

Call Framed from your own stack.

Protocol 2026-08-21

Start

You call Framed from your own stack. You talk to a customer workspace without opening the Framed UI.

Mail is another door into the same chat. A workspace member can email the workspace address on mail.framed.dev; Framed answers in the thread and in Chat. Slack and Teams can use this API later as more doors into that conversation.

You call only https://www.framed.dev/api. Every workspace, memory, chat, Studio, agent, job, insight, and approval action on this page uses that host and a short path. You can create and read those objects from your own stack. You cannot approve a customer write from this API.

The platform objects you drive are:

  • Workspace: one customer environment.
  • Memory: governed facts the workspace holds. You read and write them at /context.
  • Chat: a thread that runs the same Framed chat path.
  • Studio item: a memo, report, or presentation, plus its public share URL.
  • Agent: an installed runner on that workspace. You list agents, then start them as jobs.
  • Job: one agent run, or one memory write that waits for approval.
  • Insight: the briefing a job leaves behind.
  • Approval: a write that still waits in Insights → To review.

Key

Request a developer account and API key by emailing partner-support@framed.dev.

After you have a key, copy it once. Framed stores only a hash. Send the key on every call in one of these headers:

Authorization: Bearer frk_live_…
X-Framed-Partner-Key: frk_live_…

Either header works. You do not need a Framed browser session. A key can be pinned to one workspace. Without a pin, the key only reaches workspaces it is allowed to see.

First call

Check that the API is up first.

curl https://www.framed.dev/api/health

Then ask which workspaces this key can see.

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/whoami

GET /workspaces returns the same list. The sections below explain each concept, then the calls. The Reference lists every method with a working curl.

Workspaces

A workspace is one customer environment. It holds memory, chats, Studio items, and installed agents. Your key only reaches workspaces it is allowed to see.

List the workspaces this key can see.

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/workspaces

Get one workspace.

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/workspaces/WORKSPACE_ID

If you want a new workspace on an account this key can use, send POST /workspaces. If the key can see more than one account, include agency_id.

curl -X POST https://www.framed.dev/api/workspaces \
  -H "Authorization: Bearer frk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"name":"Northwind"}'

A created workspace looks like this.

{
  "ok": true,
  "workspace": {
    "id": "WORKSPACE_ID",
    "name": "Northwind",
    "agency_id": "ACCOUNT_ID",
    "status": "draft"
  }
}

If this key is pinned to one workspace, create returns 403. If the account is at its workspace limit, create returns 403 with code: "limit_reached".

Memory

Memory is the governed facts a workspace holds. Chat, Studio, and agents read it. You read and write memory through /context.

Read the current memory on a workspace.

curl -H "Authorization: Bearer frk_live_…" \
  "https://www.framed.dev/api/context?workspace_id=WORKSPACE_ID"

If you want to add or correct a memory fact, send POST /context.

curl -X POST https://www.framed.dev/api/context \
  -H "Authorization: Bearer frk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"workspace_id":"WORKSPACE_ID","fact":"Debtor 123 is on hold until 2026-09-01"}'

The response is 202. You get a job_id and an approval_id. Status is awaiting_approval until someone decides in Framed. The fact is not stored yet.

If you want to replace an existing fact, send that fact’s id as replace_id. The replacement still waits for approval.

Chats

A chat is a thread on a workspace. You start a thread, send the next message, or read what is already there. The same Framed chat path runs the turn. Writes that change memory still wait in Insights → To review.

List chats on a workspace.

curl -H "Authorization: Bearer frk_live_…" \
  "https://www.framed.dev/api/chats?workspace_id=WORKSPACE_ID"

Start a chat and send the first message.

curl -X POST https://www.framed.dev/api/chats \
  -H "Authorization: Bearer frk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"workspace_id":"WORKSPACE_ID","message":"What do we know about Debtor 123?"}'

Continue a thread, then read it.

curl -X POST https://www.framed.dev/api/chats/CHAT_ID/messages \
  -H "Authorization: Bearer frk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"message":"Summarize the last answer."}'

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/chats/CHAT_ID

If the turn is still running after 45 seconds, the response is 202 with status: "accepted". Poll GET /chats/:id for the finished messages.

Studio

A Studio item is a draft document on a workspace: a memo, a report, or a presentation. You create it, list items, get one item, and fetch its public share URL.

List Studio items on a workspace.

curl -H "Authorization: Bearer frk_live_…" \
  "https://www.framed.dev/api/studio?workspace_id=WORKSPACE_ID"

Create a draft. Generation runs in the background. Poll GET /studio/:id until the body is filled.

curl -X POST https://www.framed.dev/api/studio \
  -H "Authorization: Bearer frk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"workspace_id":"WORKSPACE_ID","title":"Q3 brief","prompt":"Write a one-page memo","type":"memo"}'

Get one item, then fetch the public share URL.

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/studio/STUDIO_ID

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/studio/STUDIO_ID/share

share_url is the live link on https://www.framed.dev/share/…. If a frozen publication exists, publication_url points at /p/….

Agents

An agent is a runner already installed on a workspace. You do not install agents from this API. You list the ones that are there, then start one as a job.

List installed agents on a workspace.

curl -H "Authorization: Bearer frk_live_…" \
  "https://www.framed.dev/api/agents?workspace_id=WORKSPACE_ID"

Each row gives you the slug you send to POST /jobs.

{
  "ok": true,
  "workspace_id": "WORKSPACE_ID",
  "agents": [
    {
      "slug": "news_tracker",
      "name": "News tracker",
      "description": "Watch the topics this workspace follows",
      "status": "active",
      "kind": "catalog"
    }
  ]
}

If an agent is paused, do not start it until someone turns it on in Framed.

Jobs

A job runs an agent that is already installed on that workspace. Writes the agent proposes still go through the same approval queue.

If you do not know the slug, call GET /agents?workspace_id= first.

curl -X POST https://www.framed.dev/api/jobs \
  -H "Authorization: Bearer frk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"workspace_id":"WORKSPACE_ID","agent_slug":"news_tracker"}'

Poll status with GET /jobs/:id, or list jobs with GET /jobs?workspace_id=WORKSPACE_ID.

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/jobs/JOB_ID

curl -H "Authorization: Bearer frk_live_…" \
  "https://www.framed.dev/api/jobs?workspace_id=WORKSPACE_ID"

When the job succeeds, it often returns an insight_id. Fetch that briefing with GET /insights/:id.

Optional HTTPS webhook (per request or stored on the key). Framed POSTs metadata only, never the fact body.

{
  "event": "job.status",
  "api_version": "2026-08-21",
  "job_id": "…",
  "workspace_id": "…",
  "kind": "context_write",
  "status": "awaiting_approval"
}

When the call used an API key, the body is signed as X-Framed-Signature: sha256=… (HMAC-SHA256 of the raw body, key = the API key). Treat the webhook as a ping and confirm with GET /jobs/:id.

Insights

An insight is a briefing a job or agent leaves on a workspace. You read insights. You do not create or dismiss them from this API.

List recent insights on a workspace.

curl -H "Authorization: Bearer frk_live_…" \
  "https://www.framed.dev/api/insights?workspace_id=WORKSPACE_ID"

Get one insight, including the markdown body.

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/insights/INSIGHT_ID

List rows omit content_md. The detail call includes it. Use the list to find an id, then fetch the body.

Approvals

If a write changes what a workspace holds, Framed does not store it immediately. Framed puts the write on Insights → To review.

Why: a customer workspace must not change silently. The customer stays the owner of what the workspace holds. Someone at the customer approves or rejects it. The fact lands only after approval.

Memory writes through POST /context always wait. Chat and agent writes that change memory use the same queue. Approve & save runs the memory write. Dismiss or Deviation rejects the job. There is no silent write path.

You can read the queue. You cannot approve or reject from this API. Someone at the customer decides in Framed.

curl -H "Authorization: Bearer frk_live_…" \
  "https://www.framed.dev/api/approvals?workspace_id=WORKSPACE_ID"

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/approvals/APPROVAL_ID

Reference

Methods

GET /health, GET /whoami, GET /workspaces, POST /workspaces, GET /workspaces/:id, GET /context?workspace_id=, POST /context, GET /chats?workspace_id=, POST /chats, GET /chats/:id, POST /chats/:id/messages, GET /studio?workspace_id=, POST /studio, GET /studio/:id, GET /studio/:id/share, GET /agents?workspace_id=, POST /jobs, GET /jobs, GET /jobs/:id, GET /insights?workspace_id=, GET /insights/:id, GET /approvals?workspace_id=, GET /approvals/:id.

MethodPathAuthResponse
GET/healthNo200 API status
GET/whoamiYesAccount and workspaces
GET/workspacesYesWorkspace list
POST/workspacesYes201 created workspace
GET/workspaces/:idYesOne workspace
GET/context?workspace_id=YesMemory facts
POST/contextYes202 awaiting approval
GET/chats?workspace_id=YesChat list
POST/chatsYesStarted thread
GET/chats/:idYesThread and messages
POST/chats/:id/messagesYesContinued thread
GET/studio?workspace_id=YesStudio list
POST/studioYes202 generating
GET/studio/:idYesOne Studio item
GET/studio/:id/shareYesPublic share URL
GET/agents?workspace_id=YesInstalled agents
POST/jobsYes202 or 200
GET/jobsYesJob list
GET/jobs/:idYesOne job
GET/insights?workspace_id=YesInsight list
GET/insights/:idYesOne insight
GET/approvals?workspace_id=YesApproval queue
GET/approvals/:idYesOne approval

GET /health

If you want to check that the host is up, call this first. No key.

curl https://www.framed.dev/api/health
{
  "ok": true,
  "service": "framed-partner-api",
  "api_version": "2026-08-21",
  "docs": "https://www.framed.dev/docs",
  "base": "https://www.framed.dev/api"
}

GET /whoami

If you want the account and workspaces this key can see, call this.

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/whoami
{
  "ok": true,
  "api_version": "2026-08-21",
  "account": { "id": "ACCOUNT_ID", "name": "Northwind Labs" },
  "key_scoped_workspace": null,
  "workspaces": [
    { "id": "WORKSPACE_ID", "name": "Northwind", "sandbox": false }
  ]
}

GET /workspaces

If you want only the workspace list, use this instead of /whoami.

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/workspaces

POST /workspaces

If you want a new workspace, send name. If the key can see more than one account, also send agency_id.

curl -X POST https://www.framed.dev/api/workspaces \
  -H "Authorization: Bearer frk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"name":"Northwind","agency_id":"ACCOUNT_ID"}'

GET /workspaces/:id

If you already have a workspace id, fetch that one row.

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/workspaces/WORKSPACE_ID

GET /context

If you want the current memory facts, pass workspace_id.

curl -H "Authorization: Bearer frk_live_…" \
  "https://www.framed.dev/api/context?workspace_id=WORKSPACE_ID"
{
  "ok": true,
  "workspace_id": "WORKSPACE_ID",
  "facts": [
    {
      "id": "FACT_ID",
      "content": "Debtor 123 is on hold until 2026-09-01",
      "source": "approved",
      "source_label": "Finance",
      "created_at": "2026-08-21T08:00:00.000Z",
      "updated_at": "2026-08-21T08:00:00.000Z"
    }
  ]
}

POST /context

If you want to add or correct a fact, this call always returns 202. The fact waits in Insights → To review.

curl -X POST https://www.framed.dev/api/context \
  -H "Authorization: Bearer frk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"workspace_id":"WORKSPACE_ID","fact":"Debtor 123 is on hold until 2026-09-01","replace_id":"FACT_ID","label":"Finance","webhook_url":"https://hooks.example/framed"}'
{
  "ok": true,
  "job_id": "JOB_ID",
  "approval_id": "APPROVAL_ID",
  "status": "awaiting_approval",
  "write": "queued_for_approval"
}

GET /chats

If you want threads on one workspace, pass workspace_id.

curl -H "Authorization: Bearer frk_live_…" \
  "https://www.framed.dev/api/chats?workspace_id=WORKSPACE_ID"

POST /chats

If you want a new thread, send the first message. Optional title.

curl -X POST https://www.framed.dev/api/chats \
  -H "Authorization: Bearer frk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"workspace_id":"WORKSPACE_ID","message":"What do we know about Debtor 123?","title":"Debtor 123"}'

GET /chats/:id

If you want the messages on a thread, fetch it by id.

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/chats/CHAT_ID

POST /chats/:id/messages

If you want to continue a thread, send the next message.

curl -X POST https://www.framed.dev/api/chats/CHAT_ID/messages \
  -H "Authorization: Bearer frk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"message":"Summarize the last answer."}'

GET /studio

If you want drafts on one workspace, pass workspace_id.

curl -H "Authorization: Bearer frk_live_…" \
  "https://www.framed.dev/api/studio?workspace_id=WORKSPACE_ID"

POST /studio

If you want a new draft, send title, prompt, and optional type (memo, report, or presentation).

curl -X POST https://www.framed.dev/api/studio \
  -H "Authorization: Bearer frk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"workspace_id":"WORKSPACE_ID","title":"Q3 brief","prompt":"Write a one-page memo","type":"memo"}'

GET /studio/:id

If you want one draft, including its body, fetch it by id.

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/studio/STUDIO_ID

GET /studio/:id/share

If you want the public URL, call share. Framed mints a token if the item does not have one yet.

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/studio/STUDIO_ID/share
{
  "ok": true,
  "studio_id": "STUDIO_ID",
  "share_url": "https://www.framed.dev/share/SHARE_TOKEN",
  "publication_url": null
}

GET /agents

If you want the slugs you can send to POST /jobs, list the agents installed on that workspace.

curl -H "Authorization: Bearer frk_live_…" \
  "https://www.framed.dev/api/agents?workspace_id=WORKSPACE_ID"

POST /jobs

If you want to run an installed agent, send agent_slug. Optional prompt and webhook_url.

curl -X POST https://www.framed.dev/api/jobs \
  -H "Authorization: Bearer frk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"workspace_id":"WORKSPACE_ID","agent_slug":"news_tracker","prompt":"Focus on Q3","webhook_url":"https://hooks.example/framed"}'

GET /jobs

If you want recent jobs for this key, call list. Pass workspace_id to narrow the list.

curl -H "Authorization: Bearer frk_live_…" \
  "https://www.framed.dev/api/jobs?workspace_id=WORKSPACE_ID"

GET /jobs/:id

If you want one job, including insight_id and result, fetch it.

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/jobs/JOB_ID

GET /insights

If you want recent briefings, pass workspace_id.

curl -H "Authorization: Bearer frk_live_…" \
  "https://www.framed.dev/api/insights?workspace_id=WORKSPACE_ID"

GET /insights/:id

If you want the markdown body, fetch one insight.

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/insights/INSIGHT_ID

GET /approvals

If you want the review queue, pass workspace_id.

curl -H "Authorization: Bearer frk_live_…" \
  "https://www.framed.dev/api/approvals?workspace_id=WORKSPACE_ID"

GET /approvals/:id

If you want one queued write, including the proposed fact, fetch it.

curl -H "Authorization: Bearer frk_live_…" \
  https://www.framed.dev/api/approvals/APPROVAL_ID

Fields

POST /workspaces: name (required), agency_id when the key can see more than one account.

POST /context: workspace_id (required), fact (required), replace_id, label, webhook_url (https only).

POST /chats: workspace_id (required), message (required), title.

POST /chats/:id/messages: message (required).

POST /studio: workspace_id (required), title (required), prompt (required), type (memo, report, or presentation).

POST /jobs: workspace_id (required), agent_slug (required), prompt, webhook_url (https only).

Statuses

queued, awaiting_approval, running, succeeded, failed, rejected, generating, accepted.

Errors

401 if the key is missing or invalid. 403 if the key is inactive or cannot see this workspace. 400 if a required field is missing. 404 if the job, chat, Studio item, insight, or route is unknown. Unknown routes point to https://www.framed.dev/docs.

Write kinds

A memory write waits as partner_write on v2_approvals until someone decides in Insights → To review.

Isolation

  • A key only reaches workspaces it is allowed to see.
  • A key never reads or writes another developer’s workspaces, or workspaces this key cannot access.
  • Workspace data stays in the EU (Stockholm).
  • Plaintext keys are never stored. Only a SHA-256 hash is kept.