HTTP API
read as.mdGuuey’s HTTP API is what the CLI and the chat SDKs call, and parts of the console. This page covers the routes you can call yourself: reading what happened in your app (sessions, threads, transcripts) from your own backend, and reading or deleting an end user’s own conversation from your frontend.
The base URL for every route on this page is:
https://api.us-east-1.guuey.com/v1There are two kinds of caller, and they authenticate differently:
| Caller | Routes | Credential |
|---|---|---|
| You, the builder (your backend, a script, CI) | /v1/apps/… |
An API key in Authorization: Bearer |
| Your end user (their browser or app) | /v1/threads/…, /v1/agents/… |
The end user’s own identity: a guest secret or a sign-in token |
To run a conversation, use the SDK rather than raw HTTP. See Running a turn.
Authenticate as the builder
Section titled “Authenticate as the builder”Send one header on every builder route:
Authorization: Bearer <key>The key’s prefix tells Guuey what kind of key it is:
| Prefix | What it is | Where you get it |
|---|---|---|
guuey_user_ |
A personal access token. It acts as you, on every app you can reach. | The console’s API keys page, or guuey login (see the CLI guide) |
guuey_wkz_ |
A workspace API key. It acts as the workspace, with no person behind it, on the workspace’s own apps. | The workspace’s API Keys page (see Workspace-owned apps) |
guuey_svc_ |
An app service token. It can only deploy its own app: the reconcile and deployment routes in Agents as code. | guuey tokens create --label "<what uses it>" --app-id <appId> (see the CLI guide) |
A key is shown in full once, when it is created. Guuey stores only a hash of it, so copy it then. If you lose it, create a new one.
A personal access token carries a scope. A key you create yourself, in the console or with guuey login, has the manage scope and can call every route your account can. A read-only key (the read scope, granted to an app you connect through OAuth) can only make read requests: reads of your own data that return no secret values, plus one read-shaped POST, the probe that checks an MCP server address before you add it. Reading an app’s environment values, for example, needs manage. Every other route, including one Guuey doesn’t recognize, answers it with 403.
A missing, wrong, revoked or expired key all get the same answer, 401 with the code UNAUTHENTICATED. A key with an unknown guuey_ prefix gets 401 too, and the message lists the prefixes Guuey accepts.
Read your app’s sessions
Section titled “Read your app’s sessions”A session is one visitor’s run on your agent’s pod: when it started, its last activity, whether it ended in an error, and the thread it belongs to.
GET /v1/apps/{appId}/sessions?limit=200Authorization: Bearer <key>{ "sessions": [ { "id": "…", "appId": "…", "sessionStatus": "completed", "hasError": false, "createdAt": "2026-11-06T09:14:03.120Z", "lastActivityAt": "2026-11-06T09:16:40.002Z", "threadId": "…" } ], "truncated": false}- Sessions come newest first.
limitis optional and defaults to200. A value above2000reads as2000. A value below1, or one that is not a whole number, is a400. truncated: truemeans more sessions exist than this page holds. There is no cursor: ask for a largerlimit, up to2000.sessionStatusisactive,completed,expired, ornull.hasErrorturnstrueonce a turn in the session has failed, and staystrue, even after later turns succeed.createdAtandlastActivityAtcan benull, andthreadIdisnulluntil the session is linked to its thread.
Read one session by its id:
GET /v1/apps/{appId}/sessions/{sessionId}Authorization: Bearer <key>It answers with one session object, the same shape as a row of the list. A session id from another app gets the same 404 as one that does not exist.
Sessions can carry other fields. Ignore any field you don’t know: responses can gain fields.
Read your app’s conversations
Section titled “Read your app’s conversations”A thread is one conversation between a visitor and your agent. These two routes list your app’s threads and read one thread’s transcript.
GET /v1/apps/{appId}/threads?limit=50&cursor=<nextCursor>Authorization: Bearer <key>{ "appId": "…", "threads": [ { "threadId": "…", "userId": "g_…", "userKind": "guest", "title": "…", "status": "active", "lastMessageAt": "2026-11-06T09:16:39.871Z", "lastMessagePreview": "…", "createdAt": "2026-11-06T09:14:03.120Z" } ], "nextCursor": "…"}- Threads come newest first, by their last message.
limitis optional, from1to100, and defaults to50. Any other value is a400. - When
nextCursoris notnull, pass it back ascursorfor the next page. A cursor from another app is a400. userIdis a handle for the person on the other side, stable across their threads in your app. A guest’s handle starts withg_, and a handle for a user from your own sign-in starts withbyo_. Guuey derives both by hashing, so neither carries the person’s email or name.userKindsays what kind of person it is:guest(an anonymous visitor),byo(signed in through your own sign-in) oraccount(signed in with a Guuey account).
GET /v1/apps/{appId}/threads/{threadId}/messages?limit=200&cursor=<nextCursor>Authorization: Bearer <key>{ "appId": "…", "threadId": "…", "messages": [ { "seq": 0, "at": "2026-11-06T09:14:03.120Z", "kind": "text", "authorRole": "user", "text": "Do you ship to Canada?", "toolName": null } ], "nextCursor": null}- Messages come in conversation order, by
seq.limitis optional, from1to500, and defaults to200. Any other value is a400. - A page can hold fewer messages than
limitand still carry anextCursor, because a page that would be too large is cut short. Keep reading untilnextCursorisnull. kindistext,card(a generative UI card, named by the tool that made it intoolName) orevent.authorRoleisuser,agentorsystem.textcan benull.- Messages can carry other fields. Ignore any field you don’t know: responses can gain fields.
Who can read them
Section titled “Who can read them”The sessions and conversation routes are for people, because what visitors type can be personal:
- Call them with a personal access token that has the
managescope, or from a console session. A workspace API key, an app service token or a read-only personal access token gets403. - The person must be the app’s owner, or, for an app owned by a workspace, an owner or admin of that workspace. Anyone else gets
404, the same answer as for an app that does not exist. A thread or session id from another app gets that404too. - Each read writes an event to the audit log (the workspace’s, for an app owned by a workspace), with how many rows it returned.
To read only one end user’s conversations, for example to answer their request for a copy of their data, see Exporting one end user’s conversations on the Security page.
Your end user’s own conversation
Section titled “Your end user’s own conversation”These routes are called from your frontend, as the end user. They read and delete only that person’s own threads. @guuey/chat and @guuey/agent-client call the transcript and delete routes for you. Neither calls the thread list, so you call that one yourself. When you build your own client, you call all of them.
Every call carries exactly one identity:
- A guest sends the
guuey_guestcookie (a browser request withcredentials: "include") or the guest secret in anx-guuey-guestheader. - A signed-in end user sends
Authorization: Bearer <token>, with a Guuey sign-in token, or with your own sign-in’s token for an app inbyomode (see End-user identity).
A call with none of them gets 401. See Give visitors a stable identity for the guest secret.
List their threads
Section titled “List their threads”GET /v1/agents/{appId}/threads lists the caller’s threads in one app, newest first. Its request and response are on the SDK page, under Conversation continuity.
Read a thread’s transcript
Section titled “Read a thread’s transcript”GET /v1/threads/{threadId}/messages?limit=50&nextToken=<nextToken>x-guuey-guest: <guest secret>{ "rows": [ { "seq": 0, "at": "2026-11-06T09:14:03.120Z", "kind": "text", "authorRole": "user", "text": "Do you ship to Canada?" } ], "nextToken": null, "thread": {}}limitis optional and defaults to50. A value above100reads as100. A missing, zero or unreadable value reads as50.nextTokencomes back on every page. Pass it back for the next page until it isnull. A page can be cut short when it would be too large, so loop onnextToken, not on the row count. A token from another thread is a400.order=descreads newest first, so a client that stops after a few pages keeps the newest messages. The response then carries"order": "desc". Without it, rows come oldest first.includeadds a turn’s tool results and calls to its rows. Send it once, comma-separated:include=toolResults,toolCalls, or just one of the two. Unknown entries are ignored.@guuey/agent-clientreads them for you.threadis{}while the conversation is open, and{ "closed": { "code", "message", "closedAt" } }once the agent has ended it.
Each row has seq, at, kind (text, card or event), authorRole (user, agent or system), text (which can be null) and clientMessageId: the turn’s key on the user’s row, and that key with a suffix on the agent’s rows for the same turn. Its type in @guuey/agent-client is optional, so handle a row without it. Rows carry more fields, such as a card’s contents. Read those through fetchThreadHistory in @guuey/agent-client, whose types describe them, and ignore any field you don’t know.
The answers, in the order Guuey checks:
- No identity at all:
401. - With a guest identity:
404if the thread does not exist, then403if it belongs to someone else. - With a
Bearertoken:404if the thread does not exist (checked before the token), then401if the token does not verify, then403if the thread belongs to someone else.
Delete a thread
Section titled “Delete a thread”DELETE /v1/threads/{threadId}x-guuey-guest: <guest secret>It answers 200 with { "deleted": true, "threadId": "…" }. It takes the same identities as the transcript read and answers with the same 401, 404 and 403.
This erases the thread and every message in it. It does not hide them. clearConversation() in @guuey/chat and deleteThread in @guuey/agent-client both call it, so a “new chat” button wired to either one deletes the old conversation. To start fresh and keep the old conversation, call reset() from useAgentInvoke in @guuey/agent-client, which starts a new thread and leaves the old one stored. @guuey/chat has no call that keeps the old conversation. Who else can delete conversations is on the Security page.
Move a guest’s threads after sign-in
Section titled “Move a guest’s threads after sign-in”In an app with your own sign-in (byo mode), a visitor who chats as a guest and then signs in has a new identity. This route moves their guest threads in the app to the signed-in user. @guuey/chat calls it for you when a guest signs in, and mergeGuestThreads in @guuey/agent-client calls it when you build your own client (see Signed-in users).
POST /v1/agents/{appId}/merge-guestAuthorization: Bearer <your sign-in's token>x-guuey-guest: <guest secret>Content-Type: application/json
{ "threadIds": ["<the open thread>"] }- The request carries both identities: the signed-in user’s token from your own sign-in, and the guest’s secret in
x-guuey-guestor theguuey_guestcookie. The body names neither. - The token must come from this app’s own sign-in. Guuey checks it against the app’s issuer and audience, and a Guuey account token answers
403. Without the token or the guest credential, the route answers401. Only this app’s threads move. threadIdsis optional, at most 20. Put the open thread there, because it can be too new for Guuey to find on its own, and keep using that thread id afterwards.- It answers
200with{ "moved": 2, "skipped": 0 }. One call moves at most 100 threads. When the answer carriesnextCursor, send it back ascursorin the body to move the rest. - A run the guest left paused does not resume after the move, so the user has to send that message again. Rotate or clear the guest secret afterwards.
- An app without its own sign-in configured (
byomode with an issuer and audience) answers400withBYO_NOT_CONFIGURED. Each signed-in user, and each guest, can be merged up to 10 times an hour, and past that the route answers429. A503means try again later.
Running a turn
Section titled “Running a turn”A turn is one message to your agent and its streamed reply. Run it with the SDK, not raw HTTP:
- In React,
useAgentInvokefrom@guuey/agent-clientruns turns and keeps the thread for you. - Anywhere else,
invokeTurnruns one turn as a stream of events. The events are AgJSON, silverprotocol’s open event format, and@silverprotocol/corereduces them into messages. - When a turn pauses to ask the user something, answer a consent ask with
createHitlAnswerRelay.
The SDK functions and the AgJSON event types are the contract. The HTTP framing underneath them can change between releases. See Building your own client.
Errors
Section titled “Errors”An error answers with a JSON body:
{ "error": { "code": "NOT_FOUND", "message": "Thread … not found" } }Read code. message is for people and can change.
| Status | Builder routes (/v1/apps/…) |
End-user routes (/v1/threads/…, /v1/agents/…) |
|---|---|---|
400 |
VALIDATION |
BAD_REQUEST |
401 |
UNAUTHENTICATED |
UNAUTHORIZED |
403 |
FORBIDDEN |
FORBIDDEN |
404 |
NOT_FOUND |
NOT_FOUND |
500 |
INTERNAL |
INTERNAL |
The two kinds of route use different codes for the same status, as the table shows. A builder route can also answer 409 with CONFLICT, or 503 with TRANSIENT_CONFLICT, which means “try again”. An end-user route can answer 503 with UNAVAILABLE, which means “try again shortly”. When it sends Retry-After, wait that many seconds.
More builder routes
Section titled “More builder routes”Other builder routes are documented on the pages for what they do:
- Deploying from CI, rolling back and reading deployments: Agents as code.
- Erasing or exporting one end user’s data: Security.
- The CLI calls this API with your key. The CLI guide covers each command.