Lampson as an engine
HTTP API
Every Lampson project (workspace) exposes an API for your apps, your bots and other agents:
https://your-lampson/w/<workspace>/v1/…
With no public URL, the base is http://127.0.0.1:8080/w/<workspace>/v1 — from this PC only. To use it from outside: lampson --domain or a tunnel.
API keys§
The API always requires a key, even from the same PC.
lampson key add --agent tienda --name "whatsapp bot"
lampson key add --agent tienda,soporte --rate 60 --callback https://hooks.my-app.com/
lampson key list
lampson key remove k6KJgJHpu
Or from the web UI: API in the left panel → + crear una API key.
- The key is shown only once, with the API and MCP URLs ready to copy. Lampson stores only its hash.
--agent: the agents it may use (with none, all of them).--rate N: requests per minute (default 120;0= no limit). Past the limit it answers429withRetry-After.--callback URL: the only URLs Lampson will send callbacks to with this key: same scheme, host and port, and the path as a folder (https://app.com/hooksallows/hooks/…, notapp.com.evil.netnor/hooks-x). Without it, the key cannot ask for callbacks. Keys created before Lampson 0.5.1 reach any public host name, andlampson key listflags them so you replace them.- An API key only opens
/v1: never the web UI, the terminal or the chat.
A workspace with keys doesn't shut down for inactivity: otherwise your bot's next message would find it off.
Sending a message§
curl -X POST https://your-lampson/w/tienda/v1/messages \
-H "Authorization: Bearer lk_…" -H "Content-Type: application/json" \
-d '{"to": "tienda", "thread": "whatsapp:+5491122334455", "text": "do you have whole-wheat bread?"}'
{"agent": "tienda", "thread": "whatsapp:+5491122334455", "session": "20261016-132332-8546",
"text": "Hi! Yes, we have sourdough whole-wheat bread at $3,200.", "steps": 2,
"usage": {"input": 1453, "output": 148, "cached": 1024}, "stopped": "done",
"batch": ["m59429972irl8"], "lead": "m59429972irl8"}
The body is an envelope:
| field | |
|---|---|
text | the message (required) |
to | the agent (default: the workspace's) |
thread | who/what this conversation is: same thread = same conversation. Each key has its own: another app with the same thread starts another one |
session | or a known session, from this same key (another key's gives 404) |
images | [{"media_type": "image/jpeg", "data": "<base64>"}] — models with vision see them |
from, meta | free-form: they travel with the message (ids from your system, the customer's name) |
sent_at | when the customer wrote (unix): orders a burst that arrived out of order |
callback | see below |
Errors: 400 (invalid envelope), 401 (no key or invalid key), 403 (agent outside the key, or callback not allowed), 404 (unknown agent), 429 (limit).
Three ways to get the answer§
- JSON —
POST /v1/messages: the request waits for the agent and returns the result. - Streaming —
POST /v1/messages/stream: Server-Sent Events with each step (session,assistant,tool_call…,done), likelampson run --json. - Callback —
POST /v1/messageswith"callback": "https://…": it answers202 {"job": "m…", "status": "queued"}right away and, when the agent finishes, Lampson POSTs to your URL. That's what WhatsApp-style webhooks need, since they demand a quick response.
The callback carries {job, jobs, batch, status, agent, thread, session, text, steps, usage, stopped, meta}. It is signed with the secret of the key that asked for it, which lampson key add shows once next to the key: X-Lampson-Signature: sha256=<HMAC-SHA256 of the body>, and X-Lampson-Key says which key. So another app using the same Lampson cannot sign a fake callback for yours. Keys created before Lampson 0.5.1 sign with LAMPSON_WEBHOOK_SECRET, as before. Verify it with the SDK or:
import hmac, hashlib
ok = hmac.compare_digest("sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest(), signature)
GET /v1/jobs/<id> returns the status of a message sent with a callback.
The mailbox§
Each conversation (agent + thread) has its mailbox. This comes built in, there's nothing to configure:
- Never two turns at once on the same conversation. Different customers do run in parallel.
- Bursts are merged. The turn starts after 3 seconds with no new messages (or 15 after the first): "hi" / "how much is the gluten-free bread?" / "is it in stock?" go in one turn and get one answer — the same one in all three requests, with
batch(the messages in the batch) andlead(the first one). With a callback, one POST goes out per batch. - What arrives mid-turn joins the turn in progress, before the agent answers. If it arrives while the model is already writing the answer, Lampson drops it and answers again with everything: an answer that ignores the customer's latest message never goes out.
The wait is tuned per agent with "buffer": {"quiet_s": 3, "max_s": 15}, or "buffer": false to answer without waiting (the queue still applies). → Agents
Objectives§
An objective is something an agent (or a team) works on by itself until it's done.
curl -X POST https://your-lampson/w/shop/v1/objectives -H "Authorization: Bearer lk_…" \
-d '{"agent": "cto", "text": "Add filtering by status, with tests; ask me before merging.", "budget_tokens": 400000}'
POST /v1/objectives | create: {text, agent? | team?, budget_tokens?, round_seconds?} → 201 with the objective |
GET /v1/objectives | the ones this key can see (its agents') |
GET /v1/objectives/<id> | one: status (active, waiting, done, failed, cancelled, paused), progress (last report), question and question_kind (question or approval), result, tasks (its board), ledger (its record), spent |
POST /v1/objectives/<id>/reply | answer: {text} to a question, {approved: true|false, text?} to an approval |
POST /v1/objectives/<id>/cancel | stop it |
A waiting objective doesn't move or spend until you answer.
Other endpoints§
GET /v1/agents | the agents this key may use |
GET /v1/jobs/<id> | status of a message sent with a callback |
POST /v1/mcp · /v1/mcp/stream | MCP over HTTP → Lampson as an MCP server |
GET /v1/openapi.json | the OpenAPI 3.1 of this API |
OpenAPI§
/v1/openapi.json describes only the public API — the web UI, the terminal and the channel webhooks don't appear. Use it to generate a client in your language (openapi-generator for Go, Java, C#, PHP…) or to try the API from your favorite tool. For JavaScript and Python there's already an SDK.