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.