Skip to content

REST API Tickets

Ed Mozley edited this page Jul 2, 2026 · 1 revision

🎫 REST API: Tickets

The complete usage guide for the Tickets module of the REST API β€” every URL, method, parameter and response shape. This mirrors the interactive documentation built into the product (System β†’ API β†’ Documentation), where each endpoint also has a live "Try it" tester.

Note

This is the first of the per-module API guides. As the API grows (assets, problems, changes…), each module gets its own page like this one.


πŸš€ Quick start

1. Create a key under System β†’ API (grant it only what it needs β€” for the example below, just Tickets: create). 2. Copy the key when it's shown β€” that's the only time you'll see it. 3. Call the API:

curl -X POST "https://your-server/api/v1/tickets" \
  -H "Authorization: Bearer fitsm_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
        "subject": "Disk space low on SQL01",
        "requester_email": "alerts@example.com",
        "description": "C: drive below 5% free.",
        "priority": "High"
      }'

That's a ticket in the inbox β€” with the description as its opening message, an audit entry, and any matching workflows already fired. πŸŽ‰

The basics

Base URL https://your-server/api/v1 β€” shown on the System β†’ API page for your install. No mod_rewrite? Use …/api/v1/index.php/<path> instead; identical behaviour.
Authentication Authorization: Bearer fitsm_… on every request (X-Api-Key: fitsm_… also accepted).
Format JSON in, JSON out (UTF-8). Timestamps are always ISO 8601 UTC: 2026-07-02T22:18:22Z.
Success {"data": …} β€” plus "meta" (pagination) on list endpoints. Status 200, or 201 on creation.
Errors {"error": {"code": "…", "message": "…"}} with a real status: 401 bad key Β· 403 missing permission (named in the message) Β· 404 not found/not visible Β· 405 wrong verb Β· 409 conflict Β· 422 invalid input Β· 429 rate limited.
Rate limit 60 requests/min per key by default (per-key override available). Watch X-RateLimit-Remaining; a 429 means slow down.
Permissions Each endpoint below shows its required permission as a badge, e.g. πŸ”‘ tickets.read.
Companies (MSP) On multi-company installs a key only sees its scoped companies. null company fields mean the Default company. Single-company installs can ignore everything about company_id.
PATCH semantics Send only the fields you want to change. Omitted = untouched; explicit null (or "") = cleared. Re-sending the same PATCH is safe β€” no duplicate audit rows or emails.
Name or id Lookups accept either: "priority": "High" or "priority_id": 3.

🀝 Getting started endpoints

🟒 GET /ping β€” check a key works Β  πŸ”‘ none

Verifies authentication and returns what the key can do β€” its name, the analyst it acts as, its permission map, company scope and expiry. Any valid key can call it.

{ "data": { "ok": true,
    "key": { "name": "Monitoring", "acts_as": "Administrator",
             "permissions": { "tickets": ["read", "create"] },
             "companies": null, "expires_at": null },
    "server_time": "2026-07-02T22:18:09Z" } }

🟒 GET / β€” endpoint index Β  πŸ”‘ none

The API version and the full list of routes.


🎟️ Tickets

🟒 GET /tickets β€” list / search Β  πŸ”‘ tickets.read

Trash is excluded unless deleted=true. Results are limited to the key's company scope.

Parameter Description
state open | closed | all (default all)
status / status_id Filter by status name (e.g. In Progress) or id
priority / priority_id Filter by priority name or id
assigned_analyst_id Filter by assignee Β· unassigned=true finds unassigned tickets
requester_email / user_id Filter by requester
ticket_type_id Β· origin_id Β· department_id Β· company_id More id filters
q Search subject and ticket number
created_since Β· created_before Β· updated_since Β· closed_since ISO 8601 date-time bounds
deleted true lists the trash instead
sort created_at, updated_at, closed_at, id, subject, ticket_number, priority, status β€” prefix - for descending (default -created_at)
page / per_page Pagination β€” default 1 / 25, max per_page 100
GET /tickets?state=open&priority=High&updated_since=2026-07-01T00:00:00Z&sort=-updated_at&per_page=50
{ "data": [ { "id": 139, "ticket_number": "WBK-509-34271",
              "subject": "API test - printer jam",
              "status":   { "id": 2, "name": "In Progress", "is_closed": false },
              "priority": { "id": 3, "name": "High" },
              "ticket_type": null, "origin": null, "department": null,
              "assigned_analyst": { "id": 1, "name": "Administrator" },
              "requester": { "id": 173, "email": "jane@example.com", "name": "Jane Smith" },
              "company":   { "id": 1, "name": "Default" },
              "first_time_fix": null, "it_training_provided": null,
              "created_at": "2026-07-02T22:18:22Z", "updated_at": "2026-07-02T22:18:47Z",
              "closed_at": null, "work_start_at": null, "deleted_at": null } ],
  "meta": { "page": 1, "per_page": 50, "total": 1, "total_pages": 1 } }

πŸ”΅ POST /tickets β€” create Β  πŸ”‘ tickets.create

Creates a ticket exactly like the UI: the requester is found or created by email, the description becomes the ticket's opening message (so it reads like any other ticket in the inbox), an audit entry is written, and the workflow engine fires ticket.created. Defaults: status Open, priority Normal, unassigned, the key's default company. Returns 201 with the full ticket.

Field Required Description
subject βœ… Ticket subject
requester_email βœ… Requester's email β€” found or created automatically
requester_name Display name, used if the requester is new
description The request text (plain text; becomes the ticket body)
status / status_id Override the default Open
priority / priority_id Override the default Normal
ticket_type_id Β· origin_id Β· department_id Optional classification
assigned_analyst_id Assign on creation
company_id File under a specific company (must be in the key's scope)
mailbox_id Send-from mailbox for later replies (validated against the company)

🟒 GET /tickets/{id} β€” get one Β  πŸ”‘ tickets.read

Everything in the list shape plus description_html β€” the original request body.

🟠 PATCH /tickets/{id} β€” update Β  πŸ”‘ tickets.update

Updates any combination of fields, with full UI parity: closing sets closed_at (and reopening clears it), assignment keeps the owner in sync and sends the assignment email, closing sends the closure email and can auto-trigger a CSAT survey, every change lands in the audit log, and the workflow engine fires ticket.status_changed / ticket.priority_changed / ticket.assigned. Returns the updated ticket.

Field Notes
subject Cannot be blanked
status / status_id By name or id
priority / priority_id null clears
ticket_type_id Β· origin_id Β· department_id null clears
assigned_analyst_id null unassigns
first_time_fix Β· it_training_provided Booleans
work_start_at Scheduled work start, ISO 8601 (null clears)
company_id Move to another company β€” the key must be scoped to both sides
{ "status": "In Progress", "priority": "High", "assigned_analyst_id": 1 }

Warning

A ticket in the trash answers 409 conflict to updates β€” restore it first.

πŸ”΄ DELETE /tickets/{id} β€” trash Β  πŸ”‘ tickets.delete

Soft-deletes (moves to the trash β€” same as the UI, fully restorable). Already trashed β†’ 409.

πŸ”΅ POST /tickets/{id}/restore β€” restore Β  πŸ”‘ tickets.restore

Brings a ticket back from the trash and returns it.


πŸ’¬ Notes, conversation & history

🟒 GET /tickets/{id}/notes Β  πŸ”‘ ticket_notes.read

All notes, oldest first β€” is_internal: true marks private analyst notes.

πŸ”΅ POST /tickets/{id}/notes Β  πŸ”‘ ticket_notes.create

Field Required Description
text βœ… The note text
is_internal false = public note (default true)

The note is attributed to the analyst the key acts as. Returns 201.

🟒 GET /tickets/{id}/thread Β  πŸ”‘ ticket_thread.read

The full conversation β€” inbound emails / channel messages (channel is email, whatsapp, …) and outbound replies β€” oldest first, with HTML bodies:

{ "data": [ { "id": 512, "direction": "Inbound", "is_initial": true, "channel": "email",
              "subject": "Printer jam", "from": { "address": "jane@example.com", "name": "Jane Smith" },
              "to": "support@company.com", "cc": null,
              "body_preview": "Paper jam error, tray 2.", "body_html": "<p>Paper jam error, tray 2.</p>",
              "received_at": "2026-07-02T09:14:03Z" } ] }

🟒 GET /tickets/{id}/audit Β  πŸ”‘ ticket_audit.read

Every audited change with old/new values and who made it:

{ "data": [ { "id": 120, "field": "Status", "old_value": "Open", "new_value": "In Progress",
              "analyst": { "id": 1, "name": "Administrator" },
              "created_at": "2026-07-02T22:18:47Z" } ] }

🟒 GET /tickets/{id}/sla Β  πŸ”‘ ticket_sla.read

The live SLA computation β€” the same engine the UI uses: response/resolution targets from the ticket's priority, elapsed and remaining business minutes against the SLA calendar (with paused statuses honoured), percentages and breach flags.


⏱️ Time tracking

🟒 GET /tickets/{id}/time-entries Β  πŸ”‘ ticket_time_entries.read

Active time entries with analyst, minutes, notes and timestamp.

πŸ”΅ POST /tickets/{id}/time-entries Β  πŸ”‘ ticket_time_entries.create

Field Required Description
minutes βœ… Positive integer
notes What the time was spent on
entry_at When the work happened, ISO 8601 (default now)

πŸ”΄ DELETE /tickets/{id}/time-entries/{entry_id} Β  πŸ”‘ ticket_time_entries.delete

Soft-deletes the entry (same as the UI).


πŸ‘€ Requesters

End users who raise tickets (distinct from analysts). POST /tickets creates requesters automatically β€” these endpoints are for lookups and directory sync.

🟒 GET /users Β  πŸ”‘ users.read

Parameter Description
q Search email and names
email Exact email lookup
page / per_page Pagination

🟒 GET /users/{id} Β  πŸ”‘ users.read

One requester, plus their ticket counts (within the key's company scope):

{ "data": { "id": 173, "email": "jane@example.com", "display_name": "Jane Smith",
            "preferred_name": null, "created_at": "2026-07-02T22:18:22Z",
            "tickets": { "total": 12, "open": 2 } } }

πŸ”΅ POST /users Β  πŸ”‘ users.create

email (required, unique β€” duplicate β†’ 409), display_name, preferred_name. Returns 201.

🟠 PATCH /users/{id} Β  πŸ”‘ users.update

Change email (uniqueness enforced), display_name, preferred_name.


πŸ“š Reference data

Everything an integration needs to build valid writes β€” grab these once and cache them.

Endpoint Permission Returns
🟒 GET /analysts πŸ”‘ analysts.read Active analysts (id, name, email) β€” assignment targets
🟒 GET /companies πŸ”‘ companies.read The companies this key can see (id, name, is_default)
🟒 GET /statuses πŸ”‘ reference.read Statuses with is_closed, is_default, pauses_sla, colour
🟒 GET /priorities πŸ”‘ reference.read Priorities with SLA response/resolution targets (minutes)
🟒 GET /ticket-types πŸ”‘ reference.read Ticket types β€” pass ?company_id= to apply that company's add/hide overrides
🟒 GET /origins πŸ”‘ reference.read Ticket origins β€” ?company_id= as above
🟒 GET /departments πŸ”‘ reference.read Departments

πŸ§ͺ Worked example: monitoring integration

A typical "alert β†’ ticket β†’ auto-resolve" round trip using a key with just tickets: read, create, update:

B="https://your-server/api/v1"; K="Authorization: Bearer fitsm_…"

# 1. Alert fires β†’ raise a High ticket
TICKET=$(curl -s -X POST "$B/tickets" -H "$K" -H "Content-Type: application/json" -d '{
  "subject": "[ALERT] SQL01 disk C: below 5%",
  "requester_email": "alerts@example.com",
  "description": "Datastore free space 4.2%. Threshold 5%.",
  "priority": "High"}' | jq -r '.data.id')

# 2. Alert recovers β†’ find our open ticket and close it with a note*
curl -s -X PATCH "$B/tickets/$TICKET" -H "$K" -H "Content-Type: application/json" \
  -d '{"status": "Closed"}'

* closing via the API sends the closure email and fires your workflows, exactly as if an analyst clicked it. Give the key ticket_notes: create too if you want the recovery details on the ticket.


Under the hood: REST API β€” How It Works. Keys & permissions: System β†’ API in the product (System module). Live tester: System β†’ API β†’ Documentation.

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally