Skip to content

REST API Reference

Anushka Wijesundara edited this page Oct 14, 2026 · 8 revisions

ServiceOps REST API reference

Version 1.0 · ServiceOps 1.19+

1. Scope and base URL

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.

2. Create an API client

  1. Sign in as a ServiceOps administrator.
  2. Open Administration → API clients.
  3. Select the active user whose permissions the integration will exercise.
  4. Select only the required scopes.
  5. 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)

3. Authentication and common headers

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-access with the Cloudflare Access CF_Authorization JWT in Cf-Access-Jwt-Assertion (and optional mfa_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 to serviceops://keycloak?code=…&state=… (or error=access_denied|mfa_assurance|link_refused|inactive|failed). Redeem the code within two minutes, once, with POST /api/v1/auth/mobile/keycloak/exchange and {"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/options issues a five-minute, tenant/user-bound WebAuthn creation challenge.
  • POST /api/v1/auth/passkeys/register/complete verifies 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/passkeys lists 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/options to issue a discoverable credential challenge.
  • POST /api/v1/auth/passkeys/authenticate/complete to 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-175ba8de2adc

X-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-value

The key must contain 1–128 letters, digits, ., _, :, or -. Repeating the same key, method, path, and body returns the stored response with:

Idempotency-Replayed: true

Reusing the key for a different request returns 409 Conflict.

4. List tickets

GET /api/v1/tickets?type=incident&state=In%20Progress&limit=50&cursor=0
Authorization: Bearer sop_REDACTED

Required 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.

5. Retrieve one ticket

GET /api/v1/tickets/INC0000041
Authorization: Bearer sop_REDACTED

Required scope: tickets:read

Ticket numbers are case-insensitive. A record outside the caller's tenant or visibility policy returns 404, preventing record-existence disclosure.

Ticket detail sections

GET /api/v1/tickets/INC0000041/details
Authorization: Bearer sop_REDACTED

Required 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.

Ticket comments

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.

5a. List a change's tasks (CTASKs)

GET /api/v1/tickets/CHG0000041/ctasks
Authorization: Bearer sop_REDACTED

Required 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.

5b. Update a single change task (CTASK)

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.

6. Create an incident

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.

7. Update a ticket

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.

8. Trigger an API workflow

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.

9. CMDB auto-registration

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.

10. Monitoring ingestion

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.

10a. Backup status reporting

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.

11. Error contract

{
  "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.

12. cURL quick start

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_TOKEN

13. Python example

import 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.

13b. MCP server for AI clients

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, and GET/DELETE return 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 needs mcp:access, plus the scope of each tool it should see; tools/list returns only those tools.
  • Cross-origin browser requests (a foreign Origin header) 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.

13a. Outbound webhook: change ticket state transitions

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.

Verifying a signed delivery

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.

change_task.state_changed

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.

13c. SCIM 2.0 user provisioning

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 userName and at least one emails value; displayName, active and externalId are optional. New users get the requester role and an unusable random password, so they sign in through LDAP or SSO. An existing username or email returns 409 (uniqueness).
  • Update accepts active, displayName and emails. PATCH supports the add and replace operations 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) and count (default and maximum 200). totalResults is the full number of matches; request the next page with startIndex increased by itemsPerPage. A non-integer value returns 400.

13d. Projects and work items

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"]}'

14. Current compatibility boundary

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.

Mobile app endpoints

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

Browser session endpoints

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&notifications=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.

Mobile list pagination

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.

Clone this wiki locally