Repository navigation
REST API Reference
Version 1.0 · ServiceOps 1.19+
The supported API is REST v1:
https://serviceops.example.com/api/v1
The live OpenAPI 3.1 document is available without authentication:
GET /api/v1/openapi.json
A human-readable rendering of the same contract is available at:
GET /api/v1/docs
The current contract supports ticket discovery, incident creation, controlled ticket updates, workflow triggering, CMDB auto-registration, and monitoring-event ingestion. Browser session cookies are not API credentials.
- Sign in as a ServiceOps administrator.
- Open Administration → API clients.
- Select the active user whose permissions the integration will exercise.
- Select only the required scopes.
- Create the client and copy the displayed token immediately.
Tokens begin with sop_ and are displayed once. ServiceOps stores only an
HMAC-derived token verifier. Store the token in a secret manager, never in
source control, shell history, URLs, screenshots, or ordinary log output.
Every request executes as the selected user. A scope does not bypass that user's tenant, role, team ownership, record visibility, lifecycle approvals, or field-projection rules. Disabling the user, revoking the client, or moving the user out of the required team takes effect on subsequent requests.
Available scopes:
| Scope | Purpose |
|---|---|
tickets:read |
List and retrieve visible incidents and changes |
incidents:create |
Create incidents |
tickets:update |
Update an authorized owning-team ticket |
workflows:execute |
Trigger configured API workflows for an authorized ticket |
cmdb:write |
Idempotent upsert of Configuration Items (§9) |
mcp:access |
Use the embedded MCP server (§13b) |
cmdb:read |
MCP tool: search configuration items (agent role required) |
knowledge:read |
MCP tool: search the knowledge base |
approvals:read |
MCP tool: list the acting user's pending approvals |
users:provision |
SCIM 2.0 user provisioning (§13c); the acting user must also be a security administrator |
projects:read |
Read projects and work items the acting user can see (§13d) |
projects:write |
Create and change work items and add comments (§13d) |
Native mobile applications authenticate an actual ServiceOps user rather than
embedding an API-client secret. POST /api/v1/auth/mobile/login accepts
username, password, provider (local or ldap) and optional mfa_code;
MFA-enabled accounts require a valid TOTP or backup code. Requests identify the
client with X-ServiceOps-App-Version, X-ServiceOps-App-Build,
X-ServiceOps-Platform and X-ServiceOps-Device. Success returns a 15-minute
som_ access token and rotating 30-day sor_ refresh token. Store both only in
the platform secure credential store. Refresh through POST /api/v1/auth/mobile/refresh and revoke through POST /api/v1/auth/mobile/logout. Password changes revoke previously issued mobile
access and refresh tokens. Deactivating the account or its organization also
blocks authentication. Existing mobile sessions retain their current credential
version during migration. Mobile activity is authorized as the signed-in
user and audited with authoritative user and app/device attribution.
The app discovers the sign-in methods a server offers with unauthenticated
GET /api/v1/auth/mobile/methods, which returns local, ldap, passkeys,
keycloak and cloudflare_access booleans. Two single sign-on routes issue the
same mobile session as the password route:
-
POST /api/v1/auth/mobile/cloudflare-accesswith the Cloudflare AccessCF_AuthorizationJWT inCf-Access-Jwt-Assertion(and optionalmfa_code). The token's signature, audience, issuer and expiry are verified; exactly one active account must have its email; lockout and MFA apply. - Keycloak: open
/auth/keycloak/mobile/start?code_challenge=…&state=…(PKCE S256) in the system browser; it returns toserviceops://keycloak?code=…&state=…(orerror=access_denied|mfa_assurance|link_refused|inactive|failed). Redeem the code within two minutes, once, withPOST /api/v1/auth/mobile/keycloak/exchangeand{"code": …, "code_verifier": …}.
Ticket attachment access uses the same bearer identity and tickets:read
scope. GET /api/v1/tickets/{number}/attachments returns id, fileName,
contentType, byteSize, createdAt, and an authenticated relative
downloadURL. GET /api/v1/tickets/{number}/attachments/{attachment_id}/download streams the
bytes with private/no-store caching. Both calls apply the acting user's normal
ticket visibility and tenant boundary; invisible tickets or attachments return
404. Mobile compatibility aliases exist under /api/v1/mobile/tickets/....
Passkey registration is available to an existing authenticated mobile session:
-
POST /api/v1/auth/passkeys/register/optionsissues a five-minute, tenant/user-bound WebAuthn creation challenge. -
POST /api/v1/auth/passkeys/register/completeverifies the Apple platform credential, consumes the challenge once, and stores only the credential ID, public key, signature counter, display name and transports. -
GET /api/v1/auth/passkeyslists the signed-in user's registered passkeys;DELETE /api/v1/auth/passkeys/{credential_id}revokes one owned credential and records the action in the audit trail.
Passwordless mobile sign-in uses:
-
POST /api/v1/auth/passkeys/authenticate/optionsto issue a discoverable credential challenge. -
POST /api/v1/auth/passkeys/authenticate/completeto verify user presence, user verification, origin, relying-party ID, signature and counter, then issue the standard mobile access/refresh token pair.
Passkeys fail closed unless WEBAUTHN_RP_ID and an HTTPS
WEBAUTHN_ORIGIN are configured. Apple clients also require an Associated
Domains webcredentials: entitlement matching the relying-party domain and
that domain must serve /.well-known/apple-app-site-association containing
the configured APPLE_PASSKEY_APP_ID. Plain HTTP LAN development addresses
cannot complete an Apple passkey ceremony.
Authorization: Bearer sop_REDACTED
Accept: application/json
Content-Type: application/json
X-Request-ID: 9acdd549-4938-4eb2-bf8c-175ba8de2adcX-Request-ID is optional but recommended. Supply a UUID. ServiceOps returns
the accepted or generated identifier in every response and records it in the
audit chain.
State-changing endpoints also require:
Idempotency-Key: integration-name-operation-unique-valueThe key must contain 1–128 letters, digits, ., _, :, or -. Repeating
the same key, method, path, and body returns the stored response with:
Idempotency-Replayed: trueReusing the key for a different request returns 409 Conflict.
GET /api/v1/tickets?type=incident&state=In%20Progress&limit=50&cursor=0
Authorization: Bearer sop_REDACTEDRequired scope: tickets:read
Query parameters:
| Parameter | Meaning |
|---|---|
type |
Optional: incident or change
|
state |
Optional exact state |
limit |
1–100; default 50 |
cursor |
Last returned numeric record ID; start at 0 |
{
"data": [
{
"id": 41,
"number": "INC0000041",
"type": "incident",
"title": "Production API unavailable",
"description": "Health checks are failing.",
"state": "In Progress",
"priority": "P1",
"category": "Software / Application",
"subcategory": "Business application",
"closure_category": null,
"closure_subcategory": null,
"resolution_notes": null,
"opened_at": "2026-07-26T12:00:00+00:00",
"updated_at": "2026-07-26T12:05:00+00:00",
"resolved_at": null,
"internal": {
"assignment_group": {"id": 1, "name": "CoreApps"},
"assigned_to": {"id": 18, "name": "Application Engineer"}
}
}
],
"meta": {
"limit": 50,
"next_cursor": null,
"request_id": "9acdd549-4938-4eb2-bf8c-175ba8de2adc"
}
}The internal object is returned only to agent, manager, and administrator
audiences. Requester API clients never receive it.
Continue pagination using next_cursor until it is null. Do not calculate a
cursor from result counts.
GET /api/v1/tickets/INC0000041
Authorization: Bearer sop_REDACTEDRequired scope: tickets:read
Ticket numbers are case-insensitive. A record outside the caller's tenant or
visibility policy returns 404, preventing record-existence disclosure.
GET /api/v1/tickets/INC0000041/details
Authorization: Bearer sop_REDACTEDRequired scope: tickets:read. Returns the versioned TicketDetailsEnvelope
(the same details and configuration_items that GET /api/v1/tickets/{number}
includes): record fields, CIs and impacted services, governance, SLAs, approvals,
tasks and related records, filtered by the caller's role, team and CI-class
policy. See Native ticket details.
GET /api/v1/tickets/INC0000041/comments
POST /api/v1/tickets/INC0000041/comments
Content-Type: application/json
{"body": "Rebooted the node; monitoring is green.", "parent_id": null}Reading requires tickets:read. Each comment returns id, body, author,
parent_id, deleted_attachments, source_platform, ai_assisted and
created_at. Posting requires tickets:update and an acting user who may
manage the ticket; body is 1–10,000 characters and parent_id replies to an
existing comment. Idempotency-Key is optional on this endpoint. The response
is 201 with the new comment.
GET /api/v1/tickets/CHG0000041/ctasks
Authorization: Bearer sop_REDACTEDRequired scope: tickets:read
Returns the change tasks (CTASKs) belonging to a change ticket, ordered by
sequence:
{
"data": [
{
"number": "CTASK0000001",
"title": "Snapshot database",
"taskType": "Implementation",
"state": "Open",
"required": true,
"sequence": 1,
"assignmentGroup": "Unix",
"assignee": null,
"plannedStart": null,
"plannedEnd": null,
"workNotes": ""
}
],
"meta": { "count": 1, "request_id": "..." }
}state follows the change-task lifecycle: Open, Work in Progress,
Pending, Closed Complete, Closed Incomplete, Cancelled. Calling this
against a non-change ticket returns 400. A ticket outside the caller's
tenant or visibility policy, or an unknown ticket number, returns 404.
PATCH /api/v1/tickets/CHG0000041/ctasks/CTASK0000001
Authorization: Bearer sop_REDACTED
Content-Type: application/json
Idempotency-Key: <caller-generated>
{
"state": "Closed Complete",
"append_work_notes": "Completed in FlowOps runbook 'DB cutover' by J. Tan"
}work_notes replaces the task's notes. append_work_notes adds one
timestamped, client-attributed line ([2026-10-02 09:14 UTC · FlowOps] …)
below the existing notes without overwriting what the owning team wrote;
when the 2000-character limit is reached the oldest text is trimmed. Use
append_work_notes for execution evidence from an integration.
Required scope: tickets:update. The caller must also be able to manage the
parent change ticket (owning group membership/management, or admin) — the
same authorization rule PATCH /api/v1/tickets/{number} already enforces.
All fields are optional but at least one field is required; state must be
a valid transition from the task's current state under the same change-task
lifecycle listed above (an invalid transition returns 409, matching the UI's
own task-update behavior — including the change-task gating rules, e.g. a
Review task cannot close while required Implementation/Testing tasks remain
open). Returns the updated CTASK using the same shape as 5a's list endpoint.
An idempotency key is required, as with every mutating endpoint in this
reference. A non-change ticket, unknown ticket, or unknown CTASK number
returns 400/404 as appropriate.
POST /api/v1/incidents
Authorization: Bearer sop_REDACTED
Content-Type: application/json
Idempotency-Key: monitoring-inc-20260726-00041
{
"title": "Production API unavailable",
"description": "All regional health checks are failing.",
"category": "Application",
"priority": "P1",
"assignment_group_id": 1
}Required scope: incidents:create
Required fields: title, description, assignment_group_id.
assignment_group_id must identify an active IT fulfilment team in the API
client's tenant. priority accepts P1, P2, P3, or P4 and defaults to
P3. Unknown JSON fields are rejected.
Successful creation returns 201 Created and the governed ticket document.
The incident receives SLA records, assignment ownership, task history, and an
append-only audit event in the same transaction.
PATCH /api/v1/tickets/INC0000041
Authorization: Bearer sop_REDACTED
Content-Type: application/json
Idempotency-Key: resolver-inc41-progress-1
{
"state": "In Progress",
"priority": "P1",
"assigned_to_id": 18
}Required scope: tickets:update
Allowed fields are state, priority, assigned_to_id, resolution_notes,
closure_category, and closure_subcategory. Use null to clear the
assignee. A non-null assignee must be active and belong to the owning team.
Incidents record their categorisation twice: category/subcategory as
logged, and closure_category/closure_subcategory at closure, so reporting
reflects the actual cause. Resolution fields are applied before a state change
in the same request, so one call can document and resolve:
{
"state": "Resolved",
"resolution_notes": "Renewed the expired VPN certificate.",
"closure_category": "Security",
"closure_subcategory": "Policy breach"
}closure_category applies to incidents only and is matched case-insensitively
against the tenant's active categories. When an incident is resolved without
one, the logging categorisation is recorded as the closure categorisation.
resolution_notes is required when resolving from the web UI but optional
here, so existing integrations keep working; resolved_at records the latest
entry into Resolved and is cleared on reopen.
The acting user must have update, assignment, and transition permissions and must be an active member or manager of the owning team, unless the user is an administrator. Approval and lifecycle guards are applied server-side. The API cannot move a change into implementation merely because the token has update scope.
POST /api/v1/tickets/INC0000041/workflow-events
Authorization: Bearer sop_REDACTED
Content-Type: application/json
Idempotency-Key: automation-inc41-workflow-1
{}Required scope: workflows:execute
The acting user must be authorized to manage and transition the ticket. A
successful request returns 202 Accepted:
{
"data": {
"event_id": "9320a248-f7b7-47ae-a398-97282c668413",
"state": "Pending",
"ticket": "INC0000041"
}
}Execution is durable and asynchronous. Administrators can inspect workflow jobs, retries, evidence, and dead-letter state in the workflow administration area.
PUT /api/v1/cmdb/configuration-items
Authorization: Bearer sop_REDACTED
Content-Type: application/json
{
"name": "web01.example.com",
"ci_class": "Server",
"environment": "Production",
"operational_status": "Operational",
"ip_address": "10.0.0.5"
}Required scope: cmdb:write
Unlike the ticket-creation endpoints, this call is idempotent by design and
does not require an Idempotency-Key — it is meant to be called on every
agent run (cron, Puppet, systemd timer). The Configuration Item is matched by
name within the acting API client's tenant: the first call creates it
(201), every later call updates the same row (200). Only name,
ci_class, environment, operational_status, and ip_address are
accepted; unknown fields are rejected with 400. Input values must be strings;
name, ci_class and ip_address are limited to 160, 80 and 60 characters
respectively. Overlong names are rejected rather than truncated, preserving
the exact tenant-scoped matching key.
A ready-to-use POSIX shell agent lives at tools/cmdb_sync_agent.sh (curl and
standard Linux tools only — no facter, no Puppet module dependency), with an
example Puppet class at deploy/puppet/cmdb_sync.pp.example showing how to
drop it onto managed nodes and run it hourly via cron.
Monitoring sources use separate one-time credentials, not API-client tokens. Create a source under Administration → Integrations, select its assignment team, then copy the source ID and token.
POST /api/v1/monitoring/7faacfb5-3f78-4cb4-a661-f3eeb20c9864/events
Authorization: Bearer REDACTED_MONITORING_TOKEN
Content-Type: application/json
{
"external_id": "alert-prod-api-9001",
"severity": "critical",
"resource": "api-prod-01",
"summary": "Production API health check failed",
"observed_at": "2026-07-26T12:00:00Z",
"source_url": "https://monitoring.example.com/alerts/9001"
}Required fields are external_id, severity, resource, and summary.
Severity accepts critical, high, medium, low, or info. Additional
monitoring fields are preserved in the event payload.
The pair of source and external_id is deduplicated. First ingestion returns
201; a replay returns 200 with "deduplicated": true. ServiceOps creates
an EVT record and an investigation task routed to the source's configured team.
A daily backup succeeding is routine, not an incident, so this is a narrower sibling of monitoring ingestion: it never creates a record, it only updates the recovery-set status shown on Administration → System health. Point an external backup job (a Kubernetes CronJob, cron, a managed snapshot pipeline) at it after each successful run, using the same monitoring source credential created above.
POST /api/v1/monitoring/7faacfb5-3f78-4cb4-a661-f3eeb20c9864/backup-report
Authorization: Bearer REDACTED_MONITORING_TOKEN
Content-Type: application/json
{
"manifest": "/backups/serviceops-20260926T183000Z.dump",
"offsite": "not-configured"
}Required fields are manifest (a path or identifier for the backup that was
taken, up to 500 characters) and offsite (archived if it was also copied
to off-site/object storage, otherwise not-configured). Report backup
failure through the regular monitoring-events endpoint above instead, so it
becomes a tracked, routed incident rather than a status flag.
{
"error": {
"status": 403,
"title": "Forbidden",
"detail": "The API client lacks scope tickets:update.",
"request_id": "9acdd549-4938-4eb2-bf8c-175ba8de2adc"
}
}| Status | Meaning |
|---|---|
400 |
Invalid JSON, fields, parameters, transition, or idempotency key |
401 |
Missing, invalid, or revoked token |
403 |
Scope, role, team, tenant, or action is not authorized |
404 |
Record does not exist or is intentionally hidden |
409 |
Idempotency conflict, integrity failure, or state conflict |
429 |
Reserved for enforced rate limits in a future compatible revision |
500 |
Unexpected server failure; retain the request ID for investigation |
Clients should retry only transient 5xx failures, using exponential backoff
with jitter and the same idempotency key. Do not automatically retry 400,
401, 403, 404, or 409.
export SERVICEOPS_URL="https://serviceops.example.com"
read -rsp "ServiceOps API token: " SERVICEOPS_TOKEN
export SERVICEOPS_TOKEN
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer ${SERVICEOPS_TOKEN}" \
-H "Accept: application/json" \
"${SERVICEOPS_URL}/api/v1/tickets?limit=25"Avoid putting the token directly on the command line. Clear it when finished:
unset SERVICEOPS_TOKENimport os
import uuid
import requests
base_url = os.environ["SERVICEOPS_URL"].rstrip("/")
token = os.environ["SERVICEOPS_TOKEN"]
response = requests.post(
f"{base_url}/api/v1/incidents",
headers={
"Authorization": f"Bearer {token}",
"Idempotency-Key": str(uuid.uuid4()),
"X-Request-ID": str(uuid.uuid4()),
},
json={
"title": "Database connection failures",
"description": "Connection failures exceed the alert threshold.",
"category": "Database",
"priority": "P2",
"assignment_group_id": 2,
},
timeout=15,
)
response.raise_for_status()
print(response.json()["data"]["number"])Always set connect/read timeouts, validate TLS, keep tokens in a secret manager, and log request IDs rather than credentials or full sensitive bodies.
ServiceOps embeds a Model Context Protocol
server at POST /api/v1/mcp, so AI clients such as Claude Code, Claude
Desktop or agent frameworks can read ServiceOps data. It ships in the same
image and release as the application; there is nothing separate to install.
- Transport: Streamable HTTP with JSON responses, one JSON-RPC 2.0 message
per request. Stateless: no
Mcp-Session-Id, andGET/DELETEreturn 405. - Protocol versions:
2025-11-25(latest),2025-06-18,2025-03-26. - Authentication: the same
Authorization: Bearer sop_...API client token as the rest of the API. The client needsmcp:access, plus the scope of each tool it should see;tools/listreturns only those tools. - Cross-origin browser requests (a foreign
Originheader) are refused.
All tools are read-only and return only what the API client's acting user may see in their own tenant, including the CMDB class read policy and knowledge draft visibility.
| Tool | Scope | Returns |
|---|---|---|
search_tickets |
tickets:read |
Visible incidents and changes matching text, type, state |
get_ticket |
tickets:read |
One ticket with logging and closure categorisation, resolution notes, recent comments |
search_configuration_items |
cmdb:read |
Configuration items by name, IP, serial number, description |
search_knowledge |
knowledge:read |
Knowledge articles (body up to 4,000 characters) |
list_my_approvals |
approvals:read |
Approval votes waiting on the acting user |
Invalid tool arguments come back as a tool result with isError: true so the
model can correct itself; unknown tools and methods are JSON-RPC errors.
Connect Claude Code:
claude mcp add --transport http serviceops https://serviceops.example.com/api/v1/mcp \
--header "Authorization: Bearer sop_REDACTED"Create a dedicated API client for AI use with only the read scopes it needs, acting as a user whose visibility matches what the AI should see. Record text is written by people and integrations; the server tells clients to treat it as data, not instructions.
Administration → Integrations webhook connections (kind: "webhook") can
subscribe to change.state_changed, delivered whenever a change ticket's
state transitions (create-time state is not an event). The connection's
event-pattern list (fnmatch-style, e.g. "change.state_changed", "change.*",
or "*") controls whether it receives this event; an empty pattern list
receives everything.
{
"id": "8f9c9e2e-...-outbox-event-id",
"type": "change.state_changed",
"created_at": "2026-09-12T03:14:00+00:00",
"data": {
"number": "CHG0000961",
"kind": "change",
"state": "In Progress",
"previous_state": "Approved",
"priority": "P3",
"impact": "Medium",
"urgency": "Medium",
"category": "Software"
}
}Delivery is signed exactly like every other webhook event and goes through the same SSRF-hardened delivery worker (DNS pinning, private-address rejection, bounded redirects) as every other outbound event.
Each signed delivery carries:
| Header | Value |
|---|---|
X-ServiceOps-Event-ID |
The outbox event UUID; use it to discard duplicates |
X-ServiceOps-Timestamp |
Unix seconds when the delivery was signed |
X-ServiceOps-Signature |
sha256= + hex HMAC-SHA256 of <timestamp>.<raw body>
|
The body is transmitted exactly as signed (compact JSON with sorted keys), so compute the HMAC over the raw request bytes, compare in constant time, and reject timestamps more than five minutes old. Deliveries are retried, so the same event ID can arrive more than once.
Delivered whenever a change task (CTASK) changes state, from the web UI or
from PATCH /api/v1/tickets/{number}/ctasks/{ctask}. Notes-only updates are
not events. Subscribe with "change_task.state_changed" or "change_task.*".
{
"id": "1c0d…",
"type": "change_task.state_changed",
"created_at": "2026-10-02T03:14:00+00:00",
"data": {
"number": "CTASK0000041",
"ticket": "CHG0000961",
"title": "Snapshot database",
"task_type": "Implementation",
"state": "Closed Complete",
"previous_state": "Work in Progress",
"required": true,
"sequence": 1,
"assignment_group": "Unix",
"assignee": "J. Tan"
}
}FlowOps consumes both events at
POST /api/integrations/serviceops/events/{instance}, so a runbook follows
change approval and CTASK progress made directly in ServiceOps.
GET /scim/v2/Users?filter=userName eq "jsmith"
GET /scim/v2/Users?startIndex=201&count=200
POST /scim/v2/Users
GET /scim/v2/Users/{id}
PUT /scim/v2/Users/{id}
PATCH /scim/v2/Users/{id}
DELETE /scim/v2/Users/{id}The API client needs the users:provision scope and must act as a user with
security-administration authority. Every operation is limited to the client's
tenant.
-
Create requires
userNameand at least oneemailsvalue;displayName,activeandexternalIdare optional. New users get therequesterrole and an unusable random password, so they sign in through LDAP or SSO. An existing username or email returns409(uniqueness). -
Update accepts
active,displayNameandemails.PATCHsupports theaddandreplaceoperations only. - Delete deactivates the user and ends their sessions; history is kept.
-
List supports only the filter
userName eq "value"and pages as in RFC 7644 §3.4.2.4:startIndex(1-based, default 1) andcount(default and maximum 200).totalResultsis the full number of matches; request the next page withstartIndexincreased byitemsPerPage. A non-integer value returns400.
Requires project management to be enabled. The API client acts as its acting user, so private projects and project roles apply.
| Method and path | Scope | Purpose |
|---|---|---|
GET /api/v1/projects |
projects:read |
Projects the acting user can see, with statuses |
GET /api/v1/projects/{key} |
projects:read |
One project |
GET /api/v1/projects/{key}/work-items |
projects:read |
Work items; filters q, type, status (id), category (todo, in_progress, done, open), priority, assignee (user id, me, none), label, epic (id); limit 1–200 and cursor (last work item number) |
POST /api/v1/projects/{key}/work-items |
projects:write |
Create; needs Idempotency-Key. Fields: title (required), description, type, status_id, priority, assignee_ids, labels (list), parent_key, start_date, due_date, story_points, estimate_minutes
|
GET /api/v1/work-items/{key} |
projects:read |
One work item with comments |
PATCH /api/v1/work-items/{key} |
projects:write |
Change the fields sent |
POST /api/v1/work-items/{key}/comments |
projects:write |
{"body": "..."}; @username mentions notify |
GET /api/v1/work-items/{key}/worklogs |
projects:read |
Work logs |
POST /api/v1/work-items/{key}/worklogs |
projects:write |
Log work: time_spent ("2h 30m"), optional started_at, comment, remaining_mode (auto, leave, new, reduce) with new_remaining or reduce_by
|
POST /api/v1/work-items/{key}/votes |
projects:write |
Vote or remove your vote |
GET /api/v1/projects/{key}/components |
projects:read |
Components |
GET /api/v1/projects/{key}/versions |
projects:read |
Versions (releases) |
Work items also carry and accept remaining_minutes, component_ids, fix_version_ids, affects_version_ids, environment, resolution_id and flagged. Responses include the component and version names, the resolution and spent_minutes.
curl -s -X POST "$BASE/api/v1/projects/WEB/work-items" \
-H "Authorization: Bearer $TOKEN" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"title": "Fix login timeout", "type": "bug", "priority": "high", "labels": ["auth"]}'REST v1 is the supported integration surface. GraphQL is intentionally
deferred. CMDB registration (PUT /api/v1/cmdb/configuration-items, cmdb:write
scope) is the first CMDB surface exposed as a public REST resource; it is
deliberately narrow (upsert-by-name only, five fields). The current API does
not yet expose catalog ordering, REQ/RITM/SCTASK, PRB/PTASK, CHG/CTASK
creation, CI relationship management, attachment upload, or reporting as
public integration resources. User lifecycle is available only through SCIM
(section 13c). Authenticated mobile sessions additionally use
an app-specific surface under /api/v1/mobile for bootstrap/profile,
push-device registration, notification inbox, approvals, knowledge search and
read-only CMDB; ticket comments and attachment list/download remain under the
ticket resource. These mobile
routes reject non-mobile API clients and are not shared API-key automation
contracts. Do not automate browser forms
as a substitute. Those resources will require explicit versioned contracts,
scopes, projections, idempotency, and compatibility tests.
These routes accept only mobile sessions (from /api/v1/auth/mobile/login or a
passkey sign-in) and return 403 to API-key clients.
| Method and path | Purpose |
|---|---|
GET /api/v1/mobile/bootstrap |
Signed-in user, assignment groups, pending-approval and unread counts, capabilities |
GET /api/v1/mobile/profile/avatar |
The user's own picture (404 when none) |
POST /api/v1/mobile/push-devices |
Register for APNs push: token, device_id, environment (sandbox or production) |
DELETE /api/v1/mobile/push-devices/{device_id} |
Stop pushes to a device |
GET /api/v1/mobile/notifications |
Notification inbox (paged) |
POST /api/v1/mobile/notifications/{id}/read, POST /api/v1/mobile/notifications/read-all
|
Mark read |
GET /api/v1/mobile/approvals |
Direct and delegated approval requests (paged) |
POST /api/v1/mobile/approvals/{vote_id}/decide |
decision (Approved or Rejected) and optional comments; Idempotency-Key optional |
GET /api/v1/mobile/knowledge?q= |
Knowledge search (paged) |
GET /api/v1/mobile/cmdb?q= |
CI search, agent role and CI-class policy apply (paged) |
GET /api/v1/mobile/racks/{rack_id} |
Rack and readable mounted equipment, agent role (up to 1,000 devices) |
GET /api/v1/mobile/tickets/{number}/details, /rack-placements, /attachments, /attachments/{id}/download
|
Mobile aliases of the ticket resources |
GET, DELETE /api/v1/auth/passkeys, POST /api/v1/auth/passkeys/register/options, /register/complete
|
Manage the user's passkeys |
These routes serve the web pages and use the signed-in browser session and its CSRF token; they are not API-client contracts.
| Method and path | Purpose |
|---|---|
GET /notifications/poll |
Unread count and latest notifications; sends an ETag and answers 304 to a matching If-None-Match
|
GET /events/auth?watch=ticket:N,… |
Used by the events service: the signed-in user, tenant and the watched records the user may see |
GET /events/stream?watch=ticket:N¬ifications=1 |
Server-sent events: hello, notification, ticket, resync, signed-out (events service) |
GET /push/config |
Whether Web Push is enabled and the VAPID public key |
POST /push/subscriptions, DELETE /push/subscriptions
|
Save or remove this browser's push subscription (endpoint, keys.p256dh, keys.auth) |
GET /changes/rt-preview?rt_id=N |
Agents: RT ticket summary for a new change (when RT_CHANGE_LINK_ENABLED) |
POST /ticket/{id}/rt-link |
Change managers: link an existing change to an RT ticket |
The OpenAPI document at /api/v1/openapi.json lists every /api/v1 route; a
test fails when a new route is missing from it.
GET /api/v1/mobile/approvals, /mobile/knowledge, /mobile/cmdb and
/mobile/notifications retain the existing data array and add meta:
{"limit": 100, "returned": 100, "has_more": true, "next_cursor": "opaque-signed-cursor"}Pass limit (1–100, default 100) and then cursor=meta.next_cursor to fetch
subsequent pages. Keep the same endpoint, signed-in user and q search.
Invalid or reused cursors return 400. Every page reapplies tenant and record
permissions. Knowledge sorts by newest creation time and ID, assets by name
and ID, and notifications by newest creation time and ID. Requested direct
and delegated approvals precede completed/future-stage votes; each group
sorts by descending ID. Refresh from the first page after decisions or edits
that change ordering. Older clients can continue decoding the data array;
clients must follow the cursor to display more than the first 100 rows.
Implementation and acceptance status: Review remediation.