Mercury HTTP surface (0.17.1)

Download OpenAPI specification:

The HTTP surface of Mercury — the intended primary channel for a custom web UI. It is enabled by declaring the http channel in mercury.config.ts, together with an auth provider (auth): without one the surface doesn't start. CORS is enabled on every response (Access-Control-Allow-Origin, configurable via HTTP_SURFACE_CORS_ORIGIN, default *) and every route answers an OPTIONS preflight, so a browser UI on another origin can call it.

Authentication

Every route except this document, /version and the OPTIONS preflights needs Authorization: Bearer <token>. The app's auth provider decides what a valid token is (an OpenID Connect access token, a static test token); a missing or refused one gets 401 with WWW-Authenticate: Bearer, before anything runs. Then the core decides whether it talks to that person at all: someone the app's directory doesn't know gets 403 (unless the app lets unknown people in), and 503 when the directory can't be reached. Both carry { ok: false, error, reason }, error being what to tell them.

What a caller sees

Every read is scoped to the caller: their own conversations, pending confirmations, tool calls and memory, and the wiki as they see it, the team's common area (curated/) plus their own notes (personal/). Someone else's data doesn't exist as far as a caller can tell. /manifest lists what the caller is offered, and only /health is the same for everyone.

Conversations

A conversation is identified by an opaque, client-owned conversationId and belongs to whoever opened it: Mercury's session key is <caller id>:<conversationId>, so the same id sent by someone else is a conversation of their own, and a confirmation token staged in one person's conversation is never found from another's. Omitting conversationId on POST /turn starts a fresh ephemeral conversation. The durable transcript of one of the caller's conversations is readable via GET /conversation, and the list of their conversations via GET /conversations (both backed by the verbatim archive; they degrade to empty when the vector store is unreachable). A conversation never ends: sent again with the same conversationId, a turn continues it however long after. Mercury frees a conversation's in-memory history once it has been idle a while; the next turn rebuilds it from the archive (a summary of its older part and its newest messages, within HTTP_SURFACE_RESUME_MAX_CHARS), so the model still knows what the person sees on screen.

Streaming and cancellation

POST /turn streams the answer as Server-Sent Events: reasoning and text arrive as incremental deltas (never a single finished block), followed by an authoritative final event. To cancel a turn whose generation is diverging, the client simply closes the connection — Mercury aborts the in-flight generation.

Confirmation

An irreversible action is never executed on the model's say-so: the turn emits a pending event carrying a one-time token. The client confirms by sending that token back — either as the text of a follow-up POST /turn, or via POST /confirm. An action can offer several choices (e.g. "only this time" / "always"), each with its own token: the caller sends back one, and the others die with it.

Some actions hand their result back to the model instead of to the caller (a page read after the caller allowed it, for example). Confirming one by POST /turn streams the model's answer on that same stream; POST /confirm answers resume: true, and the client then opens POST /turn { resume: true, conversationId } to hear it. The answer waits five minutes and is heard once: later, after a restart, or a second time, that final's text says it's no longer available.

Run a conversational turn (SSE stream)

Runs one model turn and streams the result as text/event-stream. If text is a pending confirmation token it is resolved deterministically without ever consulting the model. Close the connection to cancel an in-flight turn. When the confirmation hands its result back to the model, the model's answer to it streams instead. With resume: true and no text, the turn is the one a /confirm answered resume: true for. With opening: true and no text, the assistant speaks first in the conversation named (right after someone signs in): it does only for someone it doesn't know the name of, asking how to call them, and otherwise the stream carries a single final with an empty text. Every text the person reads that isn't the model's (a refusal, a status line, a confirmation's reply) is in their language: the one they saved, else the one Accept-Language asks for, else the instance's default. The model reads the person's message after when and where they wrote it: in the time zone they saved, else the one X-Time-Zone says, else the instance's.

Authorizations:
bearerAuth
header Parameters
Accept-Language
string

The languages the client prefers, as browsers send them (it-IT,it;q=0.9,en;q=0.8). Used for the texts the person reads when they saved no language of their own; a language the instance doesn't ship is passed over.

X-Time-Zone
string

The time zone the client is in, by its IANA name, as a browser gives it (Intl.DateTimeFormat().resolvedOptions().timeZone). Used for the turn's time when the person saved no time zone of their own; a name that isn't a zone is passed over.

Request Body schema: application/json
required
text
string

The user's message, or a bare confirmation token. Required, unless opening or resume is true.

opening
boolean
Value: true

The assistant speaks first in conversationId, which is then required; text is ignored.

resume
boolean
Value: true

The turn a /confirm answered resume: true for, in conversationId, which is then required; text is ignored.

conversationId
string^[A-Za-z0-9_-]{1,128}$

Opaque, client-owned; letters, digits, - and _ (a UUID fits). Omit to start a fresh ephemeral conversation, which can't be continued.

Responses

Request samples

Content type
application/json
{
  • "text": "string",
  • "opening": true,
  • "resume": true,
  • "conversationId": "string"
}

Response samples

Content type
application/json
{
  • "ok": false,
  • "error": "string"
}

Confirm a staged irreversible action

Runs a one-time confirmation token through the same mechanism the bare-token-in-text path uses, keyed on the caller's own conversation (<caller id>:<conversationId>). Never touches the model. The reply is in the caller's language, as on /turn.

Authorizations:
bearerAuth
header Parameters
Accept-Language
string

The languages the client prefers, as browsers send them (it-IT,it;q=0.9,en;q=0.8). Used for the texts the person reads when they saved no language of their own; a language the instance doesn't ship is passed over.

Request Body schema: application/json
required
token
required
string
conversationId
required
string^[A-Za-z0-9_-]{1,128}$

Responses

Request samples

Content type
application/json
{
  • "token": "string",
  • "conversationId": "string"
}

Response samples

Content type
application/json
{
  • "ok": true,
  • "resolved": true,
  • "text": "string",
  • "resume": true
}

A code for linking another account to the caller

A one-time code (xxxx-xxxx-xxxx, valid for ten minutes) the person sends from another account, on any channel, twice: the first time Mercury says whom the account would be linked to, the second time it links it. From then on both accounts are one person. Only the caller sees the code; the model never does.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "ok": true,
  • "code": "string",
  • "expiresAt": "2019-08-24T14:15:22Z"
}

A conversation's durable transcript

Authorizations:
bearerAuth
query Parameters
id
required
string^[A-Za-z0-9_-]{1,128}$

The caller's conversationId, as /conversations lists it.

limit
integer
Default: 200
offset
string

Opaque pagination cursor from a previous page's nextOffset.

Responses

Response samples

Content type
application/json
{
  • "ok": true,
  • "messages": [
    ],
  • "nextOffset": "string"
}

List the caller's conversations (most-recently-active first)

Authorizations:
bearerAuth
query Parameters
limit
integer
Default: 50

Responses

Response samples

Content type
application/json
{
  • "ok": true,
  • "conversations": [
    ]
}

Liveness/health of Mercury and its dependencies

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{ }

The plugins, tools and skills the caller is offered

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{ }

The caller's pending confirmations (tokens redacted)

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{ }

Who the caller is to the assistant

How the caller asked to be called, null when they never said: a UI asks for an opening turn after a sign-in only then. Also the language they asked the assistant to use and the time zone they saved, null when they never said (missing on a core that doesn't know them). Not there on an older core, which can't say.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "ok": true,
  • "name": "string",
  • "language": "string",
  • "timeZone": "string"
}

Saves the caller's time zone

The time zone the caller is in, by its IANA name, for a UI's settings: from then on their turns are stamped with the time there, whatever X-Time-Zone says. Saved as the IANA list writes it (europe/rome is Europe/Rome). Not there on a core without settings.

Authorizations:
bearerAuth
Request Body schema: application/json
required
timeZone
required
string

An IANA name such as Europe/Rome; a fixed offset (+02:00) isn't one.

Responses

Request samples

Content type
application/json
{
  • "timeZone": "string"
}

Response samples

Content type
application/json
{
  • "ok": true,
  • "timeZone": "string"
}

The tool calls made in the caller's recent turns

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{ }

List the wiki files the caller can see (`curated/` and `personal/`)

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{ }

Read one wiki file the caller can see

Authorizations:
bearerAuth
query Parameters
path
required
string

A path as /wiki/list gives it, e.g. curated/team.md or personal/notes/plan.md.

Responses

Response samples

Content type
application/json
{ }

Grep the wiki files the caller can see

Authorizations:
bearerAuth
query Parameters
pattern
required
string

A regular expression, matched line by line, case ignored.

Responses

Response samples

Content type
application/json
{ }

Scroll the caller's points in a memory collection

Authorizations:
bearerAuth
query Parameters
collection
required
string

One of the collections kept per person (the verbatim archive, episodic memory, semantic facts), by its configured name.

limit
integer
Default: 50
offset
string

Responses

Response samples

Content type
application/json
{ }

Where a person comes back after logging in to a service

Mounted when HTTP_SURFACE_PUBLIC_URL is set. The service sends the person's browser here with code and state (or error); the single-use state is the credential, so no bearer token is needed. Answers with a plain-text page, in the language the browser asks for (Accept-Language), else the instance's default; a failed login reads in the language of the person who started it.

query Parameters
code
string
state
string
error
string
header Parameters
Accept-Language
string

The languages the client prefers, as browsers send them (it-IT,it;q=0.9,en;q=0.8). Used for the texts the person reads when they saved no language of their own; a language the instance doesn't ship is passed over.

Responses

Which Mercury this is

The framework's version, major.minor.patch, for a UI to show. Needs no bearer token.

Responses

Response samples

Content type
application/json
{
  • "ok": true,
  • "version": "0.59.0"
}

This OpenAPI document

Responses