API documentation

Everything the app can do, your scripts and agents can do too: plain JSON over HTTPS. Create issues from your tooling, drive a board from CI, or let an AI agent propose work into triage.

Authentication

Create an API key in Settings → API (your org id is shown there too). Send it as a bearer token on every request:

curl -H "Authorization: Bearer ibk_..." \
  https://api.issueboard.dev/v1/orgs/{orgId}/issues

Keys come in two shapes, both with an editor (read-write) or viewer (read-only) level:

  • Org keys see every project the org role allows.
  • Project keys are scoped to a single project — they can only read and write issues of that project, and must pass its project_id when creating. Give agents and integrations the narrowest key that works.

Keys always address the org explicitly in the path — there is no endpoint to discover an org from a key, by design. A key never grants more than the role of the person who created it.

Conventions

Base URL https://api.issueboard.dev/v1. Requests and responses are JSON. Every successful response is wrapped in a data envelope:

{ "data": { "id": "01KT...", "number": 42, "title": "..." } }

List endpoints return { items, page, limit, total } inside the envelope and paginate with ?page= and ?limit= (max 500). total is the full filtered count, so you can page honestly. Timestamps are UTC RFC 3339; dates (like due_date) are YYYY-MM-DD; ids are ULIDs.

Issues

GET/orgs/{o}/issueslist and search
POST/orgs/{o}/issuescreate
GET/orgs/{o}/issues/{number}detail
PATCH/orgs/{o}/issues/{number}update (editor)
DELETE/orgs/{o}/issues/{number}delete (editor)

List filters: status, assignee, team, project, type_id, priority, label, cycle, customer, sort and free-text q (word-prefix search over title and description).

Create an issue — only title is required:

curl -X POST -H "Authorization: Bearer ibk_..." \
  -H "Content-Type: application/json" \
  https://api.issueboard.dev/v1/orgs/{orgId}/issues -d '{
    "title": "Search returns no results for plurals",
    "description": "Markdown **supported**.",
    "type": "bug",
    "priority": "high",
    "project_id": "01KT...",
    "due_date": "2026-07-01"
  }'

type accepts a type name (case-insensitive, resolved within the org) or pass type_id directly — issue types are configurable per org (GET /orgs/{o}/types); omit it for the org default. priority is none | low | medium | high | urgent. Optional references: status_id, team_id, assignee_id, cycle_id, label_ids, and parent_id to file it as a sub-issue of another issue.

Propose instead of place: add "triage_state": "pending" to land the issue in the project's triage inbox for a human to accept — the right default for agents and automations. (Viewer-level keys and reporters always enter triage.)

Updates take the same fields. For safe concurrent edits, send expected_updated_at with the updated_at you last read — the API answers 409 if someone changed the issue in between:

curl -X PATCH -H "Authorization: Bearer ibk_..." \
  -H "Content-Type: application/json" \
  https://api.issueboard.dev/v1/orgs/{orgId}/issues/42 -d '{
    "status_id": "01KT...",
    "expected_updated_at": "2026-06-11T10:42:07Z"
  }'

Comments

GET/orgs/{o}/issues/{n}/comments
POST/orgs/{o}/issues/{n}/comments{ "body": "markdown" }
PATCH/orgs/{o}/issues/{n}/comments/{id}author only
DELETE/orgs/{o}/issues/{n}/comments/{id}author only

Comment (and description) bodies are markdown; image and video attachment URLs embed inline. @[Name](user:USER_ID) mentions a member and notifies them; #123 links to that issue. The issue's activity history is read-only at GET /orgs/{o}/issues/{n}/activities.

Triage

GET/orgs/{o}/triagepending inbox; ?project= or ?unrouted=true
POST/orgs/{o}/issues/{n}/triageaccept / decline / merge (editor)
# accept into a project with a starting status
{ "action": "accept", "project_id": "01KT...", "status_id": "01KT..." }

# decline (reporter keeps seeing the outcome in My reports)
{ "action": "decline" }

# merge a duplicate into the canonical issue
{ "action": "merge", "merge_into_number": 17 }

Reference data

Everything an issue references is readable (and writable by org-level editor keys) at the obvious places:

GET/orgs/{o}/statusesboard columns, with category
GET/orgs/{o}/typesconfigurable issue types (icon, color)
GET/orgs/{o}/projects
GET/orgs/{o}/labels
GET/orgs/{o}/members
GET/orgs/{o}/teams
GET/orgs/{o}/customers
GET/orgs/{o}/cycles?state=active|upcoming|past

Project-scoped keys may read these too (they need status and label ids to work) but only ever see issues of their own project.

Attachments

POST/orgs/{o}/attachmentsmultipart file= (images ≤10 MB, video ≤50 MB)
POST/orgs/{o}/attachments/from-url{ "url": "https://..." } — images only, ≤10 MB
GET/orgs/{o}/attachments/{id}serves the file

Upload first, then embed the returned URL in an issue description or comment as markdown: ![screenshot.png](url).

Errors

Errors use conventional HTTP status codes — 400 invalid or missing fields, 401 bad or revoked key, 403 outside the key's scope or level, 404 not found (also returned for resources the key is not allowed to see), 409 conflicts — with a JSON body:

{ "error": { "message": "this API key can only create issues in its scoped project" } }

The API is in public beta: we add fields and endpoints without notice but treat renames and removals as breaking. Questions or gaps? support@issueboard.dev.