API Reference

Flint Task exposes two APIs over the same data: a GraphQL endpoint (the richer one, and the one most clients should use) and a REST API. The REST endpoints live under https://flinttask.com/api and exchange JSON.

Authentication

Use a personal API key (create one under API keys) or a token from /auth/login, then send it on each request:

curl https://flinttask.com/api/issues?limit=20 \
  -H "Authorization: Bearer <token>"

Authentication

Email + password authentication. Login or registration returns a JWT (valid 7 days). Send it as `Authorization: Bearer <token>` on every authenticated request.

POST/auth/register Public

Create an account (role: member) and return a JWT.

POST/auth/login Public

Exchange email + password for a JWT.

GET/auth/me Bearer token

Return the authenticated user.

POST/auth/change-password Bearer token

Change your own password.

Workspace config

Public, read-only workspace configuration — enabled features and branding.

GET/config Public

Enabled feature flags + workspace branding.

Issues

The core work items. List supports filtering (teamId, stateId, assigneeId, projectId, cycleId, priority), full-text search (q) and pagination (limit ≤ 250, offset).

GET/issues Bearer token

List issues. Returns { issues, pageInfo: { total, limit, offset } }.

Response

200 OK
{
  "issues": [ { "id", "identifier", "title", "stateId", "priority", … } ],
  "pageInfo": { "total": 142, "limit": 50, "offset": 0 }
}
POST/issues Bearer token

Create an issue (auto-assigns identifier, e.g. CLA-42).

Request

{
  "title": "Fix flaky cycle filter",
  "teamId": "t_cla",          // optional, defaults to first team
  "priority": 1,               // 0 none … 1 urgent … 4 low
  "assigneeId": "<user id>",
  "projectId": "<project id>",
  "labelIds": ["l_bug"]
}

Response

201 Created
{ "issue": { "id", "identifier": "CLA-42", "title", "stateId",
             "priority", "labelIds": [...], "createdAt", … } }
GET/issues/:id Bearer token

Get an issue by id or identifier (e.g. CLA-42).

PATCH/issues/:id Bearer token

Update title, state, priority, assignee, project, cycle, labels, …

DELETE/issues/:id Bearer token

Delete an issue and its comments.

GET/issues/:id/comments Bearer token

List comments on an issue.

POST/issues/:id/comments Bearer token

Add a comment to an issue.

Projects

Organizational containers for work, with status, health, lead and target date.

GET/projects Bearer token

List projects.

POST/projects Bearer token

Create a project.

GET/projects/:id Bearer token

Get a project.

PATCH/projects/:id Bearer token

Update a project.

DELETE/projects/:id Bearer token

Delete a project.

Cycles

Time-boxed iterations belonging to a team. Filter the list by teamId.

GET/cycles Bearer token

List cycles (optionally ?teamId=).

POST/cycles Bearer token

Create a cycle (auto-numbers within the team).

PATCH/cycles/:id Bearer token

Update a cycle.

DELETE/cycles/:id Bearer token

Delete a cycle.

Reference data

Read-only collections used to resolve ids on issues and projects.

GET/teams Bearer token

List teams.

GET/workflow-states Bearer token

List workflow states (optionally ?teamId=).

GET/labels Bearer token

List labels.

GET/users Bearer token

List workspace members.

GraphQL

The full domain is also served as a GraphQL API at https://flinttask.com/graphql. It is the richer of the two — one round trip can fetch an issue with its state, assignee, labels and comments — and it is schema-compatible with Linear's, so a client written against their reference works here by changing only the host.

Authentication

Send a personal API key as the bare Authorization value, or a login token with a Bearer prefix.

curl https://flinttask.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: <your API key>" \
  --data '{"query":"{ viewer { id name email } }"}'

Pagination

Every list is a connection. Read it as nodes for the common case, or as edges when you need cursors. Connections accept first, last, after, before, orderBy (createdAt · updatedAt), includeArchived and filter. The first 50 results come back when no arguments are given.

query {
  issues(first: 50, orderBy: updatedAt) {
    edges { node { id identifier title } cursor }
    pageInfo { hasNextPage endCursor }
  }
}

Filtering

Filters are comparator objects. Every field takes eq · neq · in · nin; numbers and dates add lt · lte · gt · gte; strings add contains · containsIgnoreCase · startsWith · endsWith and their negations; optional fields add null. Fields combine with AND; use or for alternatives. Relations nest, and many-to-many relations take every / some.

query {
  issues(filter: {
    state: { type: { eq: "started" } }
    assignee: { email: { eq: "[email protected]" } }
    labels: { name: { eq: "Bug" } }
    or: [{ priority: { eq: 1 } }, { dueDate: { lt: "2026-09-01" } }]
  }) {
    nodes { identifier title priorityLabel state { name } }
  }
}

Mutations

Each mutation returns a payload carrying success, lastSyncId and the affected record. Deletes return entityId.

mutation IssueCreate($input: IssueCreateInput!) {
  issueCreate(input: $input) {
    lastSyncId
    success
    issue { id identifier title url branchName }
  }
}

# variables
{ "input": { "title": "Rate-limit the public API", "teamId": "<team id or key>", "priority": 2 } }

Available: issueCreate · issueUpdate · issueDelete · issueArchive · issueUnarchive · commentCreate · commentUpdate · commentDelete · projectCreate · projectUpdate · projectDelete · cycleCreate · cycleUpdate · issueLabelCreate · issueLabelUpdate · issueLabelDelete · attachmentCreate · attachmentUpdate · attachmentDelete · fileUpload. Introspection is enabled, so codegen and GraphQL IDEs work against the endpoint directly.

Uploading files

Uploads are two steps: ask for a URL, then PUT the bytes to it. The returned assetUrl is what you reference from a description, a comment or an attachment. Files are capped at 25 MB.

mutation FileUpload($contentType: String!, $filename: String!, $size: Int!) {
  fileUpload(contentType: $contentType, filename: $filename, size: $size) {
    success
    uploadFile { assetUrl uploadUrl headers { key value } }
  }
}

# then, with the headers the payload asked for:
curl -X PUT "<uploadUrl>" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: image/png" \
  --data-binary @screenshot.png

# and attach it to an issue:
mutation { attachmentCreate(input: {
  issueId: "<id>", title: "screenshot.png", url: "<assetUrl>"
}) { success attachment { id url } } }

A one-shot REST form exists for clients that would rather not round-trip: POST /api/files with the file as the raw body, its name in an x-filename header (percent-encoded) and its type in Content-Type.

Webhooks

A workspace admin can register endpoints (System administration → Webhooks). When an issue or comment changes, Flint Task POSTs a JSON payload to each endpoint.

POST <your endpoint>
X-Flint-Event: issue.create
X-Flint-Signature: sha256=<hex>

{ "type": "issue", "action": "create",
  "data": { "id", "identifier", "title", … },
  "createdAt": "2026-…Z" }

Verify authenticity with HMAC-SHA256(secret, rawBody) and compare to the X-Flint-Signature header.