docs / Reference

API reference

Authentication, every REST endpoint, the GraphQL map by topic, WebSocket subscriptions and the limits that apply.

Two APIs, two audiences. GraphQL at /graphql is what the UI uses and what integrations use for anything a person could do. REST under /api/v1/ is for machines: agents, push clients, backup jobs, licence activation and file uploads.

Authentication

People: JWT

mutation {
  login(username: "admin", password: "…") {
    accessToken     # 15 minutes
    refreshToken    # 7 days
    user { id username groups { name } }
  }
}

Send Authorization: Bearer <accessToken> on /graphql, and as the Authorization header or the token connection parameter on /graphql/ws. refreshToken exchanges the refresh token for a new pair; logout revokes the session. Tokens are HS256, signed with ITOPS_JWT_SECRET, issuer itops-v3. Licence tokens are a different thing (Ed25519, in ITOPS_LICENSE_KEY) and are never sent as a bearer.

Machines: the operator API key

X-API-Key: <ITOPS_SECURITY_OPERATOR_API_KEY>

Authorization: Bearer <key> is accepted as well. The key is one shared secret for every agent and script; rotate it by changing the value and rolling the agents. If the server's key is empty, the machine endpoints are unauthenticated and a warning is logged at start.

REST endpoints

Method Path Auth Purpose
GET /health none {"status":"healthy","version":"4.2.1"}
GET /ready none readiness, includes a database check
GET /api/v1/auth/providers none enabled sign-in providers, public fields only, for the login page
POST /graphql JWT GraphQL
GET /graphql/ws JWT WebSocket subscriptions
GET /api/v1/operator/services?nodeId=… API key services registered under a node, what an agent should look for
POST /api/v1/operator/register API key register or update a service; body up to 512 KiB. Schema
POST /api/v1/operator/status API key status sync from an agent, plus SLA group definitions; up to 2 MiB
POST /api/v1/operator/heartbeat API key agent liveness; up to 64 KiB. The response may carry commands[]
POST /api/v1/operator/command-result API key an agent reporting the outcome of a command
GET /api/v1/operator/nodes API key registered agents and their last heartbeat
POST /api/v1/health/report API key external health push. Push API
POST /api/v1/storage/report API key storage usage push. Push API
POST /api/v1/backup/report API key backup outcome push. Push API
POST /api/v1/sla/exclusion-window/start API key open a maintenance window: {serviceId, reason} → {windowId}
POST /api/v1/sla/exclusion-window/stop API key close it: {windowId}
POST /api/v1/sla/report/generate API key generate today's daily report now
GET /api/v1/templates/export API key SLA definitions as YAML
POST /api/v1/templates/import API key load SLA definitions from YAML; up to 1 MiB
POST /api/v1/license/activate API key or admin JWT activate a licence at runtime
POST /api/v1/tickets/{ticketId}/attachments JWT upload an attachment, multipart; 10 MiB per file
GET /api/v1/tickets/{ticketId}/attachments JWT list attachments
GET /api/v1/tickets/attachments/{id} JWT download

The /api/v1/dev/* test endpoints and the GraphQL playground at / exist only in development builds and are absent from release images.

Status sync body

What the Kubernetes agent sends, and what a seed job sends to declare SLA groups:

{
  "nodeId": "meridian/commerce/prod/eu-central-1",
  "operatorVersion": "0.3.0",
  "services": [
    { "name": "payment-gateway", "externalId": "meridian/commerce/prod/eu-central-1/payment-gateway",
      "namespace": "commerce", "status": "OPERATIONAL", "message": "3/3 replicas ready",
      "workloadType": "Deployment", "workloadName": "payment-gateway",
      "replicas": 3, "readyReplicas": 3, "availableReplicas": 3,
      "images": ["ghcr.io/meridian/payment-gateway:2.4.1"] }
  ],
  "slaGroups": [
    { "name": "checkout-flow", "displayName": "Online checkout", "tier": "critical", "targets": { "uptime": 99.95 } }
  ]
}

Only services that are already registered are updated; unknown names are counted in the response's skipped. Registration is a separate, deliberate act.

Errors

Errors are {"error": "message"} with a matching status: 400 for a body that does not parse or misses a required field, 401 for a bad key or token, 403 for a role that may not do this, 413 for a body over the limit, 503 when a plugin the endpoint needs is not active. Push endpoints answer 200 with warnings[] for input they adjusted rather than rejected.

GraphQL

One endpoint, POST /graphql, with introspection on in development and off in production; the schema is stable across a minor version. A query depth of 15 and a body of 256 KiB are the limits.

Core

Queries: me, mySessions · user, users, userByUsername, searchUsersAcrossProviders · group, groups, groupMembers · services, service · operationsNodes, operationsNode, operationsRootNodes, operationsNodeChildren · agents · dashboardStats · globalSearch, quickSearch · notifications, unreadNotificationCount, notificationSettings · savedFilters, savedFilter · fieldPermissions, fieldPermission, myFieldPermissions, fieldDefinitions, screenPermissions, screenPermission, myUIPermissions, checkEnvironmentAccess, checkEnvironmentResourceAccess, myEnvironmentResourcePermissions, groupEnvironmentAccess · authProviders, authProvider, authProviderTypes, groupMappings · licenseInfo · webhooks, webhook, webhookExecutions, webhookStats, webhookEventTypes.

Mutations: login, logout, refreshToken, revokeSession, revokeAllSessions, extendSession, changePassword, resetPassword, setPasswordWithToken, provisionProviderUser · createUser, updateUser, deleteUser, deactivateUser, createGroup, updateGroup, deleteGroup, addUserToGroup, removeUserFromGroup, createLDAPGroup, updateLDAPGroup, deleteLDAPGroup, syncLDAPGroups · createService, updateService, deleteService, deleteOperationsNode · addPermissionRule, updatePermissionRule, deletePermissionRule, updateFieldDefinition · createSavedFilter, updateSavedFilter, deleteSavedFilter, setDefaultFilter · markNotificationRead, markAllNotificationsRead, deleteNotification, clearAllNotifications, updateNotificationSettings · updateAuthProviderStatus, testAuthProvider, setGroupMapping, deleteGroupMapping · togglePlugin · createWebhook, updateWebhook, deleteWebhook, testWebhook.

A service in GraphQL carries what the agent reported (status, statusMessage, replicas, readyReplicas, images, workloadType, lastOperatorSync), what the registration declared (criticality, serviceType, tags, owner, team, metadata as a JSON string holding ownership, links, operations and the rest), and both directions of the dependency graph, resolved in one query:

query {
  service(id: "…") {
    name displayName status statusMessage criticality
    replicas readyReplicas images lastOperatorSync
    owner { username } team { name } metadata
    dependencies { targetService { name } dependencyType }
    dependents  { sourceService { name } dependencyType }
  }
}

SLA plugin

Queries: slaDefinitions, slaDefinition, slaGroups, slaGroup, slaIncidents, slaPeriodResults, slaAlerts, slaDashboardStats, slaTrendData, slaSnapshotTrend, slaExclusionWindows, slaEventLog; slaStats on the dashboard. Mutations: createSLADefinition, updateSLADefinition, deleteSLADefinition, createSLATarget, assignSLAToService, createSLAIncident, updateSLAIncident, acknowledgeSLAAlert, createExclusionWindow, stopExclusionWindow, deleteExclusionWindow.

query {
  slaGroups {
    name displayName tier status currentUptime
    targetOverrides { uptime }
    services { serviceName status backupStatus { backupOverdue lastBackupAt } }
  }
  slaPeriodResults(slaDefinitionId: "…") {
    actualValue coveragePercent downtimeMinutes excludedMinutes
    errorBudgetMinutes errorBudgetUsedMinutes errorBudgetRemainingMinutes burnRate
    incidentCount mttrMinutes mtbfMinutes
  }
}

Ticketing plugin

Queries: ticket, tickets, ticketByNumber, myTickets, ticketComments, ticketStatusHistory, ticketWorklog, ticketWatchers, ticketRelations, ticketAttachments, availableWorkflows, availableCatalogCategories, availableCatalogItems, availableGroups. Mutations: createTicket, updateTicket, transitionTicket, assignTicket, resolveTicket, closeTicket, reopenTicket, deleteTicket, restoreTicket, addTicketComment, editTicketComment, deleteTicketComment, logTicketWork, deleteTicketWorklog, watchTicket, unwatchTicket, linkTickets, unlinkTickets, pauseTicketSLA, resumeTicketSLA, deleteTicketAttachment.

mutation {
  createTicket(input: {
    title: "Checkout returns 502 for EU customers"
    type: INCIDENT, impact: HIGH, urgency: HIGH
    serviceId: "…", catalogItemName: "outage_report"
    requesterId: "…"                       # operators may raise on someone's behalf
  }) { number priority responseDueAt resolutionDueAt minutesUntilDue groupId }
}

WebSocket subscriptions

GET /graphql/ws speaks the graphql-ws protocol: connection_init with the token, connection_ack, then subscribe / next / complete, with ping / pong keepalives. Named subscriptions: notificationReceived, entityViewersChanged, editLockChanged, plus a generic subscription keyed on entityType and entityId that the plugins use.

Events on the wire:

Family Events
core notification:new, license:updated, user:viewing, user:editing
service service:status_changed
ticketing ticket:created, ticket:updated, ticket:deleted, ticket:assigned, ticket:sla_breached
sla sla:alert, sla:incident

user:viewing and user:editing are the presence primitives behind the "somebody else is editing this" indicator; editLockChanged carries per-field locks. There is a per-client cap on open subscriptions.

Limits and defaults

Rate limit 10 requests/s per IP, burst 20, forced on in production
GraphQL depth / body 15 / 256 KiB
Register body 512 KiB
Status sync body 2 MiB
Heartbeat and push bodies 64 KiB
Template import 1 MiB
Attachment 10 MiB per file, 12 MiB per request
Access / refresh token 15 min / 7 days
Webhook execution history 30 days
CORS origins from ITOPS_SECURITY_CORS_ALLOWED_ORIGINS, credentials allowed, preflight cached 300 s

Security headers

In production every response carries X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy: camera=(), microphone=(), geolocation=(), Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' and Strict-Transport-Security: max-age=63072000; includeSubDomains.

Prompt files

If you are wiring an AI assistant to this API, the prompt files are a condensed, machine-friendly version of this page and the registration schema.

Documentation for ITOps 4.2 · charts itops 2.0.0, sla-portal 1.4.0 · rendered 2026-09-11