Skip to main content

Service Requests API

This section of the DeskOS API covers service requests (maintenance tickets, facilities issues, member complaints). From an outside system such as n8n or Zapier, a facilities-management platform, a tenant portal, a chatbot, or an AI agent you can list the helpdesk teams and categories set up on a campus, create a service request, read it back with its history and public comments, move it through statuses, and close it.

Everything you create shows up in the DeskOS admin console exactly as if a staff member had raised it, with the same automatic routing and notifications.

Read Authentication first

Getting a key, sending it, campus rules, the response shape and the errors every section shares are all on the Authentication page. This page only covers what is specific to service requests.

Building with an AI agent or coding assistant?

Read the general agent instructions on the Authentication page, then the service-request specific rules below.


Base path​

All paths on this page are relative to:

https://oscar.nova.deskos.net/api/v1/helpdesk/external/

Your Developer Settings page shows the exact base URL for your workspace.


Permissions​

Service request permissions all start with helpdesk:. An admin ticks them per key on the Developer Settings page. A key only gets service request permissions if the admin creating it holds Service Requests: manage themselves.

PermissionWhat it allowsEndpoints
helpdesk:catalog:readRead the helpdesk setupGET teams/, GET categories/
helpdesk:requests:readRead service requestsGET service-requests/, GET service-requests/<ref>/
helpdesk:requests:createRaise a service requestPOST service-requests/
helpdesk:requests:updateChange status (any status except CLOSED)PATCH service-requests/<ref>/status/
helpdesk:requests:closeClose a requestPOST service-requests/<ref>/close/, and CLOSED via the status endpoint

A typical "raise tickets from our portal" integration needs helpdesk:catalog:read, helpdesk:requests:create and helpdesk:requests:read. A "work the queue from our field-service tool" integration needs helpdesk:requests:read, helpdesk:requests:update and helpdesk:requests:close.

GET health/ needs no permission and works with any key. Use it to confirm the key carries the helpdesk: permissions you expect before doing anything else.


Campuses​

Teams, categories and service requests all belong to a campus. The general campus rules are on the Authentication page. For this section:

  • List endpoints accept ?campus_id= to narrow results. Without it they return everything the key can reach.
  • Create resolves the campus in this order: campus_id in the body, else the key's default campus, else the organisation's only campus. If none of those applies you get a 400 asking for campus_id.
  • Category and team ids are per campus. Always fetch them for the campus you are about to create on.

Reference values​

Statuses: OPEN, IN_PROGRESS, PENDING, RESOLVED, CLOSED

Priorities: LOW, MEDIUM, HIGH, CRITICAL

Both are accepted in any case on input (high and HIGH are the same) and always returned upper case.

References: wherever a path has <ref>, send either the numeric id or the human-readable sr_id such as SR-202609-0042. Both are returned on every request object.

Timestamps are ISO 8601 with a timezone offset, for example 2026-09-12T18:30:00+05:30.


API endpoints​

MethodPathPermissionPurpose
GEThealth/noneWhat this key is and can do
GETteams/helpdesk:catalog:readHelpdesk teams
GETcategories/helpdesk:catalog:readRequest categories
GETservice-requests/helpdesk:requests:readList and filter requests
POSTservice-requests/helpdesk:requests:createCreate a request
GETservice-requests/<ref>/helpdesk:requests:readOne request with comments and history
PATCHservice-requests/<ref>/status/helpdesk:requests:updateChange status
POSTservice-requests/<ref>/close/helpdesk:requests:closeClose with a resolution note

1. List teams​

GET /api/v1/helpdesk/external/teams/
GET /api/v1/helpdesk/external/teams/?campus_id=7

Returns the helpdesk teams the key can see, with the team lead, active member count, and the categories each team handles. Use it when you want to route a request to a specific team with team_id; otherwise DeskOS routes automatically from the category.

{
"success": true,
"count": 2,
"teams": [
{
"id": 3,
"name": "Maintenance",
"description": "Electrical, plumbing, HVAC",
"campus_id": 7,
"campus_name": "Acme North",
"team_lead": {"id": 88, "name": "Ravi Kumar", "email": "ravi@acme.example"},
"member_count": 4,
"categories": [
{"id": 40, "name": "Facilities", "inherit_to_children": true}
]
}
]
}

inherit_to_children: true means the team also handles every sub-category under that category.


2. List categories​

GET /api/v1/helpdesk/external/categories/
GET /api/v1/helpdesk/external/categories/?campus_id=7

Returns the category tree as a flat list. Each row carries its parent, whether it is a leaf, the default priority and the SLA in hours. sla_hours is the effective figure: the category's own SLA when it has one, otherwise your organisation's setting for that priority. Pick a category id from here for category_id when creating.

{
"success": true,
"count": 5,
"categories": [
{
"id": 40,
"name": "Facilities",
"description": "",
"parent_id": null,
"parent_name": null,
"is_leaf": false,
"default_priority": "MEDIUM",
"sla_hours": 48,
"is_sos": false,
"campus_id": 7,
"campus_name": "Acme North"
},
{
"id": 42,
"name": "Air conditioning",
"description": "",
"parent_id": 40,
"parent_name": "Facilities",
"is_leaf": true,
"default_priority": "HIGH",
"sla_hours": 8,
"is_sos": false,
"campus_id": 7,
"campus_name": "Acme North"
}
]
}
tip

Prefer leaf categories (is_leaf: true) when creating. They carry the most specific routing and SLA.


3. Create a service request​

POST /api/v1/helpdesk/external/service-requests/

Request headers​

Content-Type: application/json
Authorization: Api-Key dk_live_your_key_here

Minimal request body​

{
"title": "AC not cooling in Meeting Room 2",
"category_id": 42
}

This works when the key has a default campus or the organisation has a single campus.

Full request body​

{
"title": "AC not cooling in Meeting Room 2",
"description": "Reported by the 3pm booking. Room is at 29 degrees.",
"campus_id": 7,
"category_id": 42,
"priority": "HIGH",
"requester_email": "priya@tenant.example",
"team_id": 3,
"due_date": "2026-09-12T18:30:00+05:30",
"floor": "3",
"custom_location": "Meeting Room 2, near the east window"
}

Field reference​

FieldRequiredNotes
titleyesUp to 200 characters
category_idyesMust belong to the resolved campus and not be deleted
campus_idsee CampusesInteger
descriptionnoFree text
prioritynoLOW, MEDIUM, HIGH, CRITICAL. Defaults to the category's default priority
requester_emailnoA user in your organisation. Case-insensitive. The request is raised on their behalf and they get in-app updates
requester_idnoSame as above by DeskOS user id. Takes precedence over requester_email
team_idnoSkip automatic routing and assign this team. Must belong to the campus
due_datenoISO 8601 datetime. Defaults from the category's SLA, or your organisation's SLA settings for the priority
floornoFree text, up to 50 characters
custom_locationnoFree text, up to 255 characters

If neither requester_email nor requester_id is sent, the request is recorded against the staff account that created the API key.

Success response: 201 Created​

{
"success": true,
"service_request": {
"id": 9103,
"sr_id": "SR-202609-0042",
"title": "AC not cooling in Meeting Room 2",
"status": "OPEN",
"status_display": "Open",
"priority": "HIGH",
"priority_display": "High",
"category": {"id": 42, "name": "Air conditioning", "parent_id": 40},
"campus": {"id": 7, "name": "Acme North"},
"team": {"id": 3, "name": "Maintenance"},
"requester": {"id": 511, "name": "Priya Sharma", "email": "priya@tenant.example"},
"assignee": {"id": 88, "name": "Ravi Kumar", "email": "ravi@acme.example"},
"floor": "3",
"custom_location": "Meeting Room 2, near the east window",
"due_date": "2026-09-12T18:30:00+05:30",
"resolution_date": null,
"created_at": "2026-09-12T10:04:11+05:30",
"updated_at": "2026-09-12T10:04:11+05:30",
"description": "Reported by the 3pm booking. Room is at 29 degrees.",
"resolution_note": "",
"comments": [],
"history": [
{
"id": 55001,
"change_type": "CREATE",
"field_name": "status",
"old_value": "",
"new_value": "Created via API key 'n8n facilities automation'",
"actor": {"id": 12, "name": "Ops Admin", "email": "ops@acme.example"},
"created_at": "2026-09-12T10:04:11+05:30"
}
]
}
}

Store id or sr_id from the response. You need one of them to read, update or close the request later.

What happens automatically​

  • Without team_id, the request is routed to the team assigned to the category, or to the nearest ancestor category with inheritance on.
  • The team lead becomes the assignee, or the first active team member when there is no lead.
  • Team members are notified over WhatsApp and email according to their notification settings.
  • A due date is set from the category's own SLA when it has one, and from your organisation's SLA settings for the priority when it does not.
  • A history row records that the request came from your API key.

4. List service requests​

GET /api/v1/helpdesk/external/service-requests/

Query parameters​

ParameterNotes
campus_idNarrow to one campus
statusOne or more, comma separated: OPEN,IN_PROGRESS
priorityOne or more, comma separated: HIGH,CRITICAL
category_idOne category
page1-based, default 1
page_sizeDefault 25, maximum 100

Results are newest first and use the list shape (no description, comments or history).

{
"success": true,
"count": 132,
"page": 1,
"page_size": 25,
"has_next": true,
"service_requests": [
{
"id": 9103,
"sr_id": "SR-202609-0042",
"title": "AC not cooling in Meeting Room 2",
"status": "IN_PROGRESS",
"status_display": "In Progress",
"priority": "HIGH",
"priority_display": "High",
"category": {"id": 42, "name": "Air conditioning", "parent_id": 40},
"campus": {"id": 7, "name": "Acme North"},
"team": {"id": 3, "name": "Maintenance"},
"requester": {"id": 511, "name": "Priya Sharma", "email": "priya@tenant.example"},
"assignee": {"id": 88, "name": "Ravi Kumar", "email": "ravi@acme.example"},
"floor": "3",
"custom_location": "Meeting Room 2, near the east window",
"due_date": "2026-09-12T18:30:00+05:30",
"resolution_date": null,
"created_at": "2026-09-12T10:04:11+05:30",
"updated_at": "2026-09-12T11:20:45+05:30"
}
]
}

5. Retrieve a service request​

GET /api/v1/helpdesk/external/service-requests/9103/
GET /api/v1/helpdesk/external/service-requests/SR-202609-0042/

Returns the detail shape shown under Create: the list fields plus description, resolution_note, comments and history.

comments contains only public comments. Internal staff notes are never exposed. history is every change to the request in chronological order, including team and assignee changes made by DeskOS.

A request outside your organisation, or on a campus the key cannot reach, is a 404.


6. Change status​

PATCH /api/v1/helpdesk/external/service-requests/<ref>/status/

POST is also accepted for tools that cannot send PATCH.

Request body​

{
"status": "IN_PROGRESS",
"note": "Technician dispatched, ETA 30 minutes"
}
FieldRequiredNotes
statusyesOne of the five statuses
notefor RESOLVED and CLOSEDStored as the resolution note. On any other status it is kept in the request's history

Rules​

  • RESOLVED and CLOSED need a note unless the request already has a resolution note.
  • Resolving or closing sets resolution_date. Moving back to OPEN, IN_PROGRESS or PENDING clears it.
  • Setting CLOSED through this endpoint needs the helpdesk:requests:close permission in addition to helpdesk:requests:update.
  • Setting the status the request already has is accepted and returns the unchanged request.
  • The requester is notified in the app when the request is resolved or closed.

Success response​

{
"success": true,
"previous_status": "OPEN",
"service_request": { "...": "full detail shape" }
}

7. Close a service request​

POST /api/v1/helpdesk/external/service-requests/<ref>/close/
{
"note": "Gas refilled, cooling verified at 21 degrees"
}

Same note rule as resolve. Returns previous_status and the full request. A request that is already closed answers 409:

{"success": false, "errors": {"status": "This request is already closed."}}

Errors specific to this section​

Authentication, missing permission, suspension and rate limit errors are the same in every section and are listed on the Authentication page. The ones below are specific to service requests.

400: Validation error​

{
"success": false,
"errors": {
"title": "This field is required.",
"priority": "Use one of LOW, MEDIUM, HIGH, CRITICAL."
}
}

400: Campus could not be resolved​

{
"success": false,
"errors": {
"campus_id": "This organisation has several campuses. Pass campus_id or set a default campus on the key."
}
}

403: Missing permission​

{"detail": "This API key does not have the 'helpdesk:requests:close' scope."}

404: Not found​

Returned for a request the key cannot see, or a campus_id filter on a list endpoint that the key cannot reach.

{"success": false, "errors": {"detail": "No service request matching 'SR-202609-9999'."}}

409: Already closed​

{"success": false, "errors": {"status": "This request is already closed."}}

Integration examples​

Direct HTTP call: cURL​

BASE=https://oscar.nova.deskos.net/api/v1/helpdesk/external
KEY=dk_live_your_key_here

# 1. Find the category id
curl "$BASE/categories/?campus_id=7" -H "Authorization: Api-Key $KEY"

# 2. Raise the request
curl -X POST "$BASE/service-requests/" \
-H "Authorization: Api-Key $KEY" \
-H "Content-Type: application/json" \
-d '{"title": "Projector not working in Boardroom", "category_id": 45, "campus_id": 7, "priority": "MEDIUM"}'

# 3. Move it along
curl -X PATCH "$BASE/service-requests/SR-202609-0043/status/" \
-H "Authorization: Api-Key $KEY" \
-H "Content-Type: application/json" \
-d '{"status": "IN_PROGRESS", "note": "AV vendor called"}'

# 4. Close it
curl -X POST "$BASE/service-requests/SR-202609-0043/close/" \
-H "Authorization: Api-Key $KEY" \
-H "Content-Type: application/json" \
-d '{"note": "HDMI cable replaced"}'

Direct HTTP call: Python​

import os
import requests

BASE = "https://oscar.nova.deskos.net/api/v1/helpdesk/external"
HEADERS = {"Authorization": f"Api-Key {os.environ['DESKOS_API_KEY']}"}

def create_request(title, category_id, campus_id=None, **extra):
body = {"title": title, "category_id": category_id, **extra}
if campus_id:
body["campus_id"] = campus_id
r = requests.post(f"{BASE}/service-requests/", json=body, headers=HEADERS, timeout=15)
data = r.json()
if not data.get("success"):
raise RuntimeError(f"DeskOS refused the request: {data.get('errors') or data.get('detail')}")
return data["service_request"]

sr = create_request("Water leak under sink", category_id=47, campus_id=7, priority="CRITICAL")
print(sr["sr_id"], sr["assignee"]["name"] if sr["assignee"] else "unassigned")

Direct HTTP call: JavaScript / Node.js​

const BASE = 'https://oscar.nova.deskos.net/api/v1/helpdesk/external';
const headers = {
'Authorization': `Api-Key ${process.env.DESKOS_API_KEY}`,
'Content-Type': 'application/json',
};

async function closeRequest(ref, note) {
const res = await fetch(`${BASE}/service-requests/${ref}/close/`, {
method: 'POST',
headers,
body: JSON.stringify({ note }),
});
const data = await res.json();
if (!data.success) throw new Error(JSON.stringify(data.errors || data.detail));
return data.service_request;
}

n8n / Zapier / Make​

Use an HTTP Request node. Set the method and URL from the endpoint table, add a header Authorization with value Api-Key dk_live_..., set the body type to JSON, and map your fields. Read success from the response to branch on failure.


Instructions for AI agents​

The general rules (how a key is obtained, how to handle it, what to do on 401 and 403) are on the Authentication page. Read those first. This section adds what is specific to service requests.

Permissions to ask for​

Ask only for the helpdesk: permissions the task needs. Do not ask for all five out of convenience.

  • "Raise tickets when a sensor trips": helpdesk:catalog:read and helpdesk:requests:create, and probably helpdesk:requests:read to confirm.
  • "Close tickets when our field tool marks a job done": helpdesk:requests:read and helpdesk:requests:close.
  • "Mirror the queue into our dashboard": helpdesk:requests:read only.

The human also decides whose account the key is created under. That account becomes the fallback requester and the actor recorded on history rows. If the integration acts on behalf of members, prefer sending requester_email so the right person is recorded.

  1. Call GET health/. It confirms the key works and returns scopes, allowed_campuses and default_campus. Use the answer to decide whether you need to send campus_id and whether every operation you plan is in scope, before doing anything else.
  2. Call GET categories/ (and GET teams/ if you plan to route manually) for the campus you will create on. Cache the ids; they are stable but per campus.
  3. Prefer a leaf category (is_leaf: true). If the human's description does not map cleanly to one, show them the category names and ask them to pick, rather than guessing.
  4. Create with a clear title and put everything you know into description. Human staff read these.
  5. Keep the returned sr_id alongside your own record. It is what staff will quote back.

Behaviour rules​

  • Do not create duplicates. There is no server-side deduplication. Before raising a request for a recurring signal, list open requests (status=OPEN,IN_PROGRESS,PENDING) with the same category_id and campus and check whether one already covers it.
  • Do not resolve or close a request you did not open unless the human has explicitly asked for that workflow. Staff may be mid-way through the work.
  • Always send a meaningful note when resolving or closing. It is shown to the requester and kept as the resolution record. "Done" is not a meaningful note.
  • Respect the daily limit. health/ shows requests_today against daily_limit. The limit is shared by every section the key can reach. Poll with a sensible interval and page through lists with page_size up to 100 instead of hammering single reads.
  • Treat 404 on a request as "not visible to this key", not proof that it never existed. It may be on a campus the key cannot reach.
  • Do not modify tickets to test. Use health/, categories/ and teams/ to verify connectivity. If you must create a test ticket, title it clearly as a test, tell the human, and close it yourself with a note saying it was a test.

Reporting back to the human​

When you finish, tell the human:

  • the key name you used (never the key itself),
  • which permissions it turned out to need,
  • the sr_id of anything you created,
  • any 403 or 429 you hit, so they can adjust the key.

Frequently asked questions​

Can I attach files or photos? Not through this API yet. Put a link to the image in description.

Can I add a comment without changing status? Send the current status with a note. The note is written to the request's history and the status stays the same.

Can the API tell me when staff change a request? Not yet. Poll GET service-requests/<ref>/ or the list endpoint with a status filter. Outbound webhooks are planned.

Who is recorded as the requester if I do not send one? The staff account that created the API key. Send requester_email when you know who is affected so the right member gets notified.

Can the same key also book meeting rooms? Yes, if the admin ticked meeting_rooms: permissions on it as well. One key can span several sections. See Meeting Rooms.


Quick reference​

Base:    https://oscar.nova.deskos.net/api/v1/helpdesk/external/
Auth: Authorization: Api-Key dk_live_...

GET health/
GET teams/?campus_id=
GET categories/?campus_id=
GET service-requests/?campus_id=&status=&priority=&category_id=&page=&page_size=
POST service-requests/ {title, category_id, campus_id?, ...}
GET service-requests/<id or sr_id>/
PATCH service-requests/<id or sr_id>/status/ {status, note?}
POST service-requests/<id or sr_id>/close/ {note}

Permissions: helpdesk:catalog:read, helpdesk:requests:read, helpdesk:requests:create,
helpdesk:requests:update, helpdesk:requests:close
Statuses: OPEN, IN_PROGRESS, PENDING, RESOLVED, CLOSED
Priorities: LOW, MEDIUM, HIGH, CRITICAL

Getting help​

API keys, permissions, campus restrictions, revocation and the access log are all self-service from Developer Settings in your admin console (avatar menu, top right). For a higher daily limit or anything else, reach the DeskOS team through your usual support channel.