JamJet
Reference

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

RoleReadWriteAdmin
operatoryesyesyes
developeryesyesno
revieweryesnono
vieweryesnono

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.yaml compiles 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:

ParameterTypeDefaultDescription
statusstringFilter: running, paused, completed, failed, cancelled
limitint50Max results
offsetint0Pagination 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:

ParameterTypeDescription
statusstringregistered, active, paused, deactivated
skillstringFilter by skill name
protocolstringmcp, 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:

ParameterTypeDefaultDescription
execution_idstringFilter by execution
actor_idstringFilter by actor/user
event_typestringFilter by operation type
limitint50Max results (max 200)
offsetint0Pagination 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 node scheduled and 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:

StatusMeaningExample
400Bad request{ "error": "bad request: ir.workflow_id is required" }
401Unauthorized{ "error": "invalid or expired token" }
403Forbidden{ "error": "insufficient role — developer or operator required" }
404Not found{ "error": "not found: execution exec_abc123" }
500Internal error{ "error": "internal error: ..." }

Configuration

Environment variableDefaultDescription
JAMJET_BIND127.0.0.1Bind address
JAMJET_PORT7700HTTP port
JAMJET_PUBLIC_URLhttp://{bind}:{port}Public URL for federation
DATABASE_URL.jamjet/runtime.dbSQLite or PostgreSQL connection string
JAMJET_TOKENDefault API token (for clients)
RUST_LOGinfoLog level

CLI to API mapping

CLI commandAPI 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 listGET /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 dev starts the runtime server locally. jamjet validate and jamjet eval run are client-side operations that do not call the API.

On this page