Developers

Build on Issuelane

A versioned REST API for your workspace's tasks, projects and comments, and signed webhooks that tell your service the moment something changes.

Authentication

Every request carries an API token in the Authorization header. A workspace admin creates an agent and its tokens in Settings, Agents. A token belongs to one workspace and holds scopes: workspace:read for every read, and tasks:write to create and change tasks and comments. The base URL is https://api-issuelane.cublya.com/v1.

curl https://api-issuelane.cublya.com/v1/workspaces/$WORKSPACE_ID/tasks?limit=50 \
  -H "Authorization: Bearer $ISSUELANE_TOKEN"

Changes made with a token are recorded under the agent’s name in every task’s history. A browser session is not accepted here; use a token.

Pagination

Every list answers { "data": [...], "nextCursor": "..." }. To read the next page, send the same request with cursor set to nextCursor; it is null on the last page. Keep the filters and the order the same while paging: a cursor from a different list is refused. Pages hold up to 100 items, 50 unless you set limit.

To keep a copy in sync, list tasks with order=CREATED once, then poll with updated_since, or better, subscribe to webhooks.

Errors

Every error has the same shape. Switch on type, show message, and quote requestId if you report a problem. A 422 lists the refused fields in errors.

{
  "error": {
    "type": "validation_failed",
    "message": "title cannot be null",
    "requestId": "01J…",
    "errors": [{ "field": "title", "message": "title cannot be null" }]
  }
}

The types are unauthorized (401), forbidden (403), not_found (404), conflict (409), payload_too_large (413), validation_failed (422), rate_limited (429), internal_error (500) and unavailable (503).

Retrying writes

Send an Idempotency-Key header holding a UUID you generate once per intended write. If the connection drops and you retry with the same key, you get the first result back and nothing is written twice. Reusing a key for a different request is a 409.

Rate limits

Each token may make 600 requests and 60 writes a minute. All the tokens of one workspace share 300 writes a minute. The windows are fixed and 60 seconds long. Over a limit the answer is 429 with a Retry-After header in seconds; wait that long, then retry.

Webhooks

A workspace admin adds endpoints in Settings, Webhooks, and picks the events each one is sent. Every delivery is a POST with a JSON body:

{
  "id": "msg_5f0c7a1e9b2d4c6e8a0b1c2d3e4f5a6b",
  "type": "task.updated",
  "timestamp": "2026-10-28T19:42:07.512301Z",
  "data": {
    "workspaceId": "4b0e1c7e-…",
    "seq": 1842,
    "entityId": "9d3f2a10-…",
    "actorId": "1c5e8b22-…",
    "object": { "id": "9d3f2a10-…", "identifier": "WEB-42", "title": "…", "stateId": "…" }
  }
}

data.object has the same fields as the matching API object. If a body would be larger than 256 KB, object is empty and data.truncated is true; read the resource through the API.

The events:

  • task.created
  • task.updated
  • task.deleted
  • comment.created
  • comment.updated
  • comment.deleted
  • project.created
  • project.updated
  • label.created
  • label.updated
  • label.deleted
  • state.created
  • state.updated
  • task_relation.created
  • task_relation.deleted
  • attachment.created
  • attachment.deleted
  • member.created
  • member.deleted

webhook.test is sent when an admin presses Send test.

Verifying a delivery

Each delivery carries the Standard Webhooks headers webhook-id, webhook-timestamp and webhook-signature. The signature is v1, followed by a base64 HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body}, keyed with the bytes after the secret’s whsec_ prefix. Any Standard Webhooks library verifies it, or:

import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody is the request body exactly as received, before any JSON parsing.
export function verify(secret, headers, rawBody) {
  const id = headers["webhook-id"];
  const timestamp = headers["webhook-timestamp"];
  // Refuse anything older or newer than five minutes: it may be a replay.
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const expected = createHmac("sha256", key)
    .update(`${id}.${timestamp}.${rawBody}`)
    .digest("base64");
  return headers["webhook-signature"].split(" ").some((entry) => {
    const [version, signature] = entry.split(",");
    return (
      version === "v1" &&
      signature.length === expected.length &&
      timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
    );
  });
}

For receivers that already check it, X-Hub-Signature-256 carries sha256= and a hex HMAC-SHA256 of the body alone, keyed with the whole secret as shown. It does not cover the timestamp, so check webhook-timestamp yourself.

Retries and ordering

Answer with any 2xx within 10 seconds. Anything else, redirects included, is a failure and is retried after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, 10 hours: 8 attempts over about a day. Delivery is at least once and not ordered, so use webhook-id to ignore a message you have already handled and data.seq to order changes to one workspace. An endpoint that fails 20 times in a row for more than 24 hours is turned off, and the settings page says why. The delivery log keeps 30 days of attempts, and any delivery can be sent again from it.

Where deliveries go

Endpoints must be public https URLs. Private, loopback, link-local and reserved addresses are refused when the endpoint is saved, and checked again on every delivery after the name is resolved, so a name that later points inward is refused too.

Reference

The machine-readable description is served at https://api-issuelane.cublya.com/v1/openapi.json (OpenAPI 3.1). Version 1 only grows: fields and endpoints are added, never removed or changed. A breaking change would be /v2, beside this one.

Workspace

Get the workspace

get/v1/workspaces/{workspace_id}

Answers 200, or an error: 401, 403, 404, 422, 429.

List members

get/v1/workspaces/{workspace_id}/members

Every person in the workspace, by name. Not paged.

Answers 200, or an error: 401, 403, 404, 422, 429.

Projects

List labels

get/v1/workspaces/{workspace_id}/labels

Every label, or with `project_id` the shared labels plus that project's own. Not paged.

Parameters

project_id?querystring (uuid)

Answers 200, or an error: 401, 403, 404, 422, 429.

List projects

get/v1/workspaces/{workspace_id}/projects

Not paged. Archived projects only when `include_archived` is true.

Parameters

include_archived?queryboolean

Answers 200, or an error: 401, 403, 404, 422, 429.

Get a project

get/v1/workspaces/{workspace_id}/projects/{project_id}

Parameters

project_idpathstring (uuid)

Answers 200, or an error: 401, 403, 404, 422, 429.

List a project's sprints

get/v1/workspaces/{workspace_id}/projects/{project_id}/sprints

The latest start first, undated sprints last. Not paged.

Parameters

project_idpathstring (uuid)

Answers 200, or an error: 401, 403, 404, 422, 429.

List a project's workflow states

get/v1/workspaces/{workspace_id}/projects/{project_id}/states

In workflow order. Not paged.

Parameters

project_idpathstring (uuid)

Answers 200, or an error: 401, 403, 404, 422, 429.

Tasks

List tasks

get/v1/workspaces/{workspace_id}/tasks

Tasks across the workspace, narrowed by every filter given (they are ANDed) and paged by an opaque cursor. Filters narrow the whole workspace, not the page. A filter naming something the workspace does not have is a 422, not an empty page. To page, send the same filters and order with each `cursor`; a cursor from a different list is refused.

Parameters

project_id?querystring (uuid)Only tasks in this project.
state_id?querystring (uuid)Only tasks in this workflow state.
state_category?queryStateCategoryOnly tasks whose state is in this category.
assignee_id?querystring (uuid)Only tasks assigned to this person.
priority?queryTaskPriority
label_id?querystring (uuid)Only tasks carrying this label.
parent_id?querystring (uuid)Only the sub-tasks of this task.
updated_since?querystring (date-time)Only tasks changed at or after this instant. Include an offset.
include_archived?queryboolean
order?queryTaskOrder
limit?queryintegerHow many items a page holds, 1 to 100.
cursor?querystringThe `nextCursor` of the previous page. Send the same filters with it.

Answers 200, or an error: 401, 403, 404, 422, 429.

Create a task

post/v1/workspaces/{workspace_id}/tasks

Needs the `tasks:write` scope. Send an `Idempotency-Key` to make retries safe.

Parameters

Idempotency-Key?headerstring (uuid)A UUID naming this write. Retrying with the same key returns the first result instead of writing again.

Answers 201, or an error: 401, 403, 404, 409, 422, 429, 503.

Get a task

get/v1/workspaces/{workspace_id}/tasks/{task_id}

Parameters

task_idpathstring (uuid)

Answers 200, or an error: 401, 403, 404, 422, 429.

Update a task

patch/v1/workspaces/{workspace_id}/tasks/{task_id}

Changes only the fields sent. Archive with `archived: true`; the public API does not delete. Needs the `tasks:write` scope.

Parameters

task_idpathstring (uuid)
Idempotency-Key?headerstring (uuid)A UUID naming this write. Retrying with the same key returns the first result instead of writing again.

Answers 200, or an error: 401, 403, 404, 409, 422, 429, 503.

Comments

List a task's comments

get/v1/workspaces/{workspace_id}/tasks/{task_id}/comments

Oldest first, paged by cursor. A deleted comment stays in the list with a null body.

Parameters

task_idpathstring (uuid)
limit?queryintegerHow many items a page holds, 1 to 100.
cursor?querystringThe `nextCursor` of the previous page. Send the same filters with it.

Answers 200, or an error: 401, 403, 404, 422, 429.

Comment on a task

post/v1/workspaces/{workspace_id}/tasks/{task_id}/comments

Needs the `tasks:write` scope. Mentions in the body notify people as they do in the app.

Parameters

task_idpathstring (uuid)
Idempotency-Key?headerstring (uuid)A UUID naming this write. Retrying with the same key returns the first result instead of writing again.

Answers 201, or an error: 401, 403, 404, 409, 422, 429, 503.

Objects

Comment

authorIdstring (uuid)
bodystring or nullMarkdown; null once deleted.
createdAtstring (date-time)
deletedAtstring (date-time) or null
editedAtstring (date-time) or null
idstring (uuid)
parentIdstring (uuid) or nullThe comment this replies to.
taskIdstring (uuid)
updatedAtstring (date-time)
workspaceIdstring (uuid)

CommentCreate

bodystringMarkdown.
parentId?string (uuid) or nullThe comment to reply to.

CommentList

dataComment[]
nextCursorstring or nullPass as `cursor` for the next page; null on the last.

Label

colorstring or null
createdAtstring (date-time)
groupIdstring (uuid) or null
idstring (uuid)
namestring
projectIdstring (uuid) or nullNull for a label every project shares.
workspaceIdstring (uuid)

LabelList

dataLabel[]
nextCursorstring or nullPass as `cursor` for the next page; null on the last.

Member

A person in the workspace. Agents are not members.

emailstring or null
idstring (uuid)
joinedAtstring (date-time)
namestring or null
roleMemberRole

MemberList

dataMember[]
nextCursorstring or nullPass as `cursor` for the next page; null on the last.

MemberRole

A person's role in the workspace.

One of OWNERADMINMEMBERGUEST

Project

archivedAtstring (date-time) or null
createdAtstring (date-time)
descriptionstring or null
idstring (uuid)
keystringThe prefix of the project's task identifiers.
leadIdstring (uuid) or null
namestring
slugstring
startsAtstring (date) or null
targetAtstring (date) or null
workspaceIdstring (uuid)

ProjectList

dataProject[]
nextCursorstring or nullPass as `cursor` for the next page; null on the last.

Sprint

createdAtstring (date-time)
descriptionstring or null
endsAtstring (date-time) or null
idstring (uuid)
namestring
projectIdstring (uuid)
startsAtstring (date-time) or null
statusSprintStatus
updatedAtstring (date-time)
workspaceIdstring (uuid)

SprintList

dataSprint[]
nextCursorstring or nullPass as `cursor` for the next page; null on the last.

SprintStatus

Where a sprint is in its life.

One of PLANNEDACTIVECOMPLETED

State

One step of a project's workflow.

categoryStateCategory
colorstring or null
createdAtstring (date-time)
idstring (uuid)
isDefaultboolean
namestring
positioninteger
projectIdstring (uuid)
workspaceIdstring (uuid)

StateCategory

The fixed category every workflow state maps onto. States are named freely per project; read the category, never the name, to tell open work from done.

One of BACKLOGUNSTARTEDSTARTEDCOMPLETEDCANCELED

StateList

dataState[]
nextCursorstring or nullPass as `cursor` for the next page; null on the last.

Task

acceptanceCriteriastring or nullMarkdown.
archivedAtstring (date-time) or null
assigneeIdstring (uuid) or nullAlways a person.
canceledAtstring (date-time) or null
completedAtstring (date-time) or null
createdAtstring (date-time)
creatorIdstring (uuid)
delegateIdstring (uuid) or nullThe agent working on it, if any.
descriptionstring or nullMarkdown.
dueDatestring (date) or null
estimateinteger or null
idstring (uuid)
identifierstringThe human key, such as `WEB-42`.
labelIdsstring (uuid)[]
numberinteger
parentIdstring (uuid) or null
priorityTaskPriority
projectIdstring (uuid)
sprintIdstring (uuid) or null
startedAtstring (date-time) or null
stateIdstring (uuid)
teamIdstring (uuid) or null
titlestring
updatedAtstring (date-time)
workspaceIdstring (uuid)

TaskCreate

acceptanceCriteria?string or null
assigneeId?string (uuid) or null
description?string or null
dueDate?string (date) or null
estimate?integer or null
labelIds?string (uuid)[]
parentId?string (uuid) or null
priority?TaskPriority
projectIdstring (uuid)
sprintId?string (uuid) or null
stateId?string (uuid) or nullDefaults to the project's default state.
titlestring

TaskList

dataTask[]
nextCursorstring or nullPass as `cursor` for the next page; null on the last.

TaskPriority

How urgent a task is. NONE is the default for a task nobody has ranked.

One of NONEURGENTHIGHMEDIUMLOW

TaskUpdate

Only the fields sent change. Null clears a field that may be empty; title, state, priority, labels and archived cannot be null.

acceptanceCriteria?string or null
archived?boolean or null
assigneeId?string (uuid) or null
description?string or null
dueDate?string (date) or null
estimate?integer or null
labelIds?string (uuid)[] or nullReplaces the whole set.
parentId?string (uuid) or null
priority?TaskPriority or null
sprintId?string (uuid) or null
stateId?string (uuid) or null
title?string or null

Workspace

createdAtstring (date-time)
idstring (uuid)
namestring
slugstring