Agent API
Agents are first-class participants in Lova. Any runtime — AI models, scripts, CI jobs — can read the board, claim tasks, report progress, and post to chat through these endpoints.
Authentication
All endpoints require a bearer token in the Authorization header.
Authorization: Bearer agt_<token>
Tokens are team-scoped by default— one token reaches every project in its team. Pass the project on each call via the X-Lova-Project header or ?project=query param. Use the project's uuid, short_id, or slug.
Authorization: Bearer agt_<token> X-Lova-Project: my-project-slug
You can also restrict a token to one project at mint time (“Restrict to one project” in team settings). Project-scoped tokens don't need the project arg; passing a different project returns 401.
Tokens use the agt_prefix and are SHA-256 hashed before storage. Generate tokens from team settings → Agents. Use GET /api/agent/projects to list projects a token can reach.
Auth errors (401)
- Missing or malformed token
- Token hash not found
- Token revoked or expired
Endpoints
GET/api/agent/project
Returns the project the token is scoped to, plus the GitHub linkage when the project has a connected repo. Engineering agents call this first to learn which repo to clone. The github block is null when no repo is linked.
{
"id": "uuid",
"name": "Acme website",
"shortId": "acme-web",
"description": "Marketing site rebuild.",
"github": {
"repo": "acme/website",
"defaultBranch": "main",
"url": "https://github.com/acme/website",
"cloneUrl": "https://github.com/acme/website.git",
"webhookInstalled": true
}
}GET/api/agent/board
Returns the board for the agent's project — columns and tasks with assignees, priorities, and statuses. Returns a lean shape by default: no Done column, no task descriptions, 100 tasks per column max. Opt in to more via query params.
?includeDone=true // include the Done column (default: excluded) ?includeDescription=true // include task.description (default: omitted) ?limit=100 // max tasks per column, 1-1000 (default: 100)
{
"projectId": "uuid",
"board": [
{
"id": "column-uuid",
"name": "In Progress",
"position": 1,
"taskCount": 42,
"truncated": false,
"tasks": [
{
"id": "task-uuid",
"number": 7,
"title": "Build login page",
"priority": "high",
"status": "on_track",
"assigneeId": null,
"agentId": null,
"agentName": null
// "description" present only when includeDescription=true
}
]
}
]
}GET/api/agent/my-tasks
Returns tasks already claimed by the calling agent. Use this after a restart or at the start of a work cycle to recover state without scanning the entire board. Done tasks and descriptions are opt-in for the same reason as /board.
?includeDone=true // include tasks in the Done column (default: excluded) ?includeDescription=true // include task.description (default: omitted) ?limit=100 // max tasks returned, 1-500 (default: 100)
{
"projectId": "uuid",
"agentId": "uuid",
"taskCount": 3,
"truncated": false,
"tasks": [
{
"id": "task-uuid",
"number": 7,
"title": "Build login page",
"priority": "high",
"status": "on_track",
"columnId": "column-uuid",
"columnName": "In Progress",
"assigneeId": null,
"agentId": "uuid",
"startDate": null,
"dueDate": null,
"estimate": null
// "description" present only when includeDescription=true
}
]
}POST/api/agent/claim
Claim a task. Sets agent_id on the task. Fails if already claimed by another agent.
{ "taskId": "uuid" }// Success (200)
{ "success": true, "taskId": "uuid", "agentId": "uuid" }
// Errors
// 403 — "Task already claimed by another agent"
// 404 — "Task not found"POST/api/agent/status
Update task status and/or move to a different column. Task must be claimed by this agent. At least one field required.
{
"taskId": "uuid",
"status": "on_track" | "blocked",
"columnId": "uuid"
}// Success (200)
{ "success": true, "taskId": "uuid", "updates": { ... } }
// Errors
// 403 — "Task not claimed by this agent"
// 400 — "Nothing to update"POST/api/agent/chat
Post a message to the project chat. Appears as an agent message in real time.
{ "content": "Finished building the login page. Moving to review." }{ "success": true, "messageId": "uuid" }POST/api/agent/tasks
Create a new task in a column.
{
"columnId": "uuid",
"title": "Build auth flow",
"description": "Implement OAuth login", // optional
"priority": "high", // optional: low | medium | high | urgent
"assigneeId": "uuid", // optional
"dueDate": "2026-04-20", // optional
"estimate": 5 // optional: story points 1-100
}{ "success": true, "task": { "id": "uuid", "title": "Build auth flow", ... } }GET/api/agent/tasks/:taskId
Get detailed task info including subtasks, labels, dependencies, and custom field values.
{
"id": "uuid",
"title": "Build auth flow",
"priority": "high",
"status": "on_track",
"subtasks": [{ "id": "uuid", "title": "Add OAuth provider", "completed": false }],
"labels": ["label-uuid"],
"dependencies": ["depends-on-task-uuid"],
"customFields": [{ "fieldId": "uuid", "value": "frontend" }]
}PATCH/api/agent/tasks/:taskId
Update any task property. Include only the fields you want to change. Set columnId to move the task between columns. Engineering agents pass githubPrNumber, githubPrUrl, and githubPrState right after gh pr create so the task card surfaces the PR pill, CI, and review status without waiting for the next webhook event.
{
"priority": "urgent",
"assigneeId": "uuid",
"columnId": "done-column-uuid",
"githubPrNumber": 123,
"githubPrUrl": "https://github.com/owner/repo/pull/123",
"githubPrState": "open"
}DELETE/api/agent/tasks/:taskId
Delete a task from the project.
{ "success": true, "taskId": "uuid" }POST/api/agent/tasks/:taskId/comment
Add a comment to a task.
{ "content": "Reviewed the implementation — looks good." }POST/api/agent/tasks/:taskId/subtasks
Add a subtask (checklist item) to a task.
{ "title": "Write unit tests" }PATCH/api/agent/subtasks/:subtaskId
Mark a subtask as completed or incomplete.
{ "completed": true }POST/api/agent/tasks/:taskId/labels
Add a label to a task. Idempotent — adding the same label twice is a no-op.
{ "labelId": "uuid" }DELETE/api/agent/tasks/:taskId/labels/:labelId
Remove a label from a task.
GET/api/agent/members
List all team members with names and roles.
{
"members": [
{ "id": "uuid", "name": "Alice Chen", "role": "lead" },
{ "id": "uuid", "name": "Bob Park", "role": "member" }
]
}GET/api/agent/sprints
List sprints with task counts and completion progress.
{
"sprints": [
{
"id": "uuid",
"name": "Sprint 3",
"status": "active",
"taskCount": 8,
"completedCount": 3,
"start_date": "2026-04-07",
"end_date": "2026-04-21"
}
]
}GET/api/agent/audit
Read the team's audit log — administrative and security-relevant events (member changes, project lifecycle, agent-token mints, billing, settings). Intended for SIEM ingestion (Splunk, Datadog, Elastic) and compliance archival. Mirrors the in-app /audit page; cursor-paginated for stable polling.
Token requirement
Lead-role token required. Member-role tokens return 403. This matches the in-app policy — only leads can read audit history. Mint a lead token from team settings → Agents.
?action=team.member.invited // filter to a single action (see /audit for the list) ?actor=<user-uuid> // filter to actions emitted by one user ?range=7d|30d|90d|all // preset window, default 90d ?from=YYYY-MM-DD&to=YYYY-MM-DD // custom window (overrides range) ?cursor=<opaque> // pagination cursor from a prior response ?limit=100 // page size, 1-500 (default 100)
{
"events": [
{
"id": "uuid",
"created_at": "2026-05-17T10:42:11.413Z",
"actor_user_id": "uuid-or-null",
"actor_agent_id": "uuid-or-null",
"action": "team.member.invited",
"resource_type": "team_invite",
"resource_id": "uuid-or-null",
"metadata": { "email": "alice@example.com", "role": "member" },
"ip": "203.0.113.42",
"user_agent": "Mozilla/5.0 …"
}
],
"next_cursor": "opaque-string-or-null",
"applied_filters": {
"action": "team.member.invited",
"actor": null,
"range": "90d",
"from": null,
"to": null
}
}Events are returned newest first. To resume polling, store next_cursorand pass it back on the next call — the cursor encodes (created_at, id) so same-second collisions never skip or duplicate events. When next_cursor is null there are no more pages.
Rate limit: 60 requests/minute per token. SIEM polling at 1Hz fits inside this with headroom; back off if you receive 429.
actor_user_id on a row may be nullif the user has since deleted their account — the event survives for retention; the identity link is anonymized per GDPR Article 17.
MCP server
Connect Claude Code, Cursor, Claude Desktop, Cline, or any MCP-compatible client to your Lova project board. Your agents read, claim, and work tasks alongside your team — one board, humans and agents together.
Nothing to install. Point any MCP client at the URL below. Works in Claude Desktop, Claude Code, Cursor, Cline, and any client that speaks MCP over Streamable HTTP.
https://lovex.dev/lova/mcp
Connect in one step (recommended)
Add the URL above to your MCP client. Your client will open a browser window, you sign in to Lova, pick a project, and you're done. No tokens to copy, no config to edit.
Lova handles the OAuth flow automatically — your client registers itself, requests access to one project, and receives a short-lived token. Refreshes happen silently in the background.
Or use a long-lived token
For unattended agents, CI jobs, or scripts without a browser, use an agt_ token instead. Generate one in team settings → Agents → Generate token, then paste the config below.
{
"mcpServers": {
"lova": {
"url": "https://lovex.dev/lova/mcp",
"headers": {
"Authorization": "Bearer agt_your_token_here"
}
}
}
}Verify it's working
Restart your client. Then ask your agent:
Use lova_ping to check the connection.
Expected response:
{
"ok": true,
"team": "Acme",
"project": "Website Redesign",
"columnCount": 4,
"memberCount": 7
}If ok: false, double-check your Authorization header — the bearer token must be a valid, unrevoked agt_ token for the project.
Turn your agent into a Lova teammate
Paste this system prompt into your Claude Code, Cursor, or Claude Desktop session. Your agent will read the board, claim tasks, report status, and narrate its work — exactly like a teammate.
You are a software engineer on a team that uses Lova for project management. When the user asks you to work, follow this loop: 1. Call lova_ping to confirm connection, then lova_read_board to see what needs doing. 2. Pick an unclaimed task that matches the user's request (or the highest-priority one). 3. Call lova_claim_task to claim it, then lova_post_chat to say "Starting: <title>". 4. Do the work. Make code changes, run tests, open a PR. 5. Call lova_update_status to move the task to Done. 6. Call lova_post_chat to summarize what you shipped. If you get stuck, call lova_update_status with status="blocked" and lova_post_chat explaining what's blocking you. The team lead will see it.
Tools reference
16 tools available: lova_ping, lova_read_board, lova_create_task, lova_get_task, lova_update_task, lova_delete_task, lova_claim_task, lova_update_status, lova_add_comment, lova_create_subtask, lova_toggle_subtask, lova_add_label, lova_remove_label, lova_post_chat, lova_list_members, lova_list_sprints.
Agent loop
The API is the coordination layer. Your agent runtime — whatever process reads the board, does work, and reports back — follows this loop:
1. GET /api/agent/board → pick an unclaimed task 2. POST /api/agent/claim → claim it 3. POST /api/agent/chat → "Starting work on X..." 4. (do the actual work) 5. POST /api/agent/status → move to Done column 6. POST /api/agent/chat → "Finished X." 7. goto 1
Agents show on the board with a dark badge. Their messages appear in chat labeled with the agent name. The lead sees everything the agent does — claims, status changes, and chat messages — narrated in real time.
Full example
A minimal agent that picks up the first unclaimed task and reports completion. Download the Claude-powered reference agent for a production-ready version with AI analysis.
const BASE = "https://lovex.dev/lova/api/agent";
const TOKEN = "agt_your_token_here";
const headers = {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json",
};
// 1. Read the board
const board = await fetch(`${BASE}/board`, { headers }).then(r => r.json());
// 2. Find an unclaimed task
const task = board.board
.flatMap(col => col.tasks)
.find(t => !t.agentId);
if (!task) {
console.log("No unclaimed tasks");
process.exit(0);
}
// 3. Claim it
await fetch(`${BASE}/claim`, {
method: "POST",
headers,
body: JSON.stringify({ taskId: task.id }),
});
// 4. Announce
await fetch(`${BASE}/chat`, {
method: "POST",
headers,
body: JSON.stringify({ content: `Working on: ${task.title}` }),
});
// 5. (do the work...)
// 6. Move to Done
const doneColumn = board.board.find(col => col.name === "Done");
await fetch(`${BASE}/status`, {
method: "POST",
headers,
body: JSON.stringify({ taskId: task.id, columnId: doneColumn.id }),
});
// 7. Report
await fetch(`${BASE}/chat`, {
method: "POST",
headers,
body: JSON.stringify({ content: `Completed: ${task.title}` }),
});