Developers

Build on TaskNeuron

Automate your projects and tasks with the REST API, give AI assistants governed access over MCP, and react to changes with webhooks.

REST API

Quickstart

The TaskNeuron REST API gives you programmatic access to your projects and tasks: list and search work, create and update tasks, read a project's structure, and run AI plan generation. Requests and responses are JSON over HTTPS.

1. Create an API key

  1. Sign in and open Settings → Account → API keys.
  2. Give the key a name (for example, the script or service that will use it) and create it.
  3. Copy the key right away: it's shown only once. Keys start with tn_; treat them like passwords and keep them out of source control. You can revoke a key from the same screen at any time.

2. Make your first request

Every endpoint lives under https://www.taskneuron.ai/api/v1. Paths in the API reference are relative to that base URL.

Who am I?
export TASKNEURON_API_KEY="tn_..."

curl "https://www.taskneuron.ai/api/v1/me" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY"
Create a task (Node.js 18+)
const res = await fetch("https://www.taskneuron.ai/api/v1/tasks", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TASKNEURON_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ projectId: "<project id>", title: "Write the launch post", priority: "high" }),
});
if (!res.ok) {
  const { error } = await res.json();
  throw new Error(`${res.status} ${error.code ?? ""} ${error.message}`);
}
const created = await res.json();

The machine-readable description of the API is served at /api/v1/openapi.json (OpenAPI 3.1, no authentication needed). Import it into Postman, Insomnia or a client generator.

Authentication

Send your API key as a Bearer token in the Authorization header on every request. A key acts on behalf of the account that created it.

Header
Authorization: Bearer tn_your_api_key
  • A missing, malformed or revoked key returns 401.
  • A suspended account returns 403.
  • Connecting an AI assistant doesn't need an API key: assistants sign in with OAuth (see MCP).

Errors

Errors use standard HTTP status codes and a consistent JSON envelope. message is written for humans; code is a stable, machine-readable identifier and is present when the error has one, so check for it before branching on it.

Error response
{
  "error": {
    "message": "Rate limit exceeded. Try again shortly.",
    "code": "rate_limited"
  }
}

Status codes you should handle: 400 invalid input, 401 missing or invalid key, 403 not allowed, 404 not found, 409 conflict, 429 rate limited, and 5xx server errors (retry reads with backoff; check before retrying a create so you don't make it twice).

Common codes include invalid_input, not_found, invalid_cursor and rate_limited.

Pagination

List endpoints return a page of results plus a pagination object. limit sets the page size: it defaults to 50 and is capped at 200.

Page with the cursor. Pass the previous response's pagination.nextCursor as ?cursor= and keep going until nextCursor is null. A cursor is anchored to the last row you received, so items edited while you page can't shift past the page boundary.

?offset= still works for existing integrations but is deprecated: lists are ordered by last update, so a row edited between requests can move and be skipped by every page.

pagination object
"pagination": {
  "limit": 50,
  "offset": 0,
  "returned": 50,
  "hasMore": true,
  "nextCursor": "dGFzazp0c2tfMDFI..."
}
Read every task (Node.js)
async function listAllTasks() {
  const tasks = [];
  let cursor = null;
  do {
    const url = new URL("https://www.taskneuron.ai/api/v1/tasks");
    url.searchParams.set("limit", "200");
    if (cursor) url.searchParams.set("cursor", cursor);
    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.TASKNEURON_API_KEY}` },
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const page = await res.json();
    tasks.push(...page.tasks);
    cursor = page.pagination.nextCursor;
  } while (cursor);
  return tasks;
}

Rate limits

Each API key can make up to 120 requests per 60 seconds. Over the limit, requests get 429 with error code rate_limited and a Retry-After header giving the number of seconds to wait. Wait that long before retrying; for bulk jobs, spread requests out rather than sending them in bursts.

Some endpoints have their own limits on top of this, such as the number of AI plan jobs you can run at once (POST /plans returns 429 when too many are active).

AI assistants

Connect an AI assistant (MCP)

TaskNeuron runs a Model Context Protocol server, so Claude, ChatGPT, Cursor, Claude Code and other MCP clients can read and update your projects and tasks. The assistant signs in with OAuth: your browser opens, you approve access for your workspace, and the client gets a scoped token. You never paste an API key or secret into the assistant.

Before you start

MCP access is part of the Pro plan. Settings → MCP shows the server URL for your workspace and copy-ready config for each client.

MCP server URL

https://mcp.taskneuron.ai/mcp

Streamable HTTP transport, OAuth 2.1 with PKCE.

Claude

Claude Desktop: add TaskNeuron under mcpServers in claude_desktop_config.json (~/Library/Application Support/Claude/ on macOS, %APPDATA%\Claude\ on Windows), then restart Claude Desktop. It registers itself with TaskNeuron, opens the sign-in and consent page in your browser, and the TaskNeuron tools appear once you approve.

claude_desktop_config.json
{
  "mcpServers": {
    "taskneuron": {
      "type": "streamableHttp",
      "url": "https://mcp.taskneuron.ai/mcp"
    }
  }
}

Claude Code: add the server from your terminal, then run /mcp inside Claude Code to sign in.

Terminal
claude mcp add --transport http taskneuron https://mcp.taskneuron.ai/mcp

ChatGPT

  1. In ChatGPT, add a custom connector (a remote MCP server) and enter the MCP server URL above.
  2. Choose OAuth as the authentication method. ChatGPT registers itself with TaskNeuron automatically, so there's no client ID or secret to enter.
  3. Sign in to TaskNeuron in the window that opens and approve access.

Where custom connectors live in ChatGPT's settings, and which ChatGPT plans can add them, is controlled by OpenAI. If you don't see the option, check OpenAI's help center for connectors.

Cursor and other clients

In Cursor, open Settings → MCP → Add new MCP server, choose HTTP, and paste the MCP server URL. Any MCP client that supports Streamable HTTP with OAuth works the same way: TaskNeuron publishes its OAuth metadata and supports dynamic client registration, so the client discovers everything from the server URL.

Permissions

  • The consent screen lists the scopes the assistant asks for (for example tasks:read or projects:write). Each tool below shows the scope it needs.
  • Read tools work for any workspace role. Write tools need the member role or above.
  • Approvals marked human only (plan, task and checkpoint approvals) reject requests from AI assistants, so an agent can't approve its own work.
  • Every tool call is recorded in an audit log with the calling client, user and workspace.

Tools (46)

Tools are grouped by what they work on. List tools return up to 50 rows with a nextCursor; keep calling with it until it's null.

Context

Context tools
ToolWhat it does
get_me
readcontext:read
The account, workspace, role and granted scopes behind the current token.

Projects and workstreams

Projects and workstreams tools
ToolWhat it does
list_projects
readprojects:read
Your projects, newest first. Page with nextCursor.
get_project_structure
readprojects:read
A project expanded into phases and tasks nested to any depth, each with its roll-up, plus per-status totals.
get_project_tnl
readprojects:read
A project as a Taskneol (.tnl) file with ids, details, briefs and dependencies, plus anything the file couldn't hold.
get_tnl_spec
readprojects:read
The Taskneol (.tnl) language spec, as markdown.
create_project
writeprojects:write
Create a project.
update_project
writeprojects:write
Change a project's name, description, goal, summary, status, type, location or target date.
list_workstreams
readprojects:read
A project's workstreams (board swimlanes), in board order.
create_workstream
writeprojects:write
Add a workstream to a project.
update_workstream
writeprojects:write
Rename or re-describe a workstream.
delete_workstream
writeprojects:write
Delete a workstream. Its tasks are kept.
approve_plan
writeprojects:write
Human only. Approve every task waiting for review in a project at once.

Checkpoint gates

Checkpoint gates tools
ToolWhat it does
list_checkpoints
readprojects:read
A project's phase-boundary approval gates and their progress.
create_checkpoint
writeprojects:write
Add a gate that holds a phase (or the whole project) until enough people approve it.
approve_checkpoint
writeprojects:write
Human only. Cast one approval toward a gate.
delete_checkpoint
writeprojects:write
Human only. Remove a gate and its approvals.

Tasks

Tasks tools
ToolWhat it does
list_tasks
readtasks:read
Your tasks, filtered by project, status and work item type, each with its roll-up. view tree nests a project to any depth. Page with nextCursor.
search_tasks
readtasks:read
Substring search over task titles and descriptions, with filters.
get_task
readtasks:read
Everything needed to work on a task: brief, notes, dependencies, ancestors, subtasks, roll-up, item key, code links and project goal.
get_task_history
readtasks:read
The task's event history, newest first, with who made each change.
create_task
writetasks:write
Create a task or subtask, optionally with a type, agile fields, custom field values, a structured brief and blockers.
update_task
writetasks:write
Change a task's title, description, due date, phase, workstream, parent, type, agile fields, custom field values, brief or blockers.
move_task
writetasks:write
Move a task and everything below it under a new parent or to the top level, optionally setting phase and workstream on the whole branch.
list_custom_fields
readtasks:read
Custom field definitions (keys, kinds, picklist options, types, required) to use in create_task and update_task.
update_task_status
writetasks:write
Move a task between todo, in_progress, review and completed.
set_task_priority_effort
writetasks:write
Set a task's priority and/or estimated hours.
approve_task
writetasks:write
Human only. Approve one task out of review so it can be picked up.
assign_task
writetasks:write
Assign a task to a teammate or an AI agent identity.
unassign_task
writetasks:write
Clear a task's assignee.
remove_task_dependency
writetasks:write
Remove one blocker from a task.
delete_task
writetasks:write
Delete a task and its subtasks (recoverable from Trash).

AI planning

AI planning tools
ToolWhat it does
generate_project_plan
writeplans:write
Start AI plan generation from a goal. Returns a job id right away.
get_plan_job
readplans:read
Poll a plan job's status and progress.
create_project_from_plan
writeprojects:write
Turn a finished plan job into a project with its full task tree.

Agentic loop

Agentic loop tools
ToolWhat it does
request_next_task
readtasks:read
The single highest-priority task this agent may pick up next.
get_current_task
readcontext:read
The task the agent is working on now.
get_project_context
readcontext:read
A compact project summary sized for a model's context window.
submit_work_artifact
writeartifacts:write
Link a pull request, commit, file or note to a task.
update_work_artifact
writeartifacts:write
Update a work artifact's type, title, URL or summary.
delete_work_artifact
writeartifacts:write
Remove a work artifact from a task.

Attachments

Attachments tools
ToolWhat it does
list_attachments
readtasks:read
The files attached to a project or task.
get_attachment
readtasks:read
One attachment's metadata and contents.
upload_attachment
writetasks:write
Attach a file (up to 5 MB) to a project or task.
delete_attachment
writetasks:write
Remove an attachment from a project or task.

Conversations

Conversations tools
ToolWhat it does
sync_conversation
writeprojects:write
Save the chat's transcript with the projects and tasks it touched.
get_conversations
readcontext:read
Recent messages from earlier chats about a project or task.

v1.0.0

API reference

Generated from the OpenAPI document. Paths are relative to https://www.taskneuron.ai/api/v1, and every endpoint needs a Bearer API key. Besides the responses listed, any request can return 401, 403 or 429 (see Errors).

Account

Current account

get/me

Responses
  • 200
    The authenticated account
Example request
curl "https://www.taskneuron.ai/api/v1/me" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY"

Projects

List projects

get/projects

Parameters
Parameters for GET /projects
FieldDescription
view
string · querydefault "list"
tree (needs projectId) returns { tree, taskCount }: the project's active tasks nested to any depth, each node with level, rollup and children. Not paginated; status, workItemType and cursor are rejected in this mode.One of:listtree
rootTaskId
string · query
With view=tree: return only this task's branch.
limit
integer · querymax 200
offset
integer · querydeprecated
Legacy paging. Lossy: the list is ordered by a column concurrent writes can move, so a row updated between requests shifts past the offset and is returned by no page. Prefer cursor.
cursor
string · query
Opaque page token from the previous response's pagination.nextCursor. Anchors the next page to a row rather than a count, so concurrent writes cannot shift the boundary. Supersedes offset. Keep paging while nextCursor is non-null.
Responses
  • 200
    Projects + pagination
Example request
curl "https://www.taskneuron.ai/api/v1/projects" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY"

Create a project

post/projects

Request body · application/json · required
Request body for POST /projects
FieldDescription
name
stringrequired
description
string
projectType
string
Responses
  • 201
    Created project
Example request
curl -X POST "https://www.taskneuron.ai/api/v1/projects" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"<name>"}'

Update a project

patch/projects/{id}

Parameters
Parameters for PATCH /projects/{id}
FieldDescription
id
string · pathrequired
Request body · application/json · required

At least one field is required; an empty patch is rejected.

Request body for PATCH /projects/{id}
FieldDescription
name
string
description
stringnullable
goal
stringnullable
summary
stringnullable
status
string
One of:activearchivedcompleted
projectType
string
primaryLocation
stringnullable
targetDate
integernullable
Epoch milliseconds; null clears the target date.
itemKey
string
Work item key prefix (lowercase is uppercased). Keys in older commits stop resolving after a change. People only (agents get 403 human_required); audited.
codeMergeStatus
stringnullable
Move linked work items to this status when their pull request merges; none = never; null = default (the PR-status capability, when enabled, completes them). People only; audited.One of:nonein_progressreviewcompleted
Responses
  • 200
    Updated project
Example request
curl -X PATCH "https://www.taskneuron.ai/api/v1/projects/<id>" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"<name>","description":"<description>"}'

Project as a Cortex map graph (nodes + hierarchy and dependency edges)

get/projects/{id}/map

Parameters
Parameters for GET /projects/{id}/map
FieldDescription
id
string · pathrequired
Responses
  • 200
    project, nodes, edges, counts (ProjectMapGraph)
  • 404
    Not found
Example request
curl "https://www.taskneuron.ai/api/v1/projects/<id>/map" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY"

Project as a Taskneol (.tnl) file: canonical form with task and stream ids, briefs and dependencies

get/projects/{id}/tnl

Parameters
Parameters for GET /projects/{id}/tnl
FieldDescription
id
string · pathrequired
format
string · query
json returns { projectId, fileName, tnl, losses } instead of the fileOne of:json
Responses
  • 200
    The .tnl file as a download (X-Taskneol-Losses: how many things it couldn't hold), or JSON with format=json
  • 404
    Not found
Example request
curl "https://www.taskneuron.ai/api/v1/projects/<id>/tnl" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY"

Tasks

List tasks

get/tasks

Parameters
Parameters for GET /tasks
FieldDescription
projectId
string · query
status
string · query
workItemType
string · query
One of:epicfeaturestorybugtaskissue
limit
integer · querymax 200
offset
integer · querydeprecated
Legacy paging. Lossy: the list is ordered by a column concurrent writes can move, so a row updated between requests shifts past the offset and is returned by no page. Prefer cursor.
cursor
string · query
Opaque page token from the previous response's pagination.nextCursor. Anchors the next page to a row rather than a count, so concurrent writes cannot shift the boundary. Supersedes offset. Keep paging while nextCursor is non-null.
Responses
  • 200
    Tasks (each with rollup) + pagination, or the tree with view=tree
Example request
curl "https://www.taskneuron.ai/api/v1/tasks" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY"

Create a task

post/tasks

Request body · application/json · required

Plus the optional WorkItemFields (type, agile fields, customFields).

Request body for POST /tasks
FieldDescription
projectId
stringrequired
title
stringrequired
description
string
priority
string
One of:lowmediumhighurgent
estimatedHours
integer
dueDate
integer
Epoch milliseconds
phase
string
workItemType
string
One of:epicfeaturestorybugtaskissue
storyPoints
numbermin 0
severity
string
One of:criticalhighmediumlow
customFields
object
Key → value; validated against /custom-fields
Responses
  • 201
    Created task
Example request
curl -X POST "https://www.taskneuron.ai/api/v1/tasks" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"projectId":"<projectId>","title":"<title>"}'

Update a task

patch/tasks/{id}

Parameters
Parameters for PATCH /tasks/{id}
FieldDescription
id
string · pathrequired
Responses
  • 200
    Updated task
  • 404
    Not found
Example request
curl -X PATCH "https://www.taskneuron.ai/api/v1/tasks/<id>" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY"

Delete a task (soft, recoverable)

delete/tasks/{id}

Parameters
Parameters for DELETE /tasks/{id}
FieldDescription
id
string · pathrequired
Responses
  • 200
    Deleted
  • 404
    Not found
Example request
curl -X DELETE "https://www.taskneuron.ai/api/v1/tasks/<id>" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY"

Move a task and its whole branch

post/tasks/{id}/move

Re-parents the task under any allowed parent in the same project, or to the top level with null. Its children, grandchildren and so on move with it. Hierarchy rules apply (no cycle, the parent's type must accept the task's type, the branch must fit the maximum depth); a violation returns 400 or 409 invalid_parent with the rule in the message. Optional phase and workstreamId are applied to the whole branch.

Parameters
Parameters for POST /tasks/{id}/move
FieldDescription
id
string · pathrequired
Request body · application/json · required
Request body for POST /tasks/{id}/move
FieldDescription
parentTaskId
stringrequirednullable
phase
stringnullable
workstreamId
stringnullable
Responses
  • 200
    task (with ancestors, level, childSummary, rollup) and movedDescendantIds
  • 400
    Rule violation or invalid input
  • 404
    Not found
  • 409
    Cycle or too deep
Example request
curl -X POST "https://www.taskneuron.ai/api/v1/tasks/<id>/move" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"parentTaskId":"<parentTaskId>"}'

Full working context for a task (parent, subtasks, project goal, workstream)

get/tasks/{id}/context

Parameters
Parameters for GET /tasks/{id}/context
FieldDescription
id
string · pathrequired
Responses
  • 200
    task (with itemKey, ancestors root first, level, childSummary by status and type, and rollup), parent, subtasks, workstream, project (goal/summary/recommendations), codeLinks (branches, commits, pull requests and builds from GitHub, newest first, at most 50) and codeLinkCount
  • 404
    Not found
Example request
curl "https://www.taskneuron.ai/api/v1/tasks/<id>/context" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY"

The single highest-priority actionable task

get/next-task

Parameters
Parameters for GET /next-task
FieldDescription
projectId
string · query
Responses
  • 200
    { task: Task | null }
Example request
curl "https://www.taskneuron.ai/api/v1/next-task" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY"

Custom fields

List custom field definitions

get/custom-fields

The caller's workspace, or a team's with ?teamId. Agents may read.

Parameters
Parameters for GET /custom-fields
FieldDescription
teamId
string · query
includeArchived
boolean · query
Responses
  • 200
    { customFields: CustomField[] }
Example request
curl "https://www.taskneuron.ai/api/v1/custom-fields" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY"

Define a custom field

post/custom-fields

People only (agents get 403 human_required); a team's fields need team owner or admin. Audited.

Request body · application/json · required
Request body for POST /custom-fields
FieldDescription
teamId
string
key
stringrequired
label
stringrequired
kind
stringrequired
One of:textnumberdatepicklistpersonboolean
appliesTo
array of string
options
array of object
required
boolean
position
integer
Responses
  • 201
    Created
  • 403
    Not allowed
  • 409
    Key in use
Example request
curl -X POST "https://www.taskneuron.ai/api/v1/custom-fields" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"key":"<key>","label":"<label>","kind":"text"}'

Update, archive or restore a custom field

patch/custom-fields/{id}

label, appliesTo, options, required, position, archived. key and kind cannot change. People only; audited.

Parameters
Parameters for PATCH /custom-fields/{id}
FieldDescription
id
string · pathrequired
Responses
  • 200
    Updated
  • 400
    Invalid or immutable field
Example request
curl -X PATCH "https://www.taskneuron.ai/api/v1/custom-fields/<id>" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY"

Archive a custom field (stored values are kept)

delete/custom-fields/{id}

Parameters
Parameters for DELETE /custom-fields/{id}
FieldDescription
id
string · pathrequired
Responses
  • 200
    Archived
Example request
curl -X DELETE "https://www.taskneuron.ai/api/v1/custom-fields/<id>" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY"

Taskneol

AI planning

Start an async AI plan generation

post/plans

Request body · application/json · required
Request body for POST /plans
FieldDescription
goal
stringrequired3–8000 chars
projectType
string
location
string
Responses
  • 202
    Job accepted — returns { jobId, status }
  • 403
    Email not verified
  • 429
    Too many active jobs
Example request
curl -X POST "https://www.taskneuron.ai/api/v1/plans" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"goal":"<goal>"}'

Poll a plan-generation job (status/progress/result)

get/plans/{id}

Parameters
Parameters for GET /plans/{id}
FieldDescription
id
string · pathrequired
Responses
  • 200
    Job view (status, stage, progress, result when done)
  • 404
    Not found
Example request
curl "https://www.taskneuron.ai/api/v1/plans/<id>" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY"

Create a project from a completed plan job

post/projects/from-plan

Request body · application/json · required
Request body for POST /projects/from-plan
FieldDescription
jobId
stringrequired
Responses
  • 201
    Created project
  • 404
    Job not found
  • 409
    Plan job not finished
Example request
curl -X POST "https://www.taskneuron.ai/api/v1/projects/from-plan" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jobId":"<jobId>"}'

Conversations

Your conversations about a project (or its tasks) or a task, with recent messages

get/conversations

Parameters
Parameters for GET /conversations
FieldDescription
projectId
string · query
taskId
string · query
limit
integer · query1–200default 40
Responses
  • 200
    { conversations: [{ id, origin, client, title, messages, … }] }, newest last
  • 404
    Project or task not found
Example request
curl "https://www.taskneuron.ai/api/v1/conversations" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY"

Append an external assistant's chat transcript (Claude, ChatGPT…) to a conversation

post/conversations

Omit conversationId to start a conversation; pass the returned id on later calls so a continued chat keeps appending. Messages already stored are skipped. Messages over 20,000 characters are cut, and a conversation holds up to 1,000,000 characters.

Request body · application/json · required
Request body for POST /conversations
FieldDescription
conversationId
string
client
string
Assistant name, e.g. claude or chatgpt
title
string
projectId
string
Project this chat is about
taskIds
array of stringmax 50 items
messages
array of objectrequiredmax 200 items
messages[].role
stringrequired
One of:userassistant
messages[].content
stringrequired
Responses
  • 200
    Messages appended — { conversationId, added, duplicates, … }
  • 201
    Conversation created — { conversationId, added, duplicates, … }
  • 404
    Conversation not found
Example request
curl -X POST "https://www.taskneuron.ai/api/v1/conversations" \
  -H "Authorization: Bearer $TASKNEURON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"messages":[{"role":"user","content":"<content>"}]}'

Models

Shapes shared across endpoints. List responses wrap their items with a pagination object; errors use the Error envelope.

Error

Error fields
FieldDescription
error
object
error.message
stringrequired
error.code
string

Pagination

Pagination fields
FieldDescription
limit
integer
offset
integer
returned
integer
hasMore
boolean
nextCursor
stringnullable
Pass as ?cursor= to fetch the next page. Null on the last page.

Project

Project fields
FieldDescription
id
string
name
string
description
stringnullable
status
string
projectType
string
itemKey
string
Work item key prefix: "TN" makes task 123 "TN-123"
codeMergeStatus
stringnullable
Status linked work items move to when their pull request merges; null = defaultOne of:nonein_progressreviewcompleted
createdAt
string (date-time)
updatedAt
string (date-time)

Task

Task fields
FieldDescription
id
string
projectId
string
parentTaskId
stringnullable
itemNumber
integer
Number within the project; with the project's itemKey, e.g. TN-123
title
string
description
stringnullable
status
string
One of:todoin_progressreviewcompleted
priority
string
One of:lowmediumhighurgent
estimatedHours
integernullable
dueDate
string (date-time)nullable
phase
stringnullable
workItemType
string
One of:epicfeaturestorybugtaskissue
storyPoints
numbernullable
originalEstimate
numbernullable
Hours
remainingWork
numbernullable
Hours
completedWork
numbernullable
Hours
severity
stringnullable
One of:criticalhighmediumlow
reproSteps
stringnullable
acceptanceCriteriaText
stringnullable
customFields
object
Values (string, number or boolean) keyed by custom field key; see /custom-fields
createdAt
string (date-time)
updatedAt
string (date-time)

WorkItemFields

WorkItemFields fields
FieldDescription
workItemType
stringdefault "task"
One of:epicfeaturestorybugtaskissue
storyPoints
numbernullable0–100000
originalEstimate
numbernullable0–100000
Hours
remainingWork
numbernullable0–100000
Hours
completedWork
numbernullable0–100000
Hours
severity
stringnullable
One of:criticalhighmediumlow
reproSteps
stringnullablemax 20000 chars
acceptanceCriteriaText
stringnullablemax 20000 chars
customFields
object
Key → string, number, boolean, or null to clear. Merges by key on update.

Rollup

Rollup fields
FieldDescription
descendantCount
integer
completedCount
integer
percentComplete
numbernullable
storyPoints
numbernullable
remainingWork
numbernullable
completedWork
numbernullable
estimatedHours
numbernullable

CustomField

CustomField fields
FieldDescription
id
string
workspaceId
string
key
string
label
string
kind
string
One of:textnumberdatepicklistpersonboolean
appliesTo
array of string
Empty = every type
options
array of object
options[].value
string
options[].label
string
required
boolean
position
integer
archivedAt
string (date-time)nullable

Events

Webhooks

Webhooks push events to your server as they happen, so you don't have to poll. Each endpoint chooses which event types it receives.

Register an endpoint

  • Team workspaces: team admins open Team settings → Webhooks.
  • Personal workspace: open Settings → Webhooks.

Add an HTTPS URL, pick the event types to send, and keep the endpoint's signing secret somewhere safe: you'll need it to verify deliveries. Use Send test to send a ping event and check your endpoint.

Delivery format

Each delivery is an HTTP POST with a JSON body:

Request body
{
  "id": "<delivery id>",
  "type": "task.status_changed",
  "createdAt": "2026-10-07T14:03:12.000Z",
  "data": {
    "eventId": "<event id>",
    "projectId": "<project id>",
    "taskId": "<task id>",
    "actor": { "type": "agent", "id": "<actor id>" },
    "payload": { }
  }
}
Webhook delivery fields
FieldDescription
id
string
The delivery id. Same value as the TaskNeuron-Delivery header.
type
string
The event type, e.g. task.status_changed. Same as the TaskNeuron-Event header.
createdAt
string (ISO 8601)
When the event happened.
data.eventId
string
The id of the underlying event.
data.projectId
string
The project the event belongs to.
data.taskId
string | null
The task the event is about; null for project-level events.
data.actor
object
Who made the change: type is human, agent or system, plus their id.
data.payload
object
Event-specific details.

Every delivery also carries these headers:

  • TaskNeuron-Event: the event type.
  • TaskNeuron-Delivery: the delivery id.
  • TaskNeuron-Signature: t=<unix seconds>,v1=<signature>, where the signature is the hex HMAC-SHA256 of <t>.<raw body> using the endpoint's signing secret.
Headers
POST /your/webhook/endpoint HTTP/1.1
Content-Type: application/json
TaskNeuron-Event: task.status_changed
TaskNeuron-Delivery: <delivery id>
TaskNeuron-Signature: t=1791381792,v1=5f1c0a…e93b

Event types

Tasks

  • task.created
  • task.updated
  • task.status_changed
  • task.approved
  • task.assigned
  • task.unassigned
  • task.dependency_added
  • task.dependency_removed
  • task.artifact_linked
  • task.deleted
  • task.restored

Projects

  • project.created
  • project.updated
  • project.deleted
  • project.restored

Checkpoints

  • checkpoint.created
  • checkpoint.approved

Testing

ping is sent by the Send test button.

Responses and retries

  • Respond with any 2xx status within 10 seconds. Acknowledge first and do slow work afterwards.
  • Failed deliveries are retried with exponential backoff, up to 8 attempts over roughly a day. Make your handler idempotent: a delivery that timed out on our side may already have been processed on yours.
  • An endpoint that fails 25 deliveries in a row is disabled automatically, and its owner is notified.

Verify signatures

Check the signature on every delivery before trusting it. Compute the HMAC over the raw request body (not re-serialized JSON), compare in constant time, and reject signatures whose timestamp is more than 5 minutes old to prevent replay attacks.

Node.js
import crypto from "node:crypto";

const TOLERANCE_SECONDS = 5 * 60;

/**
 * rawBody: the request body exactly as received (Buffer or string), before any
 * JSON parsing. header: the TaskNeuron-Signature header. secret: the endpoint's
 * signing secret.
 */
export function verifyTaskNeuronSignature(rawBody, header, secret) {
  const parts = {};
  for (const item of (header ?? "").split(",")) {
    const i = item.indexOf("=");
    if (i > 0) parts[item.slice(0, i).trim()] = item.slice(i + 1).trim();
  }
  const { t, v1 } = parts;
  if (!t || !v1 || !/^\d+$/.test(t)) return false;

  // Reject old signatures so a captured request can't be replayed.
  if (Math.abs(Date.now() / 1000 - Number(t)) > TOLERANCE_SECONDS) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.`)
    .update(rawBody)
    .digest();
  const received = Buffer.from(v1, "hex");
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

// Express: keep the raw body for verification.
app.post("/webhooks/taskneuron", express.raw({ type: "application/json" }), (req, res) => {
  const ok = verifyTaskNeuronSignature(
    req.body,
    req.get("TaskNeuron-Signature"),
    process.env.TASKNEURON_WEBHOOK_SECRET,
  );
  if (!ok) return res.status(400).send("Invalid signature");

  const delivery = JSON.parse(req.body.toString("utf8"));
  res.sendStatus(204); // acknowledge fast, then do the work
  handleDelivery(delivery);
});
Python
import hashlib
import hmac
import time

TOLERANCE_SECONDS = 5 * 60


def verify_taskneuron_signature(raw_body: bytes, header: str, secret: str) -> bool:
    """raw_body: the request body bytes exactly as received, before JSON parsing."""
    parts = {}
    for item in (header or "").split(","):
        key, sep, value = item.partition("=")
        if sep:
            parts[key.strip()] = value.strip()
    t, v1 = parts.get("t", ""), parts.get("v1", "")
    if not t.isdigit() or not v1:
        return False

    # Reject old signatures so a captured request can't be replayed.
    if abs(time.time() - int(t)) > TOLERANCE_SECONDS:
        return False

    signed = t.encode() + b"." + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)


# Flask
@app.post("/webhooks/taskneuron")
def taskneuron_webhook():
    if not verify_taskneuron_signature(
        request.get_data(),
        request.headers.get("TaskNeuron-Signature", ""),
        os.environ["TASKNEURON_WEBHOOK_SECRET"],
    ):
        abort(400)
    delivery = request.get_json()
    enqueue(delivery)  # do the work after responding
    return "", 204