API Reference

The OpAMP Commander REST API.

The apiserver exposes a REST API under /api/v1. Most resources are namespace-scoped and live under /api/v1/namespaces/{namespace}/...; a few (hosts, containers, roles, users, server info) are cluster-scoped.

Interactive API documentation (Swagger UI) is generated from the source and served by the running server. The OpAMP agent protocol itself is handled over a WebSocket at /api/v1/opamp.

OpAMP security boundary

The OpAMP endpoint accepts unauthenticated agent connections. An agent chooses its namespace through its reported service.namespace attribute (identifying first, then non-identifying) (default when absent). REST RBAC does not restrict that choice: an agent can claim another namespace and receive remote configuration assigned there. Namespaces organize agents; they are not a trust boundary for the OpAMP protocol. Only expose /api/v1/opamp to trusted agents. If agents have different trust levels, enforce agent authentication and namespace access before traffic reaches the apiserver; checking connection headers alone cannot verify the namespace reported later in OpAMP messages.

Authentication

Obtain a JWT and send it as a bearer token:

Authorization: Bearer <your-jwt-token>

Basic auth

GET /api/v1/auth/basic
Authorization: Basic <base64(username:password)>

GitHub OAuth2 (browser)

GET  /api/v1/auth/github                 # begin browser-based login
POST /api/v1/auth/github/authcode        # exchange an authorization code

GitHub device flow (CLI)

GET /api/v1/auth/github/device           # request a device + user code
GET /api/v1/auth/github/device/exchange  # poll to exchange for a token

Session helpers

GET /api/v1/auth/info       # info about the current credential
GET /api/v1/auth/refresh    # refresh an access token

Namespaces

GET    /api/v1/namespaces
POST   /api/v1/namespaces
GET    /api/v1/namespaces/{namespace}
DELETE /api/v1/namespaces/{namespace}

A namespace is derived from each agent’s service.namespace attribute (identifying first, then non-identifying), defaulting to default.

REST namespace management uses the namespace resource in RBAC. Listing or creating namespaces requires namespace:LIST or namespace:CREATE in the * scope; reading, updating, or deleting one requires the matching action in that namespace. The built-in default role grants none of these permissions.

Agents

GET  /api/v1/namespaces/{namespace}/agents
GET  /api/v1/namespaces/{namespace}/agents/{id}
POST /api/v1/namespaces/{namespace}/agents/search

List endpoints accept limit and continue query parameters for pagination, plus the filtering parameters described in Filtering and searching.

Filtering and searching

Every list endpoint accepts three filtering parameters. They are answered by the datastore, not by the server fetching a page and discarding rows from it, so remainingItemCount always describes the same set the page was drawn from.

GET /api/v1/namespaces/prod/agents?attributeSelector=service.name%3Dotel-collector&fieldSelector=status.connected%3Dtrue
GET /api/v1/namespaces/prod/endpoints?labelSelector=env%3Dprod

labelSelector and attributeSelector

Two parameters, one grammar. They are separate because they read different metadata, and the difference is who sets it:

  • labelSelector reads metadata an operator set through this API and can change.
  • attributeSelector reads metadata the resource reported about itself. Only agents have any: their OpAMP AgentDescription, which arrives over the protocol and cannot be set here.

Sending the one a resource does not have is a 400 naming what it does have — not an ignored parameter, which would hand back the whole collection with a 200.

The expression syntax below is identical for both. Multiple requirements are comma-separated and combined with AND.

FormExampleMatches
key=value, key==valueenv=prodthe label is present and equal
key!=valuetier!=canarythe label differs or is absent
key in (a,b)env in (prod,stg)the label is one of the listed values
key notin (a,b)tier notin (canary,dev)the label is none of them, or is absent
keydeprecatedthe label is present, whatever its value
!key!deprecatedthe label is absent

The negative operators deliberately match a resource that carries no such label at all, exactly as Kubernetes label selectors do.

Which parameter a resource answers, and what backs it:

ResourceParameterBacked by
namespaces, hosts, containers, userslabelSelectormetadata.labels
agent groups, packages, remote configs, certificates, endpoints, schemaslabelSelectormetadata.attributes
agentsattributeSelectorthe reported agent description
roles, role bindingsneither—

An agent’s description is one attribute domain. The split into identifying and non-identifying attributes says which attributes form the agent’s identity, not which an operator may filter on, so attributeSelector=os.type%3Dlinux finds an agent whichever half reports it.

Roles and role bindings carry neither kind. Either parameter against them is answered with 400 Bad Request saying so, rather than with an empty list.

fieldSelector

An expression over the resource’s own fields, using =, == and != only — there are no set-based or existence forms. Only the fields a resource documents as selectable may be referenced; anything else is a 400 naming the field and listing the supported ones, so a filter is never silently dropped.

ResourceSelectable fields
agentsmetadata.namespace, status.connected, status.healthy
agent groups, remote configs, certificatesmetadata.namespace
agent packagesmetadata.namespace, spec.packageType, spec.version
remote config schemasmetadata.namespace, spec.binary, spec.version
endpointsmetadata.namespace, spec.protocol
hosts, containersspec.platform
namespacesmetadata.name
usersspec.isActive
rolesspec.isBuiltIn
role bindingsspec.roleRef.name

status.connected uses the same staleness-aware predicate as the connected badge, so a filtered list and the badge cannot disagree. The connected=true parameter on the agents endpoint is a shorthand for fieldSelector=status.connected=true.

name and nameContains

GET /api/v1/namespaces/prod/endpoints?name=tempo-
GET /api/v1/namespaces/prod/endpoints?name=tempo-&nameContains=traces

name is a case-sensitive prefix, served by an index range scan. nameContains is a case-insensitive substring. Both are matched literally — neither is ever interpreted as a pattern — and they combine.

On a large collection, always pair nameContains with name. No ordered index can answer “contains”, so nameContains alone is matched against every document in the collection. The page limit does not bound that: a listing reports an exact remainingItemCount, which means counting the whole matched set, so the scan runs in full on every request rather than stopping once a page is filled. Adding name confines it to that prefix’s indexed range.

Use name on its own wherever a prefix will do.

For agents, the name is the instance UID; for roles, the display name; for users, the email.

Deprecated parameters

The agents endpoint still accepts selector and nonIdentifyingSelector, repeated key=value parameters that predate attributeSelector. They are deprecated: attributeSelector expresses the same equality form and adds !=, in, notin and existence, and reaches both halves of the agent description.

Agent groups

GET    /api/v1/namespaces/{namespace}/agentgroups
POST   /api/v1/namespaces/{namespace}/agentgroups
GET    /api/v1/namespaces/{namespace}/agentgroups/{name}
PUT    /api/v1/namespaces/{namespace}/agentgroups/{name}
DELETE /api/v1/namespaces/{namespace}/agentgroups/{name}
GET    /api/v1/namespaces/{namespace}/agentgroups/{name}/agents

Agent packages

GET    /api/v1/namespaces/{namespace}/agentpackages
POST   /api/v1/namespaces/{namespace}/agentpackages
GET    /api/v1/namespaces/{namespace}/agentpackages/{name}
DELETE /api/v1/namespaces/{namespace}/agentpackages/{name}

Agent remote configs

GET    /api/v1/namespaces/{namespace}/agentremoteconfigs
POST   /api/v1/namespaces/{namespace}/agentremoteconfigs
GET    /api/v1/namespaces/{namespace}/agentremoteconfigs/{name}
DELETE /api/v1/namespaces/{namespace}/agentremoteconfigs/{name}

Certificates

GET    /api/v1/namespaces/{namespace}/certificates
POST   /api/v1/namespaces/{namespace}/certificates
GET    /api/v1/namespaces/{namespace}/certificates/{name}
DELETE /api/v1/namespaces/{namespace}/certificates/{name}

Connections

GET /api/v1/namespaces/{namespace}/connections

Returns the active agent connections for a namespace.

Hosts and containers (cluster-scoped)

GET /api/v1/hosts
GET /api/v1/hosts/{id}
GET /api/v1/hosts/{id}/agents

GET /api/v1/containers
GET /api/v1/containers/{id}
GET /api/v1/containers/{id}/agents

RBAC

GET    /api/v1/roles
POST   /api/v1/roles
GET    /api/v1/roles/{id}

GET    /api/v1/namespaces/{namespace}/rolebindings
POST   /api/v1/namespaces/{namespace}/rolebindings
GET    /api/v1/namespaces/{namespace}/rolebindings/{name}
DELETE /api/v1/namespaces/{namespace}/rolebindings/{name}

Users

GET  /api/v1/users
POST /api/v1/users
GET  /api/v1/users/me
GET  /api/v1/users/{id}

Server information

GET /api/v1/servers     # cluster server info
GET /api/v1/version      # build version
GET /api/v1/ping         # connectivity check

Health checks

Served by the management server (default localhost:9090):

GET /healthz
GET /readyz

Error responses

Errors follow the RFC 9457 Problem Details format with Content-Type: application/problem+json:

{
  "type": "about:blank",
  "title": "Not Found",
  "status": 404,
  "detail": "resource does not exist"
}

This includes authentication, refresh, and authorization HTTP errors. These endpoints are application APIs, not OAuth 2.0 authorization-server endpoints. The previous error / details JSON fields have been replaced by title / detail; clients should read detail for the explanation. type is about:blank, and title is the HTTP status phrase. Validation responses may also include an errors extension with field-level information.

OAuth callback redirects use RFC 6749 authorization error parameters: error contains a code (access_denied, invalid_request, or server_error) and error_description contains a public explanation. If there is no validated redirect destination, the callback returns an RFC 9457 HTTP error instead. Invalid or expired callback state returns HTTP 400 and never redirects.

Common status codes:

  • 200 OK — request succeeded
  • 201 Created — resource created
  • 204 No Content — succeeded with no body
  • 400 Bad Request — invalid parameters
  • 401 Unauthorized — missing or invalid authentication
  • 404 Not Found — resource not found
  • 500 Internal Server Error — server error

Agent-to-AgentGroup membership

GET /api/v1/namespaces/{namespace}/agents/{id}/agentgroups accepts the common list options: limit, continue, includeDeleted, labelSelector, fieldSelector (metadata.namespace), name, and nameContains. Filters describe AgentGroups; the agent must exist in the requested namespace or the response is 404. limit defaults to 50 (also when 0), accepts 1–1000, and rejects negative or larger values with 400. Consumers that previously relied on an unlimited response must follow the cursor to retrieve every matching group.

Matching happens in the read adapter before pagination. Each response contains at most one page of matching groups and an exact remainingItemCount for matching groups after that page. Per-group live statistics are computed only for returned groups. The page size bounds returned/materialized groups, not rows examined: reverse selector matching and exact counting may scan the remaining namespace inventory. Query-plan/count budgets remain tracked in #685; no membership projection is required by this read contract.

Pages follow the common insertion-key order (ascending MongoDB _id, increasing in-memory insertion sequence), rather than group priority or name. Updates retain the key. Treat continue as an opaque value and pass it back unchanged; it is non-empty for every non-empty page, including the last. Stop when remainingItemCount is 0. Resuming after a deleted anchor continues strictly after its insertion key. An invalid cursor returns 400.

Traversal is a live ordered read, not a snapshot across requests: with unchanged agent attributes, group selectors, and filters, stable groups appear exactly once in key order. Inserts after the cursor can appear on later pages; deletions and selector/attribute changes can remove or introduce memberships. A newly matching group before the cursor requires restarting from the first page. Counts apply to the current page read and can change between requests. Keep the namespace, agent, and filters fixed during traversal; cursors are currently storage-specific and not bound to these inputs. Opaque encoding/filter binding is tracked in #596.

The Go client’s ListAgentGroupsByAgent accepts the same ListOption values as other list methods. opampctl get agentgroup --agent <uid> -n <namespace> returns one bounded page by default. Add --stream to follow all pages (JSON is one array, YAML one document per page; text/short output has a header per page), or use --continue to resume. Cancellation or a later-page error can leave partial output and returns a nonzero exit status with retry details on stderr. In the web UI, an agent’s Agent groups link opens the existing paginated group table, with filters and Previous/Next controls.