Call your agent

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.

The API tab of an agent: the endpoint, a live status and a caller key
The API tab: endpoint and caller keys

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:

EventData
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
doneThe 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:

  1. Your pageAsks your server for a token
  2. Your serverCreates the user's session and a token, with the caller key
  3. 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.
  • model is required by OpenAI clients but not used. System and developer messages and sampling options such as temperature are ignored, since the agent's instructions are the deployed ones. Only text content is accepted.
  • content is the agent's final reply. With "stream": true every message the agent writes is streamed, separated by a blank line.
  • A run that fails or is cancelled returns HTTP 422 with type: "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.

MethodPath
POST/sessionsStart a session (caller key only)
GET/sessions/:sessionIdSession metadata
POST/sessions/:sessionId/messagesSend a message; 202, or a stream with Accept: text/event-stream
POST/sessions/:sessionId/tokensIssue a session token (caller key only)
GET/sessions/:sessionId/runs/:runIdStatus and result
GET/sessions/:sessionId/runs/:runId/eventsStream the run's events
POST/sessions/:sessionId/runs/:runId/cancelCancel a queued or running call
GET/sessions/:sessionId/runs/:runId/artifactsFiles the run wrote
GET/sessions/:sessionId/runs/:runId/artifacts/:artifactIdDownload a file
GET/openai/modelsThe agent, as an OpenAI model list
POST/openai/chat/completionsOpenAI 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.