---
title: "API"
id: "1701"
type: "page"
slug: "api"
published_at: "2026-09-20T20:15:32+00:00"
modified_at: "2026-09-21T01:36:28+00:00"
url: "https://xedant.com/agents/agent/docs/api"
markdown_url: "https://xedant.com/agents/agent/docs/api.md"
excerpt: "The Xedant Agent API is a way to send commands to the agent and get…"
---

# API

[https://xedant.com/agents/agent/docs/api.md](https://xedant.com/agents/agent/docs/api.md)

**The Xedant Agent API is a way to send commands to the agent and get answers back without a browser.** Other programs can create chats, send prompts, and collect results through a set of API commands — from build pipelines, external tools, and wrappers. The same key also opens the extended surface (chats, skills, models, settings, git, analytics, and speech recognition) and a subscription to live events — that is exactly how Xedant Research Agent connects to its input field, microphone, and analytics. The sections below are a developer reference for these addresses; if you are new to the term, an API is simply a set of addresses programs use to talk to each other, and the one idea worth keeping is this: a program talks to the agent by the same rules a person follows in the interface.

## Authentication

The API is switched on by setting the environment variable `AGENT_API_KEY`. It stores not the key itself but its fingerprint (a SHA-256 hash — a short string the key cannot be recovered from); on every request the server computes the fingerprint of the key you sent and compares it with the stored one.

The key is passed in the `X-API-Key` header — a service field of the request where the program states its access key:

```
X-API-Key: my-secret-api-key
```

Live connections (WebSocket/SignalR) cannot carry headers, so the same key is also accepted as the `api_key` address parameter:

```
/svelteChatHub?api_key=my-secret-api-key
```

### How to get the key hash

You can compute the key’s fingerprint on the application’s login page (the “SHA256 generator” button under the login form) or by hand:

```
# Linux / macOS
echo -n "my-secret-api-key" | sha256sum | awk '{print $1}'

# Windows PowerShell
$hash = [System.BitConverter]::ToString([System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes("my-secret-api-key"))).Replace("-", "").ToLower()
$hash
```

Put the resulting fingerprint into `AGENT_API_KEY` (an application-level variable set before launch — as a system environment variable, a command-line argument, or through a `.env` file).

### Codes returned when authentication fails

- **401 Unauthorized** — the key is missing or wrong;
- **503 Service Unavailable** — the `AGENT_API_KEY` variable is not set: “Remote API is disabled”, the API is off entirely.

## /api/remote commands

An endpoint is the address a program calls on the server to fetch something or make something happen. Every `/api/remote` command is described below.

### Creating a chat

```
POST /api/remote/chats
```

Creates a new chat. A prompt, when supplied, is **queued** and processed automatically — it is never sent directly. The chat can also be linked to a bot (`botId`); the link is silent: it never posts to Telegram and can never break chat creation, and its outcome comes back in the `botLinked` and `botLinkNote` flags.

#### Request body

- `prompt` (optional) — the initial message; with it the chat is created with the status `queued`, without it — `created`;
- `title` (optional) — the chat’s title (defaults to “Remote API Chat”);
- `model` (optional) — the model for the chat (see [Models](/agents/agent/docs/models) );
- `skill` (optional) — a skill; absent means “none” (no inheritance);
- `systemPrompt` (optional) — a system prompt, stored on the chat;
- `botId` (optional) — the bot id for the silent chat link.

#### Response (201 Created)

```
{
  "chatId": 123,
  "url": "/123",
  "status": "queued",
  "createdAt": "2026-09-04T10:00:00Z",
  "botLinked": null,
  "botLinkNote": ""
}
```

#### Example

```
curl -X POST http://localhost:5173/api/remote/chats \
  -H "X-API-Key: my-secret-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Explain what this code does",
    "title": "Code Analysis",
    "model": "claude-sonnet"
  }'
```

### Chat status

```
GET /api/remote/chats/{chatId}/status
```

Returns the current processing status: `processing` — the agent is working, `queued` — the chat is idle but its queue holds messages, otherwise `completed`. Along with the status come the title, the timestamps, the message count, and the accumulated cost. The `error` field in the response is always empty.

```
{
  "chatId": 123,
  "status": "processing",
  "title": "Code Analysis",
  "createdAt": "2026-09-04T10:00:00Z",
  "updatedAt": "2026-09-04T10:00:15Z",
  "messageCount": 2,
  "totalCost": 0.005
}
```

Example:

```
curl http://localhost:5173/api/remote/chats/123/status \
  -H "X-API-Key: my-secret-api-key"
```

### Chat messages

```
GET /api/remote/chats/{chatId}/messages?since={timestamp}
```

Incremental message reading — the program picks up only what appeared after a given timestamp instead of re-parsing the whole history. The `since` parameter (ISO 8601) returns messages from the given timestamp inclusively and is echoed back in the response; an invalid `since` value is silently ignored — the full history returns. Content is simplified by type: a user message gives its text; an agent reply gives its text with button markup stripped; an error gives the error text; a tool use becomes `Tool: <name>`.

```
{
  "chatId": 123,
  "messages": [
    {
      "id": 456,
      "role": "user",
      "content": "Explain what this code does",
      "createdAt": "2026-09-04T10:00:00Z",
      "tokens": { "input": 120, "output": 0 },
      "cost": 0.0001
    },
    {
      "id": 457,
      "role": "assistant",
      "content": "This module handles ...",
      "createdAt": "2026-09-04T10:00:20Z",
      "tokens": { "input": 140, "output": 320 },
      "cost": 0.003
    }
  ],
  "since": "2026-09-04T10:00:00Z"
}
```

Examples:

```
# All messages
curl http://localhost:5173/api/remote/chats/123/messages \
  -H "X-API-Key: my-secret-api-key"

# Only the new ones from a given timestamp
curl "http://localhost:5173/api/remote/chats/123/messages?since=2026-09-04T10:00:10Z" \
  -H "X-API-Key: my-secret-api-key"
```

### The prompt queue

Five commands under `/api/remote/chats/{chatId}/queue` manage the prompts waiting to be sent to the agent. Unlike the internal queue commands, the remote ones are stricter: an unknown chat, or a prompt that is not in the queue (never was there, belongs to another chat, or has already been dispatched), is a `404`; empty or whitespace-only text is a `400`.

#### Listing the queue

```
GET /api/remote/chats/{chatId}/queue
```

The queue in dispatch order: `id`, `prompt`, `isPaused`, `model` (a per-message override), `createdAt`. Paused prompts are included and marked with `isPaused`.

```
{
  "chatId": 123,
  "prompts": [
    {
      "id": 456,
      "prompt": "Refactor the auth module",
      "isPaused": false,
      "model": "",
      "createdAt": "2026-09-04T10:00:00Z"
    },
    {
      "id": 457,
      "prompt": "Then update the README",
      "isPaused": true,
      "model": "claude-sonnet",
      "createdAt": "2026-09-04T10:01:00Z"
    }
  ]
}
```

#### Append a prompt to the end of the queue

```
POST /api/remote/chats/{chatId}/queue
```

Appends a prompt to the end of the queue (like any other message source). `prompt` is required and non-empty; `model` is an optional model override for that one message. The `201` response is the added prompt with its id.

```
curl -X POST http://localhost:5173/api/remote/chats/123/queue \
  -H "X-API-Key: my-secret-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Now add unit tests for the refactored module"
  }'
```

#### Rewrite the text of one prompt

```
PUT /api/remote/chats/{chatId}/queue/{promptId}
```

Changes only the text: the row’s id, position, model, and pause state are preserved. `400` for empty text; `404` when the prompt is not in this chat’s queue or has already been dispatched.

```
curl -X PUT http://localhost:5173/api/remote/chats/123/queue/456 \
  -H "X-API-Key: my-secret-api-key" \
  -H "Content-Type: application/json" \
  -d '{ "prompt": "Refactor the auth module and update the docs" }'
```

#### Delete one prompt

```
DELETE /api/remote/chats/{chatId}/queue/{promptId}
```

Removes the prompt from the queue; success is `204 No Content`.

```
curl -X DELETE http://localhost:5173/api/remote/chats/123/queue/456 \
  -H "X-API-Key: my-secret-api-key"
```

#### Replace the whole queue

```
PUT /api/remote/chats/{chatId}/queue
```

The submitted array becomes the whole queue. The replacement rules:

- an item whose `id` matches an existing row keeps its identity — the same row, model, and pause state (unless `isPaused` is set explicitly);
- an item without an `id` (or with `id: 0`) creates a new prompt;
- existing prompts the array does not mention are deleted — including ones other sources added after your last read (last writer wins);
- the array’s order becomes the dispatch order — reordering the array reorders the queue;
- an empty array (`[]`) clears the queue;
- the id of an already dispatched prompt is skipped (not resurrected) — sending it again would duplicate a message the agent is already processing.

`400` errors: the array is missing; any item has empty text; the same positive `id` appears twice.

```
curl -X PUT http://localhost:5173/api/remote/chats/123/queue \
  -H "X-API-Key: my-secret-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "prompts": [
      { "id": 457, "prompt": "Refactor the auth module (edited)" },
      { "prompt": "Then update the README" }
    ]
  }'
```

### The bot list

```
GET /api/remote/bots
```

Read-only: every configured bot returns with `id`, `title`, `enabled`, `running`, and `pollingError` — enough for an external program to show bots with status dots. No tokens, no prompts, no management: bots are configured only in the Xedant Agent interface.

```
{
  "bots": [
    {
      "id": "release-bot",
      "title": "Release Bot",
      "enabled": true,
      "running": true,
      "pollingError": null
    }
  ]
}
```

## Chat statuses

| created | chat created without an initial prompt, waiting for input |
| --- | --- |
| queued | chat created with a prompt, the message is in the queue |
| processing | the agent is processing a message |
| completed | all messages are processed, the chat is idle |
| error | a processing error (in the remote API the error field in the response is always empty) |

## The extended surface unlocked by the API key

**The same key opens far more than `/api/remote`.** Sent to these paths, it acts under the **Manager** role — the same as signing in as the manager:

- `/api/chats`, `/api/chat` — reading chats and working with them;
- `/svelteChatHub` — the live SignalR connection (see below);
- `/api/skills` — skills (list, select);
- `/api/models` — models (list, select);
- `/api/autofix` — autofix status and toggle;
- `/api/settings` — settings (`settings/all`, `settings/set`);
- `/api/skills-settings` — skill colors from `skills.yml`;
- `/api/git` — status and commit of the working repository;
- `/api/MessageQueue` — edit, delete, and pause the message queue;
- `/api/analytics` — chat analytics (read-only);
- `/api/chat-labels` — chat labels: list, create, change, and assign (the `labels.yml` file);
- `/api/files` — file uploads into the agent’s working folder (images and clipboard files);
- `/api/speechrecognition` — audio transcription through the speech recognition engines.

When the key is not configured or not sent, these paths work as before — through the normal interface sign-in. Nothing breaks: API-key access and user logins coexist.

## Subscribing to live events (SignalR)

Instead of polling the status forever, an external system can subscribe to live events — receive notifications the moment something happens in the chats. Connect to `/svelteChatHub`, passing the key in the `X-API-Key` header or as the `api_key` address parameter (for WebSocket the address parameter is mandatory — headers cannot be set):

```
const connection = new signalR.HubConnectionBuilder()
    .withUrl("http://localhost:5173/svelteChatHub?api_key=my-secret-api-key")
    .build();

connection.on("messageUpdate", (event) => {
    console.log("New message in chat", event.chatId, event.message.content);
});

connection.start();
```

Events arrive on changes: new messages, chat updates (including processing statuses), message-queue and prompt-queue changes, and changes to settings, skills, and models. Connections reconnect gracefully — a drop does not lose the subscription. This is exactly how Research Agent shows the Xedant Agent interface.

## An example workflow

A typical scenario: create a chat → poll the status → collect the messages → append a prompt to the queue → replace the queue. The full cycle in Python:

```
import time, requests

API_KEY = "my-secret-api-key"
BASE = "http://localhost:5173/api/remote"
H = {"X-API-Key": API_KEY}

# 1. Create a chat — the prompt is queued
resp = requests.post(f"{BASE}/chats", headers=H, json={
    "prompt": "Add error handling to the login endpoint",
    "title": "Error Handling",
    "model": "claude-sonnet"
})
chat_id = resp.json()["chatId"]

# 2. Poll the status until it finishes
while True:
    status = requests.get(f"{BASE}/chats/{chat_id}/status", headers=H).json()["status"]
    if status in ("completed", "error"):
        break
    time.sleep(2)

# 3. Collect the replies (incrementally via since)
messages = requests.get(f"{BASE}/chats/{chat_id}/messages", headers=H).json()["messages"]
for msg in messages:
    if msg["role"] == "assistant":
        print(msg["content"])

# 4. Append a follow-up to the end of the queue
requests.post(f"{BASE}/chats/{chat_id}/queue", headers=H,
    json={"prompt": "Now add unit tests"})

# 5. Replace the whole queue
requests.put(f"{BASE}/chats/{chat_id}/queue", headers=H, json={
    "prompts": [
        {"prompt": "Fix the auth flow"},
        {"prompt": "Update the README"}
    ]
})
```

## Error format

All errors follow one format, `{error, message, details}`:

```
{
  "error": "NotFound",
  "message": "Chat 123 not found",
  "details": ""
}
```

- `Unauthorized` — 401, the key is missing or wrong;
- `BadRequest` — 400, an invalid request body (an empty prompt, a duplicate id, a missing array);
- `NotFound` — 404, a chat or prompt not found;
- `Remote API is disabled` — 503, the `AGENT_API_KEY` variable is not set;
- `InternalServerError` — 500, an unexpected server error (`details` carries the exception text).

## Related sections

- [Remote Control through Yandex.Disk](/agents/agent/docs/remote-control) — the file queue and the AgentRemote desktop app;
- [Managing the queue from the interface](/agents/agent/docs/prompts) — the prompts dialog and the server-side AutoSend;
- [Models](/agents/agent/docs/models) — which models can be passed in `model`.

**[← Remote Control](/agents/agent/docs/remote-control)**

**[Prompts →](/agents/agent/docs/prompts)**
