API examples
Worked GVenta Kanban Board API examples: an agent's work loop, bulk story creation, attachment upload, deep search, and creating and reading a project board.
Documented from GVenta Kanban Board version 2.1.15.
These examples show complete request and response exchanges against the GVenta Kanban Board REST API. All requests go to https://your-instance.example/api/v1 with an Authorization: Bearer <token> header. Responses are illustrative: they use the real envelope and real field names, but the exact set of fields returned by your instance may include more than shown here. See API getting started for authentication, the envelope and error codes, and the endpoint reference for every endpoint.
An agent’s work loop
The typical loop for an AI agent (or any automation) is: ask for work, claim it, report progress, move the card, and log a heartbeat.
1. Ask for the next item
GET /api/v1/work/next returns the highest-priority unassigned backlog stories, ordered Critical → High → Medium → Low, then oldest-first. Ask for one at a time.
GET /api/v1/work/next?limit=1 HTTP/1.1
Host: your-instance.example
Authorization: Bearer <token>
Accept: application/json
{
"success": true,
"data": [
{
"id": 4821,
"project_id": 12,
"title": "Add sitemap generation to the build",
"type": "coding",
"priority": "high",
"status": "backlog",
"assigned_to": null,
"is_epic": false,
"due_date": "2026-09-01"
}
],
"meta": { "limit": 1 }
}
Add project to scope the queue to one project. The maximum limit is 20.
2. Claim it
Assign the story to the token’s own user (the ID from GET /api/v1/me). Assignment is validated: the user must be assignable to the project, or you receive a VALIDATION_ERROR.
PATCH /api/v1/stories/4821/assign HTTP/1.1
Host: your-instance.example
Authorization: Bearer <token>
Content-Type: application/json
{ "assigned_to": 37 }
{
"success": true,
"data": { "id": 4821, "assigned_to": 37 },
"meta": {}
}
Assigning triggers the story-assigned email to the new assignee, if they have that notification enabled.
3. Report progress in a comment
POST /api/v1/stories/4821/comments HTTP/1.1
Host: your-instance.example
Authorization: Bearer <token>
Content-Type: application/json
{
"content": "Starting on this. @maria I will need the staging URL when you have a moment.",
"urgent": false
}
{
"success": true,
"data": {
"id": 9902,
"story_id": 4821,
"content": "Starting on this. @maria I will need the staging URL when you have a moment.",
"is_urgent": false,
"parent_id": null
},
"meta": {}
}
Posting a comment parses the @maria mention, marks the comment read for the author, subscribes the author as a watcher and notifies other watchers; see Comments and mentions.
4. Move the card
POST /api/v1/stories/{id}/move moves a story to a different status and position. The matching lane is resolved automatically, so the board and the status never disagree.
POST /api/v1/stories/4821/move HTTP/1.1
Host: your-instance.example
Authorization: Bearer <token>
Content-Type: application/json
{ "status": "in_progress", "position": 0 }
{
"success": true,
"data": { "id": 4821, "status": "in_progress", "lane_id": 58, "position": 0 },
"meta": {}
}
5. Log a heartbeat
POST /api/v1/pulse requires agent and activity, and status must be success, warning or error. story_ids and duration_seconds are optional. It returns the stored entry with 201.
POST /api/v1/pulse HTTP/1.1
Host: your-instance.example
Authorization: Bearer <token>
Content-Type: application/json
{
"agent": "build-bot",
"activity": "Claimed story 4821 and moved it to In Progress",
"status": "success",
"story_ids": [4821],
"duration_seconds": 4
}
{
"success": true,
"data": {
"id": 771,
"agent": "build-bot",
"activity": "Claimed story 4821 and moved it to In Progress",
"status": "success",
"story_ids": [4821],
"duration_seconds": 4
},
"meta": {}
}
Pulse is inert: logging to it has no side effects. It exists so people can see what the agent has been doing on the Pulse page.
Bulk-creating stories
POST /api/v1/stories/bulk creates many stories in one call and returns per-item results plus a created/failed summary. Each item needs project or project_id plus a title, and accepts the same optional fields as single creation: description, type, status, priority, story points, due date, assignee, epic ID and the epic flag.
POST /api/v1/stories/bulk HTTP/1.1
Host: your-instance.example
Authorization: Bearer <token>
Content-Type: application/json
{
"stories": [
{ "project": "acme-website", "title": "Write launch announcement", "type": "documentation", "priority": "medium" },
{ "project": "acme-website", "title": "Fix mobile nav overlap", "type": "bug", "priority": "high" },
{ "project": "acme-website", "title": "", "type": "coding" }
]
}
When some items succeed and others fail, the response is 207 Multi-Status:
{
"success": true,
"data": [
{ "success": true, "data": { "id": 4830, "title": "Write launch announcement" } },
{ "success": true, "data": { "id": 4831, "title": "Fix mobile nav overlap" } },
{ "success": false, "error": "VALIDATION_ERROR", "message": "Title is required" }
],
"meta": { "created": 2, "failed": 1 }
}
Check meta.failed and walk data to find which items were rejected. POST /api/v1/stories/bulk-move is the companion call for moving many stories to one status at once, logging each move.
Uploading an attachment
Attachments are sent as multipart/form-data, not JSON. The file passes through the full validation pipeline: extension allow-list, hard-block list, size limit and magic-byte verification.
POST /api/v1/stories/4821/attachments HTTP/1.1
Host: your-instance.example
Authorization: Bearer <token>
Content-Type: multipart/form-data; boundary=----boundary
------boundary
Content-Disposition: form-data; name="file"; filename="client-brief-v3.pdf"
Content-Type: application/pdf
<binary file contents>
------boundary--
{
"success": true,
"data": {
"id": 2210,
"story_id": 4821,
"filename": "client-brief-v3.pdf",
"mime_type": "application/pdf",
"size": 348112,
"download_url": "https://your-instance.example/api/v1/attachments/2210/download"
},
"meta": {}
}
A rejected file returns UPLOAD_ERROR with a plain-English reason:
{ "success": false, "error": "UPLOAD_ERROR", "message": "File type not allowed: exe" }
The download_url is on your instance’s own domain; the file is streamed through the application and never served from storage directly. GET /api/v1/stories/{id}/attachments lists a story’s files. The original filename is searchable through deep search.
Deep search
GET /api/v1/stories/search searches titles, descriptions, every comment, every attachment filename and every task title and description, filtered to the projects the token owner can access. It supports project, status, priority, type and assignee filters and paginates with page and per_page.
GET /api/v1/stories/search?search=client-brief&project=acme-website&page=1&per_page=20 HTTP/1.1
Host: your-instance.example
Authorization: Bearer <token>
Accept: application/json
{
"success": true,
"data": [
{
"id": 4821,
"project_id": 12,
"title": "Add sitemap generation to the build",
"type": "coding",
"priority": "high",
"status": "in_progress",
"assigned_to": 37
}
],
"meta": { "page": 1, "per_page": 20, "total": 1 }
}
This story matched because of its attachment filename, not its title. For a lighter title-oriented lookup with a two-character minimum, use GET /api/v1/search; for full-text comment search with highlighted snippets, use GET /api/v1/comments/search.
Creating a project and reading its board
POST /api/v1/projects creates a project, enrolls the caller as Owner and provisions the four default lanes, exactly as the web form does.
POST /api/v1/projects HTTP/1.1
Host: your-instance.example
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "Acme Website Relaunch",
"description": "Q4 marketing site rebuild for Acme.",
"repo_slug": "acme/website"
}
{
"success": true,
"data": {
"id": 12,
"name": "Acme Website Relaunch",
"slug": "acme-website-relaunch",
"description": "Q4 marketing site rebuild for Acme.",
"repo_slug": "acme/website"
},
"meta": {}
}
The slug is generated from the name. Restricted users receive FORBIDDEN. Now read the board, grouped by lane:
GET /api/v1/projects/acme-website-relaunch/board HTTP/1.1
Host: your-instance.example
Authorization: Bearer <token>
Accept: application/json
{
"success": true,
"data": {
"project": { "id": 12, "name": "Acme Website Relaunch", "slug": "acme-website-relaunch" },
"lanes": [
{ "id": 57, "name": "Backlog", "status_type": "to_do", "position": 0, "stories": [] },
{ "id": 58, "name": "In Progress", "status_type": "in_progress", "position": 1, "stories": [] },
{ "id": 59, "name": "Review", "status_type": "review", "position": 2, "stories": [] },
{ "id": 60, "name": "Done", "status_type": "done", "position": 3, "stories": [] }
]
},
"meta": {}
}
GET /api/v1/projects/{slugOrId} fetches a project by slug or numeric ID, and GET /api/v1/projects/{slug}/lanes returns just the lanes in display order. To customize the board, use the swim lane endpoints; deleting a lane that still contains stories returns 409.
Next steps
- API getting started — tokens, abilities, envelope and pagination.
- Endpoint reference — every endpoint, grouped by resource.
- Pulse and AI agents — the work queues and the Pulse page in context.