Skip to content

Session API (REST)

POST /api/session is the canonical way for a backend to create a Coframe Agent session and start its first agent turn — no SPA, no persistent connection. One request creates the session, persists the initial transcript, starts exactly one turn, and returns everything needed to follow it. Prefer the REST SDK if you’re in TypeScript; this page is the underlying wire contract for everyone else.

Send your API key with every request (including status polling):

Authorization: Bearer <api-key>

X-Jarvis-Api-Key: <api-key> is accepted as an alternative header. Unauthenticated requests receive 401.

POST /api/session
Content-Type: application/json
FieldTypeRequiredDescription
messagesMessage[]yesInitial transcript; the last user message seeds the first turn.
targetUrlstring (URL)noPage the edit targets.
targetPageUrlsstring[]noAdditional target pages.
metadataobjectnoOpaque caller metadata stamped onto the session.
idempotencyKeystringnoReplays return the original session instead of creating a duplicate.
userIdstringnoStable caller identity for turn attribution.
source"web" | "api" | "slack" | "cli"noAttribution stamped onto lifecycle events. Defaults to api.
callbackobjectnoWebhook registration — see below.

A Message is { role: "user" | "assistant", parts: [{ type: "text", text: string }] }.

{
"success": true,
"sessionId": "session-1c8816418724",
"requestId": "req-658e8124-6da0-4f4e-8c9d-583607248d86",
"runId": "run_mrrllafh_vpswfpvp",
"shareUrl": "/edit/session-1c8816418724",
"statusUrl": "/api/sessions/session-1c8816418724/requests/req-658e8124-...",
"streamUrl": "/api/sessions/session-1c8816418724/requests/req-658e8124-.../stream"
}
  • shareUrl — open in a browser to watch or join the session.
  • statusUrl — poll (same auth header). Returns { success, sessionId, requestId, runId, status } where status is "pending" | "running" | "completed"; unknown request IDs return 404.
  • streamUrl — Server-Sent Events of the run’s domain events.

Invalid input returns 400 { success: false, error, code: "invalid_request" } with a field-scoped error message, and no session is created.

Terminal window
curl -s -X POST "https://<editor-host>/api/session" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [{ "role": "user", "parts": [{ "type": "text", "text": "Make the hero CTA green." }] }],
"targetUrl": "https://example.com/landing",
"idempotencyKey": "my-experiment-1234",
"callback": { "url": "https://api.example.com/hooks/editor", "secret": "whsec_..." }
}'

Instead of polling or holding the SSE stream open, register a callback and Coframe Agent pushes run lifecycle events to your endpoint:

FieldTypeRequiredDescription
urlstringyeshttps (or http) URL to POST events to. Loopback, private-range, and link-local hosts are rejected.
secretstringnoEnables HMAC-SHA256 signing of every delivery.
eventsstring[]noSubset of ["run.started", "run.completed"]. Default: both.

Each event is POSTed as JSON:

{
"event": "run.completed",
"sessionId": "session-1c8816418724",
"requestId": "req-658e8124-6da0-4f4e-8c9d-583607248d86",
"runId": "run_mrrllafh_vpswfpvp",
"timestamp": "2026-07-19T09:33:05.669Z",
"data": { "kind": "run.completed", "...": "the full domain event" }
}

With headers:

Content-Type: application/json
X-Coframe-Event: run.completed
X-Coframe-Delivery: whd-<uuid>
X-Coframe-Signature: sha256=<hmac-hex> (only when a secret was registered)

The signature is HMAC-SHA256 of the exact raw request body, keyed by your secret. Always verify against the raw bytes — re-serializing the parsed JSON will not match.

import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody: string, secret: string, signatureHeader: string): boolean {
const expected = `sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`;
return timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}
import hmac, hashlib
def verify(raw_body: bytes, secret: str, signature_header: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header)
  • At-least-once. Deduplicate by the X-Coframe-Delivery id (or by (runId, event)).
  • Deliveries are durable — they survive server restarts and redeploys.
  • Failed attempts retry with exponential backoff (5 attempts total; each attempt times out after 10 seconds), then the delivery is dropped.
  • Respond 2xx quickly from your endpoint; do slow work asynchronously.