Call your agent
Every agent has its own endpoint, /v1/invoke/<agent id>, for your apps and
customers to call. Its API tab in the console shows the endpoint, caller
keys and request examples ready to copy. Calls run the agent's live version,
and appear in its runs with their traces.
Before handing out keys, try the agent in its Playground or with
lgr chat <agent>. Both go through the endpoint the way a caller's browser
would.

Caller keys
A caller key works for one agent only. It can start sessions, send messages and read the sessions it created. It can't reach your project's other APIs, billing, secrets, traces or other callers' sessions.
- Create keys under API → Create caller key, or
lgr keys support-bot create "Customer A". The key is shown once. - Create one key per customer when customers call directly. A shared backend key can read every session it creates, so your backend must check which of your users owns which session.
- Never put a caller key in browser code. Use a session token instead.
- Revoke a key under API, or
lgr keys support-bot revoke <key id>. Revoking stops further reads and writes but doesn't cancel runs already accepted.
Sessions and messages
In the examples, AGENT_URL is the endpoint from the API tab and
CALLER_KEY a caller key. Keys go in the Authorization header.
Create a session, which is one conversation with its own files:
curl -X POST "$AGENT_URL/sessions" \
-H "Authorization: Bearer $CALLER_KEY" \
-H 'Content-Type: application/json' -d '{}'Send a message with the session id from the response:
curl -X POST "$AGENT_URL/sessions/$SESSION_ID/messages" \
-H "Authorization: Bearer $CALLER_KEY" \
-H 'Content-Type: application/json' \
-d '{"message":"Where is my order 1057?"}'The answer is HTTP 202 with the run's id, session_id, status and a
relative status_url. GET that URL with the same key until status is
succeeded, failed or cancelled, then read result, error and
usage.
Messages in one session run one at a time, and the agent keeps the conversation and the files it wrote between them. Different sessions run in parallel, each in its own sandbox. Sending the same message twice starts two runs; there is no deduplication. A caller can't change the agent's model, limits or instructions.
Streaming
Add Accept: text/event-stream to get the reply as it is written:
curl -N -X POST "$AGENT_URL/sessions/$SESSION_ID/messages" \
-H "Authorization: Bearer $CALLER_KEY" \
-H 'Accept: text/event-stream' \
-H 'Content-Type: application/json' \
-d '{"message":"Where is my order 1057?"}'The response is a server-sent event stream that ends when the run does:
| Event | Data |
|---|---|
status | { id, session_id, status: "running" } when the run starts |
text | { text } for each message the agent writes |
artifact | { id, path, size } for each file the run produced |
done | The run as its status URL returns it: status, result, error, usage |
Callers see the agent's messages, not its tool calls, logs or instructions.
text events carry whole messages, not single tokens. To reconnect after a
dropped connection, GET .../runs/<run id>/events with Last-Event-ID (or
?after=) and the stream picks up where it left off.
From a browser or app
Keep caller keys on your server. To let a web page or mobile app talk to one session, have your backend issue a session token and hand that to the client:
- Your pageAsks your server for a token
- Your serverCreates the user's session and a token, with the caller key
- Your pageSends messages and streams replies with the token
curl -X POST "$AGENT_URL/sessions/$SESSION_ID/tokens" \
-H "Authorization: Bearer $CALLER_KEY" \
-H 'Content-Type: application/json' -d '{"ttl_seconds":3600}'The response has token (lgr_st_...), session_id and expires_at.
ttl_seconds is 60 to 86400 and defaults to 3600. The token works in place of
the key for that session only: sending messages, streaming, polling,
cancelling and downloading files. It can't start sessions or reach other
ones, and stops working when it expires or its key is revoked.
Caller endpoints accept cross-origin requests, so a page can stream with
fetch:
const res = await fetch(`${agentUrl}/sessions/${sessionId}/messages`, {
method: "POST",
headers: { Authorization: `Bearer ${token}`, Accept: "text/event-stream", "Content-Type": "application/json" },
body: JSON.stringify({ message: "Where is my order?" }),
});
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = "";
for (let chunk; !(chunk = await reader.read()).done;) {
buffer += chunk.value;
let end;
while ((end = buffer.indexOf("\n\n")) >= 0) {
const block = buffer.slice(0, end);
buffer = buffer.slice(end + 2);
const event = block.match(/^event: (.*)$/m)?.[1];
const data = block.match(/^data: (.*)$/m)?.[1];
if (event === "text") showText(JSON.parse(data).text);
if (event === "done") showResult(JSON.parse(data));
}
}Chat widget
To put a chat on a website without writing a front end, add the widget script to the page. The API tab has this snippet with your agent's endpoint filled in.
<script src="https://d3iwapvcygehqg.cloudfront.net/embed/chat.js"
data-endpoint="https://d3iwapvcygehqg.cloudfront.net/v1/invoke/ag_123"
data-token-url="/agent-token" data-title="Support" async></script>The widget shows a chat button in the bottom-right corner, streams the
agent's replies and keeps the conversation while the visitor moves between
pages. It never sees a caller key: it POSTs to data-token-url, a route on
your server that answers with { "token": ..., "session_id": ... }, and asks
again when the token expires.
Your server decides which session a visitor gets, usually one per signed-in user, and must not take a session ID from the browser:
// Express. sessionsByUser is wherever you keep each user's session ID.
app.post("/agent-token", requireUser, async (req, res) => {
const headers = { Authorization: `Bearer ${process.env.CALLER_KEY}`, "Content-Type": "application/json" };
let sessionId = await sessionsByUser.get(req.user.id);
if (!sessionId) {
sessionId = (await (await fetch(`${AGENT_URL}/sessions`, { method: "POST", headers, body: "{}" })).json()).id;
await sessionsByUser.set(req.user.id, sessionId);
}
const token = await fetch(`${AGENT_URL}/sessions/${sessionId}/tokens`, { method: "POST", headers, body: "{}" });
res.status(token.status).json(await token.json());
});The widget shows text only; files the agent writes aren't offered for download in it.
OpenAI-compatible clients
The agent also speaks OpenAI Chat Completions, so OpenAI SDKs and chat front
ends that take a base URL can call it. Use $AGENT_URL/openai as the base
URL and a caller key or session token as the API key:
from openai import OpenAI
client = OpenAI(base_url=f"{AGENT_URL}/openai", api_key=CALLER_KEY)
reply = client.chat.completions.create(
model="support-bot",
messages=[{"role": "user", "content": "Where is my order?"}],
)
print(reply.choices[0].message.content)- With a caller key, each request is a new conversation in a fresh sandbox. Send the whole history as usual; the agent gets the earlier turns with the new message.
- With a session token, requests continue that session. The agent already has the conversation and its files, so it gets only the last user message.
modelis required by OpenAI clients but not used. System and developer messages and sampling options such astemperatureare ignored, since the agent's instructions are the deployed ones. Only text content is accepted.contentis the agent's final reply. With"stream": trueevery message the agent writes is streamed, separated by a blank line.- A run that fails or is cancelled returns HTTP
422withtype: "run_failed", so clients don't retry it and start another run. If the client disconnects before the reply is complete, the run is cancelled.
Endpoints
All paths are under the agent's endpoint.
| Method | Path | |
|---|---|---|
| POST | /sessions | Start a session (caller key only) |
| GET | /sessions/:sessionId | Session metadata |
| POST | /sessions/:sessionId/messages | Send a message; 202, or a stream with Accept: text/event-stream |
| POST | /sessions/:sessionId/tokens | Issue a session token (caller key only) |
| GET | /sessions/:sessionId/runs/:runId | Status and result |
| GET | /sessions/:sessionId/runs/:runId/events | Stream the run's events |
| POST | /sessions/:sessionId/runs/:runId/cancel | Cancel a queued or running call |
| GET | /sessions/:sessionId/runs/:runId/artifacts | Files the run wrote |
| GET | /sessions/:sessionId/runs/:runId/artifacts/:artifactId | Download a file |
| GET | /openai/models | The agent, as an OpenAI model list |
| POST | /openai/chat/completions | OpenAI Chat Completions |
Calls are billed to your organisation like any other run (see
Billing). When it is out of credit, new sessions and messages
are refused with 402.