Skip to content

API ​

OpsMerge has a JSON HTTP API. Everything the web UI can do, the API can do — they share the same backend. This article covers authentication, scopes, rate limits, our stability policy, and the request/response conventions. For endpoint specifics, use the interactive reference:

The interactive docs are generated from the OpenAPI spec and are the source of truth.

Authentication ​

Every authenticated endpoint takes an API key as a bearer token:

Authorization: Bearer om_your_api_key_here

API keys start with the om_ prefix and are long-lived — they work until they expire or you revoke them.

Creating a key ​

  1. Go to Settings → API Keys.
  2. Give the key a name (you'll see this name in the list later, so make it describe the integration).
  3. Optionally set an expiry date. No expiry means the key lives until revoked.
  4. Choose its access: Full access (everything your role allows) or Custom scopes (pick exactly the areas and levels the integration needs).
  5. Click Create key.

The key is shown once. Copy it immediately; it cannot be retrieved later. If you lose it, revoke it and create a new one.

Treat keys like passwords

A key is plain bearer auth — whoever holds it can do everything its scopes permit. Don't paste keys into chat tools, don't commit them to repositories. Use a secrets manager or your CI's secret store.

Quick way to test a key:

bash
curl -H "Authorization: Bearer om_your_api_key_here" \
  https://app.opsmerge.cloud/api/v1/auth/me

Scopes ​

Scopes follow a family:level format — the family is a feature area, the level is what the key may do within it:

LevelAllows
readList and fetch
writeCreate, update, delete
executeTrigger actions (run a script, reboot an agent)

Examples: agents:read, scripts:execute, psa_tickets:write. The full family list is shown in the scope picker when you create a key, with families grouped into RMM and PSA.

Two rules to remember:

  • Scopes narrow, never widen. A key can never do something its owner's role can't. Role permissions are checked first; the key's scopes are an additional restriction on top.
  • Least privilege pays off. Give an integration only the families and levels it actually uses. A reporting script needs read scopes, not full access.

If the key's owner is deactivated, the key stops working with them.

MCP connector tokens ​

When an MCP client (Claude, PandaOS, or any client that follows the MCP authorization flow) connects through Connect to Claude on the API Keys page, it registers itself, sends you to the consent screen, and exchanges the resulting code for a token pair:

  • The access token is a scoped API key that lasts 30 days. It appears on the API Keys page as "MCP connector (client id)" and can be revoked there like any other key.
  • The refresh token lasts 90 days and is single use: the client swaps it for a new pair without showing you the consent screen again, and the old access token is revoked at that moment. A connector in regular use therefore never asks you to re-consent; one left idle for more than 90 days will.

The token response carries expires_in and refresh_token as most clients require. Keys created by hand on the API Keys page are unaffected and still have whatever expiry you gave them.

What an MCP client sees and what is recorded ​

  • Only the tools the key can use are listed. The tool list is filtered by the key's scopes, so a key granted alerts:read alone advertises list_alerts and whoami and nothing else. Ticket and invoice tools are hidden entirely when the tenant's plan does not include PSA. A tool that would only ever be refused is not offered to the model.
  • Read tools are marked read-only. Every current tool carries the MCP readOnlyHint annotation, so clients such as Claude Desktop and VS Code can allow them without prompting on every call.
  • Every tool call is recorded. Each call, reads included, writes one row to the MCP call ledger with the user, the API key, the client (for example PandaOS/2.1 or mcp-remote/0.1), the tool, a redacted copy of the arguments (strings clipped to 160 characters), the outcome and the duration. Changes made through a tool also carry the key and the tool name in the audit log entry. A connector is therefore always attributable to a specific key, and revoking that key on the API Keys page ends its access at once.
  • One call, one rate-limit token. A tool call spends a single token from the key's and the IP's buckets, however many internal API requests it makes to build its answer. Write tools have a further budget of 60 per minute per key.

Letting a connector make changes ​

By default a connector can only read. The consent screen lists the changes a client asks to make (tickets, alerts, contacts, clients, sites, device actions) as unticked boxes; tick only what you want that connector to be able to do. A connector that later needs more gets a 403 with a WWW-Authenticate: Bearer error="insufficient_scope" challenge naming the missing scope, and a client that understands it will send you back through consent.

What a connector with write access can do, and how each action is guarded:

ActionToolsGuard
Create tickets, add internal notes, update subject, assignee, priority, categorycreate_ticket, add_ticket_note, update_ticketRuns directly. Create tools accept an idempotency_key so a retried call cannot open a ticket twice.
Reply on a ticketadd_ticket_commentInternal by default. A public reply, which emails the requester, only goes out when Settings → Tickets → API and MCP replies is switched off; while it is on (the default) the reply is saved as an internal note and the tool says so. This applies to every API key, REST or MCP. OpsMerge never sends a reply on its own either way.
Move a ticket to open, pending, waiting for customer or resolvedtransition_ticketRuns directly.
Close or cancel a ticket, resolve an alert, archive a clienttransition_ticket, resolve_alert, update_clientProposed, not executed. The tool answers with a plain-English summary and a token; the change only happens when confirm_action is called with that token, by the same key, within ten minutes. cancel_action discards it.
Acknowledge or snooze an alertacknowledge_alert, snooze_alertRuns directly. Acknowledging an already-acknowledged alert reports its current state.
Create or update contacts, clients and sitescreate_contact, update_contact, create_client, update_client, create_site, update_siteRuns directly. Portal passwords and portal links are never exposed.
Maintenance mode on a deviceset_maintenanceRuns directly. Reversible.
Run a diagnostic on a devicelist_catalogue_commands, run_catalogue_commandThe catalogue is a fixed list of commands built by the server (the same one the AI assistant uses); a connector can only pick an entry and, where allowed, one validated parameter. Always proposed first and run only on confirm_action. The output is returned marked as device output, not instructions.
Reboot a device now or laterreboot_agent, schedule_rebootAlways proposed first and run only on confirm_action.

Nothing is ever deleted through MCP, no tool runs a free-form script, and bulk device actions are not offered. Every change carries the API key and the client name in the audit log, so you can always tell what a connector did and revoke its key.

Reaching the rest of the API ​

The curated tools cover what an assistant does daily. For everything else there is a generated tier behind one extra consent scope, api:dynamic ("Call other API operations"), which is also unticked by default:

  • search_api finds operations by keyword and only returns ones the key's other scopes allow.
  • describe_api gives an operation's parameters and body schema.
  • call_api runs it through the same checks as any REST call. A GET needs the family's read scope and a POST, PUT or PATCH its write scope. Bulk operations, terminal ticket transitions, anything that sends, issues, voids, archives or deactivates, and any resolve or cancel step are proposed first and only run through confirm_action, exactly like the curated tools.

Off limits through call_api whatever the scopes: DELETE, device execution (only the curated device tools, always confirmed), SMS, the internal UI-plumbing operations, and the settings, roles, users, credentials, secure share, API key and admin families.

Rate limits ​

Each API key gets 300 requests per minute. Exceed it and you get 429 Too Many Requests with a Retry-After header (in seconds) and X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset headers describing the key's bucket. Honour Retry-After and back off.

The limit is per key, not per source IP — sharing one key across many hosts shares its budget. Give separate integrations separate keys: better isolation, better audit trail, and one noisy integration can't starve the others.

Stability tiers ​

Every operation in the API reference carries a stability tier (the x-stability field in the spec):

TierWhat it coversOur promise
stableThe public API proper: clients, sites, agents, contacts, tickets, billing, checks, alerts, scripts, users and roles, time entries, and similarBreaking changes only with at least 6 months' notice, signalled via Deprecation and Sunset headers and the changelog
betaRecently shipped and still movingMay change; changes are announced in the API changelog, but there is no sunset window
internalExists to serve the web UI: settings panels, integration OAuth flows, installer plumbingDocumented for transparency, zero guarantees — may change or vanish in any release

Build integrations on stable operations. Use beta with eyes open. Treat internal as off-limits for anything you don't want to fix at short notice.

Request and response conventions ​

Requests and responses are JSON. Successful responses are wrapped in a data envelope:

json
{
  "data": { "id": "…", "hostname": "SERVER01" }
}

Errors come back as an error object with a human-readable message and a machine-readable code:

json
{
  "error": {
    "message": "API key rate limit exceeded",
    "code": "RATE_LIMITED"
  }
}

Branch on the code, not the message text — the full set is in the API error codes catalogue.

Status codes mean what you'd expect: 200/201 success, 400 malformed request, 401 missing or invalid key, 403 valid key but insufficient permissions or scopes, 404 not found, 429 rate-limited, 5xx our fault — retry with backoff and tell us if it persists.

Pagination ​

List endpoints are page-based: ?page= (starting at 1) and ?per_page= (default 50, maximum 100). List responses include the items and a total count:

bash
curl -H "Authorization: Bearer om_your_api_key_here" \
  "https://app.opsmerge.cloud/api/v1/agents?page=2&per_page=100"
json
{
  "data": { "items": [ … ], "total": 173, "page": 2 }
}

Keep requesting pages until you've collected total items. Exact response shapes vary slightly per endpoint — the interactive docs show the precise schema for each.

Common issues ​

401 on every call. Almost always a copy-paste problem with the key — check for stray whitespace, and make sure the header reads Authorization: Bearer om_… with a space between Bearer and the key.

403 on calls that should work. Both the key's scopes and the owner's role permissions must allow the action. Check the key's scopes in Settings → API Keys first, then the role.

429s under load. You're over the 300 requests/minute budget for that key. Honour Retry-After, batch where you can, and split unrelated integrations onto their own keys.

Next ​

Lovingly Created in the UK
OpsMerge is a product of Brindleford Technologies Ltd, company number 16871436, registered in England and Wales.