Agents & Sessions
Manage reusable custom chat agents (Skills), and enumerate, rename, and trash sessions across every generation type.
Two workspace inventories that sit alongside Projects:
- Agents (Skills) — reusable, named personas for the chat tool. The product now calls them Skills; every route is served under both
/api/v1/skills(current name) and/api/v1/agents(original path, kept working) with identical behavior. An agent'sdescriptionis 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/skills (alias: /api/v1/agents)
POST /api/v1/skills (alias: /api/v1/agents)
PUT /api/v1/skills/:id (alias: /api/v1/agents/:id)
DELETE /api/v1/skills/:id (alias: /api/v1/agents/:id)
GET /api/v1/sessions
PATCH /api/v1/sessions/:sessionId
DELETE /api/v1/sessions/:sessionId
POST /api/v1/sessions/:sessionId/restore
PATCH /api/v1/sessions/:sessionId/project
POST /api/v1/sessions/move
GET /api/v1/sessions/:sessionId/generations
POST /api/v1/sessions/:sessionId/generations/move
POST /api/v1/sessions/:sessionId/split
POST /api/v1/sessions/organize/undo/:operationIdThe examples below use /api/v1/agents; /api/v1/skills behaves identically.
Listing 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. |
page | number | No | 1-indexed. Default 1. |
limit | number | No | Default 50, max 100. |
Returns your personal agents, the global preset agents, and agents a member has explicitly shared to your active organization. Deleted agents are excluded. Rows are sorted newest-created first across all three groups.
The list is paginated: count is the number of rows on this page, and pagination
({ page, limit, total, has_more }) tells you whether to request the next page. List rows always return file_ids: [] because the list query loads only a file count,
not the file ids; the create and update responses carry the real file_ids.
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": "<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,
"pagination": { "page": 1, "limit": 50, "total": 1, "has_more": false }
}emoji holds a single emoji character; description, emoji and thumbnail come back as null when unset.
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 through this API.
Creating an Agent
POST /api/v1/agents| Field | Type | Required | Description |
|---|---|---|---|
name | string | No* | Agent name. Omitted or blank → generated from description. 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 blank → auto-picked from the name and description (falls back to the robot-face emoji). |
thumbnail | string | No | Thumbnail image URL. |
file_ids | array of string | No | Up to 10 media file ObjectIds to attach as the agent's retrieval corpus. Each must be a valid ObjectId of a non-deleted file you own. Ignored unless it is a JSON array. |
* At least one of name / description must be a non-empty string, otherwise 400.
Any other body field (for example an organization-sharing flag) is dropped by the API route, so an agent created here is always a personal agent.
The API route applies no length limit to name or description. The in-app editor validates
them at 100 and 8,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 indexing for any attached files is queued in the background, so a freshly created agent is usable immediately but its file knowledge lands a moment later.
Create runs a plan-limit check, but it currently allows an unlimited number of agents on every
plan, so there is no per-plan agent cap today. A view-only workspace context returns 403
(You don't have permission to create chat agents in this context). Match on the error
message, not a code: the SDK error envelope drops the underlying code and data fields on this route.
Agent create / update / delete are limited to 30 requests per minute — the same bucket the video, music, speech, chat, and upload endpoints draw from, so creating agents eats into your generation allowance (requests rejected with an error status do not consume it). 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. More than 10 ids, or a value that is not an ObjectId, returns
400 (Invalid agent files.); an id that is not a live file you own returns 403
(Some files are unavailable.). Sending the array 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 description — replaces the old one, and also replaces the persona instructions the chat tool uses (unless you send instructions). |
instructions | string | No | Persona instructions only, when they should differ from description. Wins over description for the persona. |
emoji | string | No | New emoji avatar. Sending emoji without thumbnail also clears the existing thumbnail. |
thumbnail | string | No | New thumbnail URL. |
file_ids | array of string | No | Replacement list of up to 10 media file ObjectIds you own (same validation as create). Ignored unless it is a JSON array. |
At least one of those six fields must be present, otherwise 400. Only your own personal agents can be updated: a missing id, a global preset, and an agent owned by someone else all return 404. Fields outside that list are dropped, so is_global, ownership, and organization scope cannot be reassigned through this route. If the agent is changed elsewhere (for example in the app) between the server's read and write, the update returns 409 (The skill changed. Reload it before saving your edits.) — re-send it.
The response is { "success": true, "agent": { … } }. Retrieval indexing is re-queued in the background after every successful update.
Deleting an Agent
DELETE /api/v1/agents/:idOwner only. A missing id, a global preset, or someone else's agent returns 404.
{ "success": true, "deleted": true, "id": "66a1c8a2e4b0a3c1d9f5e001" }The agent is soft-deleted (flagged deleted and hidden from every list) and its retrieval embeddings are removed. There is no restore route in the API, so treat it as final. Conversations that used the agent are untouched.
Listing Sessions
GET /api/v1/sessionsEnumerates sessions across all generation and chat types, newest activity first. Trashed (soft-deleted) sessions are excluded.
| 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. |
types | string or array | No | Several session types at once — comma-separated (types=image,video) or repeated (types=image&types=video). Additive with type; every key is validated the same way. |
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",
"types": ["image"],
"project_id": "65f1c8a2e4b0a3c1d9f5e123",
"created_at": "2026-07-12T16:38:55.871Z",
"updated_at": "2026-07-12T18:02:11.004Z",
"owner": { "userId": "65f1c8a2e4b0a3c1d9f5e001", "name": "Dana", "avatar": null },
"is_mine": true
}
],
"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, and rows carry neither field.
Several session types share one underlying store, so a row's type can come back as a pipe-joined
string of every requested type backed by that store — with no filter, video|video_from_image|elements|first_last_frame,
image|image_edit, or video_from_video|global_video_edit. The same keys arrive as an array in
types, so prefer that over splitting the string. Passing a single type returns that one key.
pagination reports returned and has_more rather than a total count.
Session types
The same 16 keys are valid for the type / types filters here and for the type hint on the rename, delete, restore, and move endpoints:
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
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.
Renaming, Trashing, and Restoring a Session
PATCH /api/v1/sessions/:sessionId body: { name, type? }
DELETE /api/v1/sessions/:sessionId query: type?
POST /api/v1/sessions/:sessionId/restore body: { type? }| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes (rename only) | New sidebar title. Trimmed; must be 1–200 characters, otherwise 400. |
type | string | No | Session type hint to speed up the lookup (one of the session types; unknown → 400). Accepted in the body or the query string on all three routes. Omit if unsure — every store is probed. |
- Rename needs edit permission on the session's project (the session owner always qualifies); otherwise
403. - Delete is a soft delete: the session is moved to the trash (
deletedAtis set) and disappears fromGET /sessions. Its generations and media are not touched. Needs delete permission (owner always qualifies). - Restore brings a trashed session back. It needs the same delete permission, and only finds sessions that are currently trashed.
An invalid id returns 400; a session that does not exist, is not attached to a project, or sits in a project you cannot access returns 404.
curl -X PATCH https://api.kolbo.ai/api/v1/sessions/65f1c8a2e4b0a3c1d9f5e999 \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Scene 03 — rooftop chase" }'Each route returns { "success": true, "session": { session_id, name, type, types, project_id } }; delete adds deleted: true and deleted_at, and restore adds restored: true. All three sit on the 120-requests-per-minute media-library limiter.
Moving a Session
PATCH /api/v1/sessions/:sessionId/project relocates a session with all of its generations and media to another project. To move many at once use POST /api/v1/sessions/move, and to reorganize generations between sessions use the generations / split routes. Full reference: Projects → Moving a Session Between Projects, Moving Many Sessions at Once, Reorganizing Work Between Sessions and Undoing a Reorganization.
The move, split, generation-move, and undo routes share a stricter 20 requests per minute limiter; listing a session's generations uses the 120/min media-library limiter.
MCP Tools
The matching @kolbo/mcp tools are list_skills, create_skill, update_skill, delete_skill (also registered under their original names list_agents, create_agent, update_agent, delete_agent, which take agent_id where the new ones take skill_id), plus list_sessions, rename_session, delete_session, restore_session, move_session, bulk_move_sessions, list_session_generations, move_generations_to_session, split_session, and undo_session_organization.
1. list_skills { search: "director" } -> resolve a name to a skill id
2. list_sessions { project_id } -> find work that landed in the wrong place
3. move_session { session_id, project_id }
4. delete_session { session_id } -> trash the empty leftover, if anyThe MCP create_skill / update_skill tools (and their *_agent aliases) do not expose file_ids
— attach a retrieval corpus through the HTTP API or the web app. create_skill also makes
description required (and name optional), which is stricter than the HTTP route, where
either one alone is enough. list_skills exposes only search, so it returns the first page
(up to 50 rows).
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
Kobi Code, which spawns the MCP with that id in its environment, sets it; on any other host the tool
takes no arguments and answers locally that per-session usage is unavailable, pointing you to
check_credits. 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.
Related
Projects
Create projects, move sessions, and manage the project knowledge base
Chat
Apply a one-off persona to an API conversation with system_prompt
AI Docs
Project-scoped documents you author through the API
Media Library
The media a session produced
Errors & Limits
Rate limits and the shared error envelope