Agents & Sessions
Manage reusable custom chat agents, and enumerate every session across all generation types.
Two workspace inventories that sit alongside Projects:
- Agents — reusable, named personas for the chat tool. An agent's
descriptionis the system instruction the model adopts. - Sessions — the containers every generation lands in. One endpoint enumerates them across all 17 generation and chat types.
Endpoint
GET /api/v1/agents
POST /api/v1/agents
PUT /api/v1/agents/:id
DELETE /api/v1/agents/:id
GET /api/v1/sessions
PATCH /api/v1/sessions/:sessionId/projectListing Agents
GET /api/v1/agents| Parameter | Type | Required | Description |
|---|---|---|---|
search | string | No | Case-insensitive filter on the agent name. Regex-escaped server-side, so user-typed text is safe. Also accepted as query. |
Returns your personal agents, any agents scoped to your active organization, and the global preset agents. Sort order is global presets first, then personal agents, then organization agents, newest-first within each group. There is no pagination.
curl "https://api.kolbo.ai/api/v1/agents?search=director" \
-H "X-API-Key: YOUR_API_KEY"Response:
{
"success": true,
"agents": [
{
"id": "66a1c8a2e4b0a3c1d9f5e001",
"name": "Creative Director",
"description": "You are a senior creative director. Turn any brief into a structured shot list…",
"emoji": "🎬",
"thumbnail": null,
"is_global": false,
"file_ids": [],
"created_at": "2026-07-12T16:38:55.871Z",
"updated_at": "2026-07-12T16:38:55.871Z"
}
],
"count": 1
}is_global: true marks a protected system preset. Presets show up in the list so you can reference
them, but they cannot be edited or deleted — both return 403.
Creating an Agent
POST /api/v1/agents| Field | Type | Required | Description |
|---|---|---|---|
name | string | No* | Agent name. Omitted → generated from description (falls back to "Custom Agent"). Trimmed server-side. |
description | string | No* | The persona and instructions — this becomes the system prompt in every conversation that uses the agent. Trimmed; an empty result is stored as null. |
emoji | string | No | Emoji avatar. Omitted or empty → auto-picked from the name and description (falls back to 🤖). |
thumbnail | string | No | Thumbnail image URL. |
file_ids | array of string | No | Media file ObjectIds to attach as the agent's retrieval corpus. Ignored unless it is a JSON array. |
* At least one of name / description must be a non-empty string, otherwise 400.
The API route applies no length limit to name or description. The in-app editor validates
them at 100 and 2,000 characters respectively, but that validation sits on the web app's own route,
not this one. Stay inside those bounds if the user needs to keep editing the agent in the app.
curl -X POST https://api.kolbo.ai/api/v1/agents \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Creative Director",
"description": "You are a senior creative director. Turn any brief into a structured shot list with camera, lens, and lighting notes."
}'The response is { "success": true, "agent": { … } } in the shape shown above. Retrieval embeddings for any attached files are built in the background, so a freshly created agent is usable immediately but its file knowledge lands a moment later.
Your plan's custom-agent cap is enforced — exceeding it returns 403 with
{ "success": false, "error": "Custom agent limit reached. …" }, or "Custom agent creation not available on your current plan." when the plan allows none. Match on the message, not a code: the
SDK error envelope drops the underlying code (CUSTOM_AGENT_LIMIT_EXCEEDED) and data fields on
this route.
Agent create / update / delete are limited to 10 requests per minute — the same bucket the video, music, speech, chat, and upload endpoints draw from, so creating agents eats into your generation allowance. Listing is limited to 120 per minute, shared with the whole media-library surface. Both counters are keyed by authenticated user, so several API keys on one account share them. Full bucket map: Errors & Limits.
Agents are created through the API but selected in the app. POST /v1/chat has no field for
an agent id — to apply a persona from an API-driven conversation, pass system_prompt on the
Chat request instead. Agents you create here are for reuse in the web
app's chat tool.
file_ids are MediaFile ObjectIds, and the value is only applied when it is a JSON array —
a bare string is silently ignored. Sending it replaces the whole file list rather than appending.
Updating an Agent
PUT /api/v1/agents/:id| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | New name. |
description | string | No | New persona/instructions — replaces the old description. |
emoji | string | No | New emoji avatar. |
thumbnail | string | No | New thumbnail URL. |
file_ids | array of string | No | Replacement list of media file ObjectIds. Ignored unless it is a JSON array. |
At least one of those five fields must be present, otherwise 400. You must own the agent — someone else's agent returns 403, a global preset returns 403, and a missing id returns 404. Fields outside that list are dropped, so is_global, ownership, and organization scope cannot be reassigned through this route.
Retrieval embeddings are rebuilt in the background after every successful update.
Deleting an Agent
DELETE /api/v1/agents/:idOwner only; global presets are protected.
{ "success": true, "deleted": true, "id": "66a1c8a2e4b0a3c1d9f5e001" }This is a permanent delete, not a soft delete — the agent record and its retrieval embeddings are removed. Conversations that used the agent are untouched.
Listing Sessions
GET /api/v1/sessionsEnumerates sessions across all generation and chat types, newest activity first.
| Parameter | Type | Required | Description |
|---|---|---|---|
project_id | string | No | Restrict to one project. Must be a valid ObjectId (400 otherwise) and you must be a member (403 otherwise). Also accepted as projectId. |
type | string | No | One of the session types below. An unknown value returns 400 listing the valid keys. |
page | number | No | 1-indexed. Default 1. |
limit | number | No | Default 20, max 50. |
curl "https://api.kolbo.ai/api/v1/sessions?project_id=65f1c8a2e4b0a3c1d9f5e123&type=image&limit=50" \
-H "X-API-Key: YOUR_API_KEY"Response:
{
"success": true,
"sessions": [
{
"session_id": "65f1c8a2e4b0a3c1d9f5e999",
"name": "API Image Generation - 2026-07-12",
"type": "image",
"project_id": "65f1c8a2e4b0a3c1d9f5e123",
"created_at": "2026-07-12T16:38:55.871Z",
"updated_at": "2026-07-12T18:02:11.004Z"
}
],
"pagination": { "page": 1, "limit": 50, "returned": 1, "has_more": false }
}Scope depends on project_id. With project_id, you get every member's sessions in that
shared project, and each row is stamped with owner ({ userId, name, avatar }) plus is_mine.
Without it, you get only your own sessions across all projects.
Several session types share one underlying store, so when you do not pass type, a row's
type can come back as a pipe-joined string — for example
video_from_image|elements|first_last_frame or image_edit|global_image_edit. Match on
substring, not equality, when you filter client-side. Passing type always returns that single key.
pagination reports returned and has_more rather than a total count.
Session types
The same 17 keys are valid for the type filter here and for the type hint on the move endpoint:
image, video, video_from_image, music, speech, image_edit, sound, creative_director, chat, elements, first_last_frame, lipsync, video_from_video, transcription, global_image_edit, global_video_edit, shorts
3D generations are project-scoped and have no session, so they never appear in this list.
A session's name comes back as null when it was never named, and project_id as null in the rare case a session is not attached to a project.
Moving a Session
PATCH /api/v1/sessions/:sessionId/project relocates a session and all of its media to another project. Full reference: Projects → Moving a Session Between Projects.
MCP Tools
The matching @kolbo/mcp tools are list_agents, create_agent, update_agent, delete_agent, list_sessions, and move_session.
1. list_agents { search: "director" } -> resolve a name to an agent id
2. list_sessions { project_id } -> find work that landed in the wrong place
3. move_session { session_id, project_id }The MCP create_agent / update_agent tools do not expose file_ids — attach a retrieval corpus
through the HTTP API or the web app. create_agent also makes description required (and
name optional), which is stricter than the HTTP route, where either one alone is enough.
get_session_usage is not about these sessions. Despite the name, it does not read anything
from list_sessions — it aggregates credit spend tagged with the X-Kolbo-Caller-Session-Id
header (a per-app-launch id) and returns a total plus per-tool and per-model breakdowns. Only the
Kolbo Code desktop app and the sidecar that spawns the MCP set that header, so a plain SDK or MCP
caller gets a 400 telling it no caller-session id was forwarded — the tool takes no arguments, so
it cannot supply one (the underlying endpoint also accepts a ?caller_session_id= query param, but
only records tagged with that id at generation time are counted). It is served by the app API
(GET /api/credit-usage/by-caller-session), not by any /api/v1 SDK route. For a plain balance,
use GET /api/v1/account/credits — see Credits & Billing.