REST API Reference
Complete HTTP API reference for the JamJet runtime — workflows, executions, agents, work items, audit, and tenants.
REST API Reference
The JamJet runtime exposes a REST API on http://localhost:7700 by default. All requests and responses use JSON.
Authentication
Protected endpoints require a Bearer token:
Authorization: Bearer <token>Tokens are created via the CLI or the operator API. The runtime stores only the hash — the plaintext is returned once at creation time.
Roles
| Role | Read | Write | Admin |
|---|---|---|---|
operator | yes | yes | yes |
developer | yes | yes | no |
reviewer | yes | no | no |
viewer | yes | no | no |
Write operations (POST, PUT, DELETE) require developer or operator. Tenant management requires operator.
Health
GET /health
No authentication required.
{ "status": "ok", "version": "0.1.1" }Workflows
POST /workflows
Register a workflow definition (compiled IR).
Request:
{
"ir": {
"workflow_id": "research-agent",
"version": "0.1.0",
"state_schema": { ... },
"nodes": { ... },
"edges": [ ... ]
}
}Response 201 Created:
{
"workflow_id": "research-agent",
"version": "0.1.0"
}tip: Most users do not call this endpoint directly.
jamjet run workflow.yamlcompiles and submits the IR automatically.
Executions
POST /executions
Start a new workflow execution.
Request:
{
"workflow_id": "research-agent",
"workflow_version": "0.1.0",
"input": {
"query": "Latest AI agent frameworks?"
}
}workflow_version is optional — defaults to the latest registered version.
Response 201 Created:
{
"execution_id": "exec_a1b2c3d4"
}CLI equivalent: jamjet run workflow.yaml --input '{"query": "..."}'
GET /executions
List executions with optional filtering.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | — | Filter: running, paused, completed, failed, cancelled |
limit | int | 50 | Max results |
offset | int | 0 | Pagination offset |
Response:
{
"executions": [
{
"execution_id": "exec_a1b2c3d4",
"workflow_id": "research-agent",
"status": "completed",
"created_at": "2026-03-12T10:00:00Z",
"completed_at": "2026-03-12T10:00:04Z"
}
]
}GET /executions/:id
Get a single execution with full state.
Response:
{
"execution_id": "exec_a1b2c3d4",
"workflow_id": "research-agent",
"status": "completed",
"state": {
"query": "Latest AI agent frameworks?",
"answer": "..."
},
"steps_executed": 3,
"created_at": "2026-03-12T10:00:00Z",
"completed_at": "2026-03-12T10:00:04Z"
}CLI equivalent: jamjet inspect exec_a1b2c3d4
GET /executions/:id/events
Get the event timeline for an execution.
Response:
{
"events": [
{
"sequence": 1,
"kind": "WorkflowStarted",
"node_id": null,
"created_at": "2026-03-12T10:00:00.000Z"
},
{
"sequence": 2,
"kind": "NodeCompleted",
"node_id": "search",
"created_at": "2026-03-12T10:00:00.200Z"
},
{
"sequence": 3,
"kind": "NodeCompleted",
"node_id": "synthesize",
"created_at": "2026-03-12T10:00:02.040Z"
}
]
}Event kinds: WorkflowStarted, NodeScheduled, NodeCompleted, ApprovalReceived, ExternalEventReceived, ToolCallCompleted, WorkflowCancelled
CLI equivalent: jamjet events exec_a1b2c3d4
POST /executions/:id/cancel
Cancel a running execution.
Response:
{
"execution_id": "exec_a1b2c3d4",
"status": "cancelled"
}POST /executions/:id/approve
Send an approval decision for a human-in-the-loop node.
Request:
{
"decision": "approved",
"node_id": "manager-review",
"user_id": "user-42",
"comment": "Looks good, proceed.",
"state_patch": {
"priority": "high"
}
}Only decision is required. Valid values: "approved", "rejected".
Response:
{
"execution_id": "exec_a1b2c3d4",
"accepted": true
}POST /executions/:id/external-event
Inject an external event to wake a paused execution.
Request:
{
"correlation_key": "payment-received",
"payload": {
"amount": 500,
"currency": "USD"
}
}Response:
{
"execution_id": "exec_a1b2c3d4",
"accepted": true
}Agents
POST /agents
Register an agent with its Agent Card.
Request:
{
"id": "research-agent",
"uri": "http://localhost:7701",
"name": "Research Agent",
"description": "Searches the web and synthesizes reports.",
"version": "0.1.0",
"capabilities": {
"skills": [
{
"name": "research",
"description": "Deep research on any topic",
"input_schema": { "type": "object", "properties": { "query": { "type": "string" } } },
"output_schema": { "type": "object", "properties": { "report": { "type": "string" } } }
}
],
"protocols": ["a2a"],
"tools_provided": ["web_search"],
"tools_consumed": []
},
"autonomy": "guided",
"auth": { "type": "bearer_token" }
}Response 201 Created:
{
"agent_id": "research-agent"
}GET /agents
List agents with optional filtering.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
status | string | registered, active, paused, deactivated |
skill | string | Filter by skill name |
protocol | string | mcp, a2a, anp |
CLI equivalent: jamjet agents list
GET /agents/:id
Get full agent details including the Agent Card.
CLI equivalent: jamjet agents inspect <agent_id>
POST /agents/discover
Discover a remote agent by URL. Fetches /.well-known/agent.json and registers the agent.
Request:
{
"url": "https://remote-agent.example.com"
}Response 201 Created: full agent object.
CLI equivalent: jamjet agents discover <url>
POST /agents/:id/activate
Activate a registered agent.
Response:
{
"agent_id": "research-agent",
"status": "active"
}POST /agents/:id/deactivate
Deactivate an agent.
POST /agents/:id/heartbeat
Agent liveness heartbeat.
Response:
{
"agent_id": "research-agent",
"ok": true
}Audit log
GET /audit
Query the immutable audit log.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
execution_id | string | — | Filter by execution |
actor_id | string | — | Filter by actor/user |
event_type | string | — | Filter by operation type |
limit | int | 50 | Max results (max 200) |
offset | int | 0 | Pagination offset |
Response:
{
"items": [
{
"id": "audit_001",
"execution_id": "exec_a1b2c3d4",
"event_type": "node_completed",
"actor_id": "agent:research-agent",
"created_at": "2026-03-12T10:00:02Z"
}
],
"total": 42,
"limit": 50,
"offset": 0
}Work items (worker protocol)
The routes an external tool worker uses. Nodes of queue type python_tool and
java_tool run outside the engine process, so they claim work over HTTP rather
than through the in-process worker. The bundled Python and Java workers speak
this protocol; you only need it to write your own.
A worker loops: claim an item, renew its lease while it works, then settle it
exactly once with complete or fail.
Two fields from the claim response must be echoed when you settle:
lease_fence— proves you still hold the lease. Omit it and the item is settled but no event is emitted, so the scheduler keeps the nodescheduledand the execution never reaches a terminal state.idempotency_key— the engine records your result against it, so a re-run of that node replays the recorded output instead of firing your tool a second time. Omit it and nothing is recorded; every replay re-fires.
Neither is required, so a worker that drops them fails silently rather than loudly. Both omissions are deprecated behaviour kept only for older clients.
POST /work-items/claim
Claim the next available item for the given queue types. Returns at most one item.
Request:
{
"worker_id": "python-worker-0",
"queue_types": ["python_tool"]
}Response (an item was claimed):
{
"claimed": true,
"work_item": {
"id": "6b1f...",
"execution_id": "exec_a1b2c3d4",
"node_id": "__tools_0__",
"queue_type": "python_tool",
"payload": { "module": "...", "function": "...", "input": {} },
"attempt": 0,
"lease_fence": 4294967297,
"idempotency_key": "9f2c...64 hex chars"
}
}Response (queue empty):
{ "claimed": false }claimed: false also covers an item the engine declined to hand out — a policy
denial, or a payload whose coordinates disagree with its execution. The item is
settled server-side; the worker simply polls again.
idempotency_key is absent on engines older than the field.
POST /work-items/:id/heartbeat
Renew the lease while the tool is still running. Send it well inside the lease duration; a lapsed lease is reclaimed and handed to another worker.
Request:
{
"worker_id": "python-worker-0",
"lease_fence": 4294967297
}Response:
{ "renewed": true, "work_item_id": "6b1f..." }A renewal presenting a stale fence fails closed — the lease was reclaimed. Stop work and abandon the item rather than settling it.
POST /work-items/:id/complete
Settle the item successfully. Only output and state_patch are required, but
see the callout above about lease_fence and idempotency_key.
Request:
{
"execution_id": "exec_a1b2c3d4",
"node_id": "__tools_0__",
"output": { "messages": [] },
"state_patch": { "messages": [] },
"duration_ms": 812,
"lease_fence": 4294967297,
"idempotency_key": "9f2c...64 hex chars",
"gen_ai_model": "claude-sonnet-4-6",
"finish_reason": "tool_calls"
}Response:
{ "completed": true, "work_item_id": "6b1f..." }409 Conflict — the fence is stale or invalid: the lease was reclaimed and
another worker owns this item. Nothing is written.
{ "completed": false, "reason": "stale or invalid lease fence" }Treat a 409 as a no-op, not an error. Do not follow it with fail — that
would kill work the new claimant is running.
400 Bad Request — the idempotency_key is not one this engine mints
(64 lowercase hex characters). Echo the claim's value verbatim rather than
constructing your own.
POST /work-items/:id/fail
Report that the tool failed. The engine decides whether to retry or dead-letter based on the node's retry policy and the attempt count.
Request:
{
"error": "upstream timed out after 30s",
"lease_fence": 4294967297
}Response:
{
"failed": true,
"work_item_id": "6b1f...",
"retryable": true,
"attempt": 1
}retryable: false means attempts are exhausted and the item was dead-lettered.
409 Conflict — as with complete: the lease was reclaimed, nothing is
written, and it is a no-op rather than an error.
Sending no lease_fence returns 200 with a warning field. The item is
settled, but no NodeFailed is emitted and the node is not rescheduled, so
the execution is stranded. It exists only for clients that predate the fence.
Memory
Hosted vector memory for your agent fleet. Three-level scope (project / agent / end-user), inclusive cascade on recall, BYOK embedding provider. See the Memory guide for concepts and quickstart.
POST /v1/memory/settings
Configure the embedding provider key for the project. Encrypts the key with AES-256-GCM at rest.
Request:
{
"provider": "openai",
"api_key": "sk-...",
"enabled": true
}Response:
{ "provider": "openai", "enabled": true, "configured": true }The api_key is never returned. provider is locked to "openai" in MVP.
GET /v1/memory/settings
Read current settings without exposing the key.
Response: { "provider": "openai" | null, "enabled": bool, "configured": bool }
PATCH /v1/memory/settings
Toggle enabled without re-saving the key.
Request: { "enabled": false }
POST /v1/memory/settings/test
Probe a provider key against text-embedding-3-small before saving. Does not persist.
Request: { "provider": "openai", "api_key": "sk-..." }
Response: { "ok": true } or { "ok": false, "error": "<reason>" }
POST /v1/memory/add
Add a fact. Embeds via the configured provider, stores the vector, optionally dedups via ingestion_key.
Request:
{
"content": "User likes formal greetings",
"agent_id": "uuid|null",
"end_user_id": "u_alpha|null",
"metadata": { "source": "session_001" },
"ingestion_key": "optional-dedup-key"
}Response:
{
"id": "abc-...",
"content": "User likes formal greetings",
"agent_id": null,
"end_user_id": "u_alpha",
"created_at": "2026-05-12T10:00:00Z"
}Errors: 400 embedding_key_missing|invalid, 402 memory_quota_exceeded (with dimension: "memory"), 403 memory_disabled, 429 embedding_rate_limited.
POST /v1/memory/recall
Cascade-scoped vector similarity search.
Request:
{
"query": "how should I greet?",
"agent_id": null,
"end_user_id": "u_alpha",
"k": 10
}Response:
{
"facts": [
{
"id": "abc-...",
"content": "User likes formal greetings",
"agent_id": null,
"end_user_id": "u_alpha",
"similarity": 0.87,
"created_at": "2026-05-12T10:00:00Z",
"metadata": {}
}
]
}k clamps to [1, 100], default 10. Recall does not count against the memory quota.
POST /v1/memory/forget
Soft-delete by id (single fact) or by end_user_id (GDPR bulk). Exactly one must be supplied.
Request (single): { "id": "abc-...", "reason": "user request" }
Request (bulk): { "end_user_id": "u_alpha", "reason": "GDPR delete" }
Response: { "count": <i64> }
Bulk delete writes one memory_user_forgotten audit row with the count, not one row per fact.
GET /v1/memory/facts
List recent facts in the project, newest-first. No vector similarity — for the dashboard list view.
Query parameters: limit (default 50, max 200), before (ISO timestamp for pagination).
Response: { "facts": [{ id, content, agent_id, end_user_id, created_at, metadata }] }
GET /v1/memory/stats
Project-level stats. Used by the Memory tab header.
Response: { "count": <i64>, "quota": <i64>, "tier": "free|starter|team|business|enterprise" }
Tenants
Tenant management requires operator role.
POST /tenants
Create a new tenant.
Request:
{
"id": "acme",
"name": "Acme Corp"
}Response 201 Created:
{
"tenant_id": "acme"
}GET /tenants
List all tenants.
GET /tenants/:id
Get tenant details.
PUT /tenants/:id
Update tenant configuration.
Request:
{
"name": "Acme Corporation",
"status": "active",
"policy": { ... },
"limits": { ... }
}All fields optional.
Workers
GET /workers
List active runtime workers.
For the routes a worker itself calls — claim, heartbeat, complete, fail — see Work items (worker protocol).
Federation
GET /.well-known/did.json
W3C DID Document for A2A federation. No authentication required. Returns the runtime's decentralized identifier with service endpoints for active agents.
Error responses
All errors return JSON with an appropriate HTTP status code:
| Status | Meaning | Example |
|---|---|---|
400 | Bad request | { "error": "bad request: ir.workflow_id is required" } |
401 | Unauthorized | { "error": "invalid or expired token" } |
403 | Forbidden | { "error": "insufficient role — developer or operator required" } |
404 | Not found | { "error": "not found: execution exec_abc123" } |
500 | Internal error | { "error": "internal error: ..." } |
Configuration
| Environment variable | Default | Description |
|---|---|---|
JAMJET_BIND | 127.0.0.1 | Bind address |
JAMJET_PORT | 7700 | HTTP port |
JAMJET_PUBLIC_URL | http://{bind}:{port} | Public URL for federation |
DATABASE_URL | .jamjet/runtime.db | SQLite or PostgreSQL connection string |
JAMJET_TOKEN | — | Default API token (for clients) |
RUST_LOG | info | Log level |
CLI to API mapping
| CLI command | API endpoint |
|---|---|
jamjet run <wf> --input <json> | POST /executions |
jamjet inspect <exec_id> | GET /executions/:id + GET /executions/:id/events |
jamjet events <exec_id> | GET /executions/:id/events |
jamjet agents list | GET /agents |
jamjet agents inspect <id> | GET /agents/:id |
jamjet agents activate <id> | POST /agents/:id/activate |
jamjet agents deactivate <id> | POST /agents/:id/deactivate |
jamjet agents discover <url> | POST /agents/discover |
note:
jamjet devstarts the runtime server locally.jamjet validateandjamjet eval runare client-side operations that do not call the API.