API

This page documents the HTTP API in Traq 3.9, based on the routes and controllers in the 3.9.0 source.

Traq has no separate API application. There are two groups of JSON endpoints:

  1. Dedicated endpoints under /api/… that always return JSON.
  2. Regular pages that also return JSON when you ask for it (the same URLs the web UI uses).

Warning

The API is currently being refactored to be RESTful. Endpoints, request formats and response shapes described here may change in a future release.

Note

Examples use https://traq.example.com. If Traq is installed in a subdirectory, include it in the base URL.

Authentication

Generate an API key from your User CP (/usercp, "Create API key"). Send it in the X-API-Key header, optionally prefixed with Bearer:

curl -H "X-API-Key: YOUR_KEY" https://traq.example.com/api/auth
curl -H "X-API-Key: Bearer YOUR_KEY" https://traq.example.com/api/auth

Requests made with a valid key act as that user, with that user's group and project permissions. Without a key (or session cookie) you are the guest user, so public projects are readable without authentication.

Requests authenticated with an API key are treated as API requests: JSON is returned for every controller action, and some actions (ticket create, update and move) return JSON instead of redirecting.

Requesting JSON

A regular (non-/api) URL returns JSON if any of these is true:

  • The path ends in .json, e.g. /projects.json
  • The Accept header is exactly application/json
  • The request is authenticated with an API key

Request bodies

POST data is read from $_POST, so send form-encoded (application/x-www-form-urlencoded or multipart/form-data) bodies. Query string parameters are also accepted by most actions.

Two endpoints are the exception and read a raw JSON body: POST /api/search and POST /{project}/tickets/{id}/related-tickets.

Responses and errors

  • Responses are JSON with Content-Type: application/json.
  • The keys password, login_hash, api_key and private_key are stripped from every JSON response.
  • Validation failures when creating a ticket return 422 with {"errors": …}.
  • Permission failures return 403 ({"error": "…"}) when the request has Accept: application/json, otherwise an HTML error page. Some legacy paths return 401.
  • A missing project returns 404; a project you can't view returns 403.
  • Dates are YYYY-MM-DD HH:MM:SS strings as stored in the database.

{project} below is the project slug.


Dedicated /api endpoints

All are GET, all return JSON. Endpoints with a project slug require the view permission for that project.

Endpoint Description
/api/auth Current user, or false if not authenticated
/api/auth/{project} Same, plus the user's permissions in that project
/api/types All ticket types
/api/types/{id} One ticket type
/api/statuses All ticket statuses
/api/priorities All ticket priorities
/api/{project}/components Project components
/api/{project}/custom-fields Project custom fields
/api/{project}/members Project members and roles
/api/search Search tickets and milestones (POST, JSON body)

GET /api/auth

{
  "id": 1,
  "username": "jack",
  "name": "Jack",
  "group_id": 1,
  "locale": "enUS",
  "group": { "id": "1", "name": "Admin", "is_admin": true },
  "permissions": {}
}

permissions is only populated on /api/auth/{project}, and only contains permissions that are granted, as { "action": true }. Returns false when there is no authenticated user.

Types, statuses, priorities

// /api/types
[{ "id": 1, "name": "Defect", "bullet": "-", "changelog": 1, "template": "" }]

// /api/statuses
[{ "id": 1, "name": "New", "status": 1, "changelog": true }]

// /api/priorities
[{ "id": 1, "name": "Highest" }]

For statuses, status is the status type: 0 closed, 1 open, 2 started.

GET /api/{project}/components

[{ "id": 1, "name": "Core", "project_id": 1 }]

GET /api/{project}/custom-fields

Each item contains the custom field columns: id, name, slug, type (default text), values, multiple, default_value, regex, min_length, max_length, is_required, project_id, ticket_type_ids.

GET /api/{project}/members

[{ "id": 2, "username": "sam", "name": "Sam", "role": "Developer" }]

POST /api/search

Body is raw JSON.

Field Type Description
query string Required. Matched with LIKE %query% against ticket summaries and milestone names
project string Optional project slug to limit the search

Results are limited to 25 tickets and 25 milestones, only from projects the user can view.

curl -X POST -H "X-API-Key: KEY" -d '{"query":"crash","project":"traq"}' \
  https://traq.example.com/api/search
{
  "tickets": [
    { "ticket_id": 12, "summary": "Crash on save", "project": { "name": "Traq", "slug": "traq" } }
  ],
  "milestones": [
    { "name": "3.9", "codename": null, "slug": "3.9", "project": { "name": "Traq", "slug": "traq" } }
  ]
}

Projects and milestones

Method Path Returns
GET /projects Array of all projects
GET /{project}.json The project
GET /{project}/roadmap.json Array of milestones (default: active ones)
GET /{project}/roadmap/{completed\|all\|cancelled}.json Filtered milestones
GET /{project}/milestone/{slug}.json One milestone

/projects needs .json or Accept: application/json (or an API key) to return JSON.

Project fields: id, name, slug, codename, info, enable_wiki, default_ticket_type_id, default_ticket_sorting, displayorder.

Milestone fields: id, name, slug, codename, info, changelog, due, completed_on, status, is_locked, project_id, displayorder.


Tickets

GET /{project}/tickets

Requires view_tickets. Returns a page of tickets.

{
  "page": 1,
  "total_pages": 3,
  "tickets": [
    {
      "ticket_id": 12,
      "summary": "Crash on save",
      "user_id": 2,
      "assigned_to_id": null,
      "votes": 0,
      "created_at": "2026-01-01 12:00:00",
      "updated_at": null,
      "is_closed": 0,
      "owner": "Sam",
      "assignee": null,
      "type": "Defect",
      "milestone": "3.9",
      "milestone_slug": "3.9",
      "version": null,
      "version_slug": null,
      "component": "Core",
      "status": "New",
      "status_id": 1,
      "priority": "Normal",
      "priority_id": 3,
      "severity": "Normal",
      "severity_id": 4,
      "project_id": 1,
      "project_slug": "traq"
    }
  ]
}

Custom fields are added to each ticket under their slug. Page size is the tickets_per_page setting.

Query parameters

Parameter Description
page Page number (default 1)
order_by field.direction, e.g. created_at.desc. Defaults to the project's default sorting
any filter field Filter, see below

Sortable fields: ticket_id, summary, votes, created_at, updated_at, owner (or user), assigned_to, milestone, component, type, status, priority, severity, plus custom field slugs. Direction is asc or desc.

Filterable fields:

Field Matching
q Search ticket summary and ticket ID (contains)
summary, description, owner Contains
milestone, version Exact, by slug
status, type, component, priority, severity, assigned_to Exact, by name
status_type Status type: 0 closed, 1 open, 2 started
custom field slugs Contains

Multiple values are comma separated (status=New,Open); prefix the value with ! to negate (type=!Task). * is a wildcard in "contains" filters. For status the special values allopen, allclosed and allstarted match by status type. Unknown parameters are ignored.

curl -H "X-API-Key: KEY" \
  "https://traq.example.com/traq/tickets?status=allopen&milestone=3.9&order_by=updated_at.desc"

POST /{project}/tickets

Creates a ticket (same as /{project}/tickets/new). Requires create_tickets. Returns {"ticket": {…}} for API-key requests, or 422 with {"errors": …}.

Form fields:

Field Notes
summary Required
description Ticket body
type Type ID (default 1)
milestone, version, component, assigned_to IDs; 0 for none
severity (default 4), priority (default 3), status (default 1) IDs
tasks JSON-encoded array of {"task": "…", "completed": false}
time_proposed, time_worked Strings
custom_fields[{field_id}] Custom field values

Each property beyond summary, description, type and privacy requires the matching ticket_properties_set_* permission, otherwise it is ignored. Non-admin users are subject to the ticket creation delay.

GET /{project}/tickets/{ticket_id}

Requires view_tickets.

{
  "ticket": { "id": 12, "ticket_id": 12, "summary": "…", "body": "…", "type": {"id": 1, "name": "Defect"}, "…": "…" },
  "attachments": [ { "id": 1, "name": "log.txt", "type": "text/plain", "size": 120, "user_id": 2, "ticket_id": 5, "created_at": "…" } ],
  "ticketHistory": [ { "id": 1, "user_id": 2, "ticket_id": 5, "changes": [], "comment": "…", "created_at": "…" } ]
}

Ticket objects have project, user, assigned_to, milestone, version, component, status, priority, severity and type expanded to {id, name} objects (users also include username), plus votes, tasks, extra, time_proposed, time_worked, is_closed, created_at, updated_at. Attachment contents are not included.

POST /{project}/tickets/{ticket_id}/update

Requires update_tickets. Returns {"ticket": {…}}.

Fields: summary, type, milestone, version, component, severity, priority, status, assigned_to, tasks, time_proposed, time_worked, custom_fields[…], comment, and a file upload attachment. Each property needs the matching ticket_properties_change_* permission; omitted fields keep their current value. Attachments need add_attachments.

POST /{project}/tickets/{ticket_id}/move

Requires move_tickets and create_tickets in the destination project. API requests must be POST (otherwise 400 {"error": "POST required"}).

Field Description
project_id Destination project ID
milestone_id, version_id, component_id, assigned_to_id Optional values in the destination project

Returns {"success": true, "project": "new-slug", "ticket_id": 7}.

POST /{project}/tickets/mass-actions

Requires update_tickets and perform_mass_actions. Fields: tickets (array of ticket IDs, or JSON string), type, milestone, version, component, severity, priority, status, assigned_to (value -1 or omitted means "don't change"), and comment. Returns {"success": true}.

GET /{project}/tickets/{ticket_id}/related-tickets

{ "related_tickets": [], "relation_types": [{ "id": 1, "name": "Relates to", "inverse_type_id": 1 }] }

POST with a JSON body {"related_tickets": [{"ticket_id": 3, "relation_type_id": 2}]} replaces the set of related tickets (requires ticket_properties_change_related_tickets; existing relations missing from the list are removed) and returns the same structure as GET.

Built-in relation types: 1 Relates to, 2 Blocks, 3 Is Blocked By, 4 Duplicates, 5 Is Duplicated By, 6 Contains, 7 Is Contained By.

Other ticket actions

Method Path Notes
POST /{project}/tickets/{id}/tasks/{task_id} Toggle a task. Needs ticket_properties_complete_tasks. Returns {"success": bool}
POST /{project}/tickets/{id}/vote Vote. Returns an HTML/JS fragment, not JSON
POST /{project}/tickets/{id}/delete Needs delete_tickets. Redirects

Wiki

Requires the project to have the wiki enabled.

Method Path Returns
GET /{project}/wiki {"page": {…}} (main page)
GET /{project}/wiki/_pages {"pages": […]}
GET /{project}/wiki/{slug} {"page": {…}}
GET /{project}/wiki/{slug}/_revisions {"page": {…}, "revisions": […]}
GET /{project}/wiki/{slug}/_revisions/{n} {"page": {…}, "revision": {…}}
POST /{project}/wiki/_new Create. Fields title, slug, body. See note below
POST /{project}/wiki/{slug}/_edit Edit. Fields title, slug, body. See note below
POST /{project}/wiki/{slug}/_delete Returns {"success": true}

Warning

The create and edit actions save the page, but in 3.9 they build the JSON response without returning it, so the response body is not the {"page": …} JSON you'd expect. Re-fetch the page with GET to confirm.

Page fields: id, project_id, title, slug, main, revision_id. Revision fields: id, wiki_page_id, revision, content, user_id, created_at, updated_at. Create, edit and delete need the corresponding wiki permissions.


Admin

These need an admin user (or appropriate permissions).

Ticket types

Method Path Fields Returns
GET /admin/tickets/types {"types": […]}
POST /admin/tickets/types/new name, bullet, changelog, template {"type": {…}}
POST /admin/tickets/types/{id}/edit same, all optional {"type": {…}}

Priorities

Method Path Fields Returns
GET /admin/priorities {"priorities": […]}
POST /admin/priorities/new name {"priority": {…}}
POST /admin/priorities/{id}/edit name {"priority": {…}}
POST /admin/priorities/{id}/delete {"success": true}

Statuses

Method Path Fields Returns
GET /admin/tickets/statuses {"statuses": […]}
POST /admin/tickets/statuses/new name, status (0 closed, 1 open, 2 started), changelog {"status": {…}}
POST /admin/tickets/statuses/{id}/edit same, all optional {"status": {…}}
POST /admin/tickets/statuses/{id}/delete {"success": true}

Severities

Method Path Fields Returns
GET /admin/severities {"severities": […]}
POST /admin/severities/new name {"severity": {…}}
POST /admin/severities/{id}/edit name {"severity": {…}}
POST /admin/severities/{id}/delete {"success": true}

See Ticket Properties for what these mean.


Other JSON endpoints

Method Path Notes
GET /_ajax/autocomplete/username?term=ab Usernames starting with term (* is a wildcard)
POST /{project}/timeline/{id}/delete Needs delete_timeline_events. Returns {"success": false} with 403 when denied

Caveats

  • Traq 3.9 is in development, so details here may change before release.
  • JSON support on the non-/api routes is per-action. Only the endpoints listed above were found to return JSON; others return HTML regardless of the .json extension.
  • Many actions still use browser-style conventions (form-encoded bodies, redirects on success when not using an API key).
  • Several other admin and project settings controllers (projects, users, groups, project roles, milestones, components, custom fields, members and project settings) return JSON from their create, edit and delete actions, but only for requests authenticated with an API key. They aren't documented here yet.