REST API guide
The base URL is http://localhost:8100/api/v1 on the Docker stack; every path below is relative to it. JSON in, JSON out. Every operation, parameter and schema is in the generated API reference, built from the OpenAPI 3.1 description of the API.
export TRACKER=http://localhost:8100/api/v1export TOKEN='1|…'curl -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" "$TRACKER/me"Create the token as described in Personal access tokens.
Conventions
Section titled “Conventions”- Issue keys look like
WEB-12. They are case-insensitive, a leading#is ignored, and the numericidworks too. - Options are objects
{ "id": "…", "name": "…" }:type(task,bug,feature),priority(lowest,low,medium,high,urgent) and timeactivity(development,review,testing,design,meeting,other). Status ids are numbers, sent as strings intransitions; a status may also be given by name, like"Review". - Status category is
todo,in_progressordone.status=openmeans “not done”. - Markdown:
descriptionand commentbodyare Markdown, returned as written. The web interface renders them with raw HTML escaped. - Dates: ISO 8601 with an offset for timestamps;
YYYY-MM-DDfordue_dateand thedateof a time entry. - Pagination: list endpoints return
data,linksandmeta { current_page, last_page, per_page, total }.per_pageis 1-100 (25 by default),pagestarts at 1. - Rate limit: 240 requests a minute per user.
Errors
Section titled “Errors”{ "message": "The title field is required.", "errors": { "title": ["The title field is required."] } }| Status | Meaning |
|---|---|
401 |
No token, or a bad, revoked or expired one |
403 |
The owner’s role or the token’s ability does not allow it |
404 |
No such issue, project or resource |
422 |
Validation failed; errors names the fields |
429 |
More than 240 requests a minute |
The issue object
Section titled “The issue object”{ "id": 13, "key": "AGENT-1", "number": 1, "title": "Skribly Tracker as the default tracker", "description": "Add a `skribly` TrackerClient…", "type": { "id": "feature", "name": "Feature" }, "status": { "id": 6, "name": "In progress", "category": "in_progress" }, "priority": { "id": "urgent", "name": "Urgent" }, "labels": ["trackers"], "assignee": { "id": 1, "name": "Ada Example", "email": "ada@example.com" }, "reporter": { "id": 1, "name": "Ada Example", "email": "ada@example.com" }, "project": { "id": 2, "key": "AGENT", "name": "Skribly Agent" }, "parent": null, "subtasks": [{ "key": "AGENT-4", "title": "…", "status": "To do" }], "estimate_hours": 8, "spent_hours": 1.5, "due_date": "2026-11-03", "url": "http://localhost:8100/issues/AGENT-1", "created_at": "2026-10-03T09:12:00+01:00", "updated_at": "2026-10-29T20:10:36+01:00", "closed_at": null, "comments": [ { "id": 6, "author": { "id": 5, "name": "Skribly Agent" }, "body": "Started on the adapter.", "created_at": "…", "updated_at": "…" } ], "attachments": [ { "id": 3, "name": "screen.png", "size": 48213, "type": "image/png", "url": "http://localhost:8100/api/v1/attachments/3" } ]}comments and attachments come only with GET /issues/{key}. labels are names. Attachment URLs need the same bearer token.
Endpoints
Section titled “Endpoints”| Method | Path | Purpose |
|---|---|---|
| GET | /me |
The token’s owner and abilities |
| GET | /users |
People an issue can be assigned to |
| GET | /projects |
Active projects with open_issues |
| GET | /projects/{key} |
A project with its statuses, labels, types and priorities |
| GET | /projects/{key}/meta |
What can be chosen when creating an issue |
| GET | /issues |
A filtered, paginated list |
| POST | /issues |
Create (write) |
| GET | /issues/{key} |
One issue with comments and attachments |
| PATCH | /issues/{key} |
Change some fields (write) |
| DELETE | /issues/{key} |
Delete (admin or reporter; write) |
| GET | /issues/{key}/transitions |
The statuses of its project, with current marked |
| POST | /issues/{key}/transitions |
Change the status (write) |
| POST | /issues/{key}/assign |
Assign or unassign (write) |
| GET | /issues/{key}/comments |
Comments, oldest first |
| POST | /issues/{key}/comments |
Add a Markdown comment (write) |
| GET | /issues/{key}/time-entries |
Logged time |
| POST | /issues/{key}/time-entries |
Log time (write) |
| POST | /issues/{key}/attachments |
Upload a file in the multipart field file (write) |
| GET | /attachments/{id} |
Download a file |
| GET | /time-activities |
Activities for time entries |
| GET | /search |
Text search; an exact key comes first |
| GET, POST | /webhooks |
List or add webhooks (admin; write) |
| DELETE | /webhooks/{id} |
Remove a webhook (admin; write) |
List and filter issues
Section titled “List and filter issues”curl -H "Authorization: Bearer $TOKEN" "$TRACKER/issues?assignee=me&status=open&sort=-updated&per_page=100"curl -H "Authorization: Bearer $TOKEN" "$TRACKER/issues?updated_since=2026-10-29T12:00:00Z&status=all"| Parameter | Values |
|---|---|
assignee |
me, none, a user id or an e-mail |
reporter, watching |
me |
status |
open (not done), closed, all, a status id or a name |
project |
A key, or several separated by commas (WEB,AGENT) |
type, priority |
An option id, or several separated by commas |
label |
A label name |
q |
Text in the title or description, or an exact key |
updated_since |
An ISO 8601 timestamp, for polling |
sort |
-updated (default), updated, -created, created, -priority, priority, key, -key, due, -due |
per_page, page |
Pagination |
Create an issue
Section titled “Create an issue”curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" "$TRACKER/issues" -d '{ "project": "WEB", "title": "Saved filters in the sidebar", "description": "Save the list filters under a name.\n\n- [ ] UI\n- [ ] API", "type": "feature", "priority": "medium", "labels": ["ui"], "assignee": "me", "status": "To do", "parent": "WEB-5", "estimate_hours": 4, "due_date": "2026-11-30"}'# 201 {"data": {"key": "WEB-9", …}}Only project and title are required. Unknown label names are created in the project. The new issue gets the next number of its project.
Update an issue
Section titled “Update an issue”curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ "$TRACKER/issues/WEB-9" -d '{"priority": "high", "labels": ["ui", "api"], "due_date": null}'It accepts the same fields as create, except project. Only the fields present change, and each change is written to the issue’s history.
Change the status
Section titled “Change the status”curl -H "Authorization: Bearer $TOKEN" "$TRACKER/issues/WEB-3/transitions"# {"data":[{"id":"1","name":"To do","category":"todo","current":false},# {"id":"2","name":"In progress","category":"in_progress","current":false},# {"id":"3","name":"Review","category":"in_progress","current":true},# {"id":"4","name":"Done","category":"done","current":false}]}
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ "$TRACKER/issues/WEB-3/transitions" -d '{"status": "Done"}' # or {"status": "4"}Assign
Section titled “Assign”curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ "$TRACKER/issues/WEB-3/assign" -d '{"assignee": "me"}' # "me", a user id, an e-mail, or null to unassignComment
Section titled “Comment”curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ "$TRACKER/issues/WEB-3/comments" -d '{"body": "## Done\n- LCP 1.6 s\n\n## How to check\n1. `npm run build`"}'Log time
Section titled “Log time”curl -H "Authorization: Bearer $TOKEN" "$TRACKER/time-activities"curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ "$TRACKER/issues/WEB-3/time-entries" -d '{"hours": 1.5, "comment": "Profiling", "date": "2026-10-29", "activity": "development"}'hours is required, more than 0 and at most 24. date defaults to today and activity to development.
Attachments
Section titled “Attachments”curl -X POST -H "Authorization: Bearer $TOKEN" -F "file=@screen.png" "$TRACKER/issues/WEB-3/attachments"curl -H "Authorization: Bearer $TOKEN" -o screen.png "$TRACKER/attachments/3"Search
Section titled “Search”curl -H "Authorization: Bearer $TOKEN" "$TRACKER/search?q=login&project=WEB&limit=20"curl -H "Authorization: Bearer $TOKEN" "$TRACKER/search?q=web-3" # the exact key comes firstSearch covers all statuses; pass status=open to narrow it.
Projects and create metadata
Section titled “Projects and create metadata”curl -H "Authorization: Bearer $TOKEN" "$TRACKER/projects"curl -H "Authorization: Bearer $TOKEN" "$TRACKER/projects/WEB/meta"# {"data":{"types":[{"id":"task","name":"Task"},…],"priorities":[…,{"id":"medium","name":"Medium","current":true},…],# "labels":[{"id":"1","name":"backend"},…],"statuses":[…],"members":[{"id":"1","name":"Ada Example"},…]}}Skribly Agent mapping
Section titled “Skribly Agent mapping”The API is shaped after Skribly Agent’s TrackerClient, so a client maps one method to one call. @skribly/core names its client methods after the same operations.
| TrackerClient | Call | Notes |
|---|---|---|
whoami() |
GET /me |
Returns data.name |
mine() |
GET /issues?assignee=me&status=open&sort=-updated&per_page=100 |
|
get(key) |
GET /issues/{key} |
Comments included |
comment(key, md) |
POST /issues/{key}/comments {body} |
Markdown as is |
statuses(key) |
GET /issues/{key}/transitions |
Already {id, name, current} |
setStatus(key, id) |
POST /issues/{key}/transitions {status: id} |
|
assign(key, to) |
POST /issues/{key}/assign {assignee} |
"me", null or an id |
projects() |
GET /projects |
{id: key, key, name} |
createMeta(project) |
GET /projects/{key}/meta |
Types, priorities, labels |
create(input) |
POST /issues {project, title, description, type, priority, labels} |
|
logTime(key, entry) |
POST /issues/{key}/time-entries {hours, comment, date, activity} |
|
activities() |
GET /time-activities |
|
search(q, project) |
GET /search?q=&project= |
OpenAPI
Section titled “OpenAPI”The OpenAPI 3.1 description is the source of the API reference. @skribly/core keeps a copy and tests its client and schemas against it, so the contract, the client and these docs cannot drift apart silently.