---
title: HTTP API
description: Lampson's /v1 API for your apps — messages to an agent as JSON, SSE streaming or callback, rate-limited API keys, the burst-merging mailbox and OpenAPI.
---
# 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`](/en/docs/phone) or a tunnel.

## API keys

The API **always** requires a key, even from the same PC.

```sh
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 answers `429` with `Retry-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/hooks` allows `/hooks/…`, not `app.com.evil.net` nor `/hooks-x`). Without it, the key cannot ask for callbacks. Keys created before Lampson 0.5.1 reach any public host name, and `lampson key list` flags 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

```sh
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?"}'
```

```json
{"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`), like [`lampson run --json`](/en/docs/headless#json-the-events).
- **Callback** — `POST /v1/messages` with `"callback": "https://…"`: it answers `202 {"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](/en/docs/sdk#verifying-a-callback) or:

```python
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) and `lead` (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](/en/docs/agents)

## Objectives

An [objective](/en/docs/objectives) is something an agent (or a [team](/en/docs/teams)) works on by itself until it's done.

```sh
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](/en/docs/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](/en/docs/sdk).
