Extraction API
The Extraction API converts an agent's tool-call trace into a replayable procedure. The parse is deterministic and model-free: the same trace gives the same steps, in the same order, with the same classification. Two small models sit beside it, both inside Memorable's own account: one writes the title, and the second stage of the judge scores whether a workflow is kept. Neither can rename, reorder or edit a step. The title is copied into the text that gets embedded, so that one field is not byte-stable.
The API is not stateless. A key that belongs to a workspace writes one row per prompt, carrying the prompt text, and one row per admitted procedure, carrying the title, the steps and the conditions, because the dashboard renders them from the server. The corpus is never sent and never stored, and request bodies are never logged. The procedure is also written into your own database, on your side, through your existing GBrain connection.
You are integrating a harness or agent with the Memorable Extraction API.
Base URL: https://memorable-extraction-api.memorable.workers.dev
1. Get a key once: keys belong to a workspace, so a human has to sign in.
POST /v1/device/code with { "hostname": "..." } -> { user_code, verification_uri,
device_code, expires_in, interval }. Show the user the code and the URL, then poll
POST /v1/device/token with { device_code } every `interval` seconds until it returns
{ "status": "approved", "api_key": "mk_..." }. STOP and ask your human to approve it;
you cannot complete a browser flow. Store the key; send it as
"Authorization: Bearer mk_...". POST /v1/keys is closed and returns 403.
2. After each finished session, POST /v1/extract with:
{ "session_id": "<stable id>", "harness": "<your harness name, any string>",
"task_description": "<one line: what the task was>", "skip_embedding": true,
"tool_calls": [ { "name": "<tool>", "input": { "command"|"file_path"|"path"|
"pattern"|"url"|"query": "<string>" }, "result": { "ok": true|false } |
{ "exit_code": 0 } } ] }
Send ONLY these fields. Do not send conversation text, file contents, or
credentials; include "result" only when the outcome is actually known.
3. Response: { "draft": { title, steps[{seq, action, activity_class, command?,
repeat_count, targets?, creates?}], trigger_signature{entities, search_text}, preconditions,
postconditions, ... }, "request_id": "..." }. The parse is deterministic and
model-free, so identical input yields identical steps; the title alone is
written by a small model and is not byte-stable. Store the draft yourself, on
the user's side. The service keeps the task_description line and the extracted
steps so the dashboard can render them; it does not keep the corpus.
4. Past the monthly allowance the API REFUSES. Still 200, still carrying the
draft, plus refused: "allowance_exhausted" and a detail sentence. Nothing is
stored on either side: check for `refused` before you store, and do not retry.
5. Errors: 401 unauthorized, 400 invalid_json/invalid_request, 413 over 8MB,
429 rate_limited (300/min per key). Every response carries request_id;
include it when reporting problems.Base URL
https://memorable-extraction-api.memorable.workers.devAuthentication
Every request requires a bearer token in the Authorization header. Requests without a valid token return 401. Keys look like mk_… and are issued through the device flow above. The key itself is never stored: only a SHA-256 hash of it is.
Authorization: Bearer $MEMORABLE_API_KEY
Every response carries a request_id (also in the x-memorable-request-id header). Include it when reporting a problem, it lets us find your exact request.
Get a key
/v1/device/codeKeys belong to a workspace, so getting one means signing in. The CLI does it for you: memorable login starts a device authorization (RFC 8628), prints a short code, waits while you approve it in a browser, and saves the key to ~/.memorable/config.json. That flow suits the machines that most need a key, a container, an SSH session, a CI runner, because it needs no reachable localhost.
POST /v1/keys used to issue anonymous keys and now returns 403. A key with no owner cannot be metered, revoked, or shown to the person it belongs to.
curl -X POST https://memorable-extraction-api.memorable.workers.dev/v1/device/code \
-H "Content-Type: application/json" -d '{"hostname":"my-laptop"}'
# → { "user_code": "TN9G-VTQX", "verification_uri": "https://memorable.sh/dash/device",
# "device_code": "…", "expires_in": 600, "interval": 5 }
# then poll POST /v1/device/token with { "device_code": "…" } until it returns
# { "status": "approved", "api_key": "mk_…" }Create a procedure
/v1/extractConverts a trace into a ProcedureDraft. Any harness is accepted: known harnesses (claude-code, codex, opencode) get curated activity registries; every other harness string is served by a generic tier that still infers execution from command-shaped input.
Body parameters
session_idstringrequiredtool_callsarray of ToolCallRecordrequirednamestringrequiredinputobjectrequiredresultobjectoptional{ ok?: boolean, exit_code?: number }. Powers postcondition detection, success is derived from real outcomes, never guessed from a command's name. Omitting it is valid; postconditions simply come back empty.task_descriptionstringoptionalcorpusstringoptionalharnessstringoptionalskip_embeddingbooleanoptionalResponse fields
draft.titlestringrequiredtask_description or the first meaningful transcript line.draft.stepsarrayrequired{ seq, action, activity_class, command?, repeat_count, targets?, creates? }. Retry cycles collapse into one step with repeat_count recording the repetitions.draft.steps[].activity_classstringrequiredread, write, search, execute, other. A shell line is classified from the command itself, deterministically: pure reads and searches are downgraded out of execute, file mutations are upgraded to write, and anything ambiguous stays execute. Without this a harness that funnels everything through one shell tool records no reads at all, and with no reads there are no dependencies between procedures.draft.steps[].targetsarray of stringoptionalcommand; present but empty means the line was parsed and touched none.draft.steps[].createsbooleanoptionaldraft.trigger_signatureobjectrequireddraft.preconditionsarray of stringrequireddraft.postconditionsarray of stringrequireddraft.embeddingarray of numberrequired[] when skipped or the provider failed. Failure never blocks the draft.draft.embedding_modelstringrequiredcurl https://memorable-extraction-api.memorable.workers.dev/v1/extract \
-H "Authorization: Bearer $MEMORABLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"session_id": "run-183",
"task_description": "rotate the TLS cert for api.example.com",
"harness": "my-python-orchestrator",
"tool_calls": [
{"name": "shell",
"input": {"command": "certbot renew"},
"result": {"ok": true}},
{"name": "shell",
"input": {"command": "nginx -s reload"},
"result": {"ok": true}}
]
}'{
"draft": {
"title": "rotate the TLS cert for api.example.com",
"session_id": "run-183",
"schema_version": "1.0.0",
"trigger_signature": {
"summary_text": "rotate the TLS cert for api.example.com",
"entities": {
"file_paths": [],
"commands": ["certbot renew", "nginx -s reload"],
"tool_names": ["shell"]
},
"search_text": "rotate the TLS cert ..."
},
"steps": [
{ "seq": 1, "action": "shell", "activity_class": "execute",
"command": "certbot renew", "repeat_count": 1 },
{ "seq": 2, "action": "shell", "activity_class": "execute",
"command": "nginx -s reload", "repeat_count": 1 }
],
"preconditions": [],
"postconditions": [
"final command exited successfully: nginx -s reload"
],
"embedding": [],
"embedding_model": ""
}
}Embed a query
/v1/embedQuery-side embedding for recall. Used only as a fallback, recall tries exact and lexical matching locally first (zero tokens, zero network), and the CLI prefers the embedding provider your GBrain already has configured.
textstringrequiredcurl https://memorable-extraction-api.memorable.workers.dev/v1/embed \
-H "Authorization: Bearer $MEMORABLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "rotate the TLS cert"}'Errors
Errors return JSON with an error code and, where useful, a detail message.
| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing or invalid bearer token. |
| 400 | invalid_request | Missing session_id or tool_calls; detail says what's expected. |
| 400 | invalid_json | The request body is not valid JSON. |
| 413 | payload_too_large | Body over 8 MB. |
| 429 | rate_limited | Over the 300-per-minute per-key limit; body carries retry_after_s. |
| 429 | quota_exceeded | Over 5,000 requests for the day on this key; body carries retry_after_s. |
| 403 | anonymous_keys_disabled | POST /v1/keys is closed; get a key through the device flow. |
| 401 | unauthorized | Also what an unknown path returns: auth runs before routing, so a wrong path never reaches a 404. |
An embedding-provider failure is not an error: the draft still returns with embedding: [] and an embedding_error field, and recall degrades to lexical and exact matching.
Past the monthly allowance is a refusal rather than a degradation. The response is still 200 and still carries the draft, because nothing about the request was wrong and retrying it changes nothing, but it carries refused: "allowance_exhausted" and a detail sentence written for a person. No memorable is created: no row is written and no embedding call is made. Capture resumes when the period resets or the plan changes. The free allowance is 1,000 memorables a month with a one-time reserve of 500 behind it.
Rate limits
300 requests per 60 seconds per API key, enforced at the edge, and 5,000 requests per key per day. A body is capped at 8 MB and 2,000 tool calls, and the prompt at 2,000 characters. A typical integration makes one /v1/extract call per completed workflow, which sits far below both limits.
Security
- What is kept, and only this. A workspace key writes one workflows row per prompt (the prompt text, the harness, the tool-call count, the judge's verdict) and one procedures row per admitted procedure (title, steps, trigger signature, conditions, embedding). The corpus is never sent. Request bodies are never logged, and only a SHA-256 hash of a key is stored.
- Your database stays yours. The API never receives connection credentials. Writes happen on your machine through your existing GBrain connection, into an isolated source.
- Pre-scanned input only.The CLI sends only content that has already passed GBrain's secret scanner, and refuses to send a session that was written unscanned.
- Injection-hardened recall.Stored procedures are re-rendered as explicitly inert reference data, stripped of control characters and size-capped, before they ever reach an agent's context.