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.
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.
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.
| Permission | What it allows | Endpoints |
|---|---|---|
helpdesk:catalog:read | Read the helpdesk setup | GET teams/, GET categories/ |
helpdesk:requests:read | Read service requests | GET service-requests/, GET service-requests/<ref>/ |
helpdesk:requests:create | Raise a service request | POST service-requests/ |
helpdesk:requests:update | Change status (any status except CLOSED) | PATCH service-requests/<ref>/status/ |
helpdesk:requests:close | Close a request | POST 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_idin the body, else the key's default campus, else the organisation's only campus. If none of those applies you get a 400 asking forcampus_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
| Method | Path | Permission | Purpose |
|---|---|---|---|
| GET | health/ | none | What this key is and can do |
| GET | teams/ | helpdesk:catalog:read | Helpdesk teams |
| GET | categories/ | helpdesk:catalog:read | Request categories |
| GET | service-requests/ | helpdesk:requests:read | List and filter requests |
| POST | service-requests/ | helpdesk:requests:create | Create a request |
| GET | service-requests/<ref>/ | helpdesk:requests:read | One request with comments and history |
| PATCH | service-requests/<ref>/status/ | helpdesk:requests:update | Change status |
| POST | service-requests/<ref>/close/ | helpdesk:requests:close | Close 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"
}
]
}
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
| Field | Required | Notes |
|---|---|---|
title | yes | Up to 200 characters |
category_id | yes | Must belong to the resolved campus and not be deleted |
campus_id | see Campuses | Integer |
description | no | Free text |
priority | no | LOW, MEDIUM, HIGH, CRITICAL. Defaults to the category's default priority |
requester_email | no | A user in your organisation. Case-insensitive. The request is raised on their behalf and they get in-app updates |
requester_id | no | Same as above by DeskOS user id. Takes precedence over requester_email |
team_id | no | Skip automatic routing and assign this team. Must belong to the campus |
due_date | no | ISO 8601 datetime. Defaults from the category's SLA, or your organisation's SLA settings for the priority |
floor | no | Free text, up to 50 characters |
custom_location | no | Free 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
| Parameter | Notes |
|---|---|
campus_id | Narrow to one campus |
status | One or more, comma separated: OPEN,IN_PROGRESS |
priority | One or more, comma separated: HIGH,CRITICAL |
category_id | One category |
page | 1-based, default 1 |
page_size | Default 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"
}
| Field | Required | Notes |
|---|---|---|
status | yes | One of the five statuses |
note | for RESOLVED and CLOSED | Stored as the resolution note. On any other status it is kept in the request's history |
Rules
RESOLVEDandCLOSEDneed anoteunless the request already has a resolution note.- Resolving or closing sets
resolution_date. Moving back toOPEN,IN_PROGRESSorPENDINGclears it. - Setting
CLOSEDthrough this endpoint needs thehelpdesk:requests:closepermission in addition tohelpdesk: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:readandhelpdesk:requests:create, and probablyhelpdesk:requests:readto confirm. - "Close tickets when our field tool marks a job done":
helpdesk:requests:readandhelpdesk:requests:close. - "Mirror the queue into our dashboard":
helpdesk:requests:readonly.
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.
Recommended first steps
- Call
GET health/. It confirms the key works and returnsscopes,allowed_campusesanddefault_campus. Use the answer to decide whether you need to sendcampus_idand whether every operation you plan is in scope, before doing anything else. - Call
GET categories/(andGET teams/if you plan to route manually) for the campus you will create on. Cache the ids; they are stable but per campus. - 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. - Create with a clear
titleand put everything you know intodescription. Human staff read these. - Keep the returned
sr_idalongside 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 samecategory_idand 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
notewhen 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/showsrequests_todayagainstdaily_limit. The limit is shared by every section the key can reach. Poll with a sensible interval and page through lists withpage_sizeup 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/andteams/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_idof 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.
- Email: sales@deskos.net
- Phone: +91 742810 9069