API Reference
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:
labelSelectorreads metadata an operator set through this API and can change.attributeSelectorreads metadata the resource reported about itself. Only agents have any: their OpAMPAgentDescription, 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.
| Form | Example | Matches |
|---|---|---|
key=value, key==value | env=prod | the label is present and equal |
key!=value | tier!=canary | the 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 |
key | deprecated | the label is present, whatever its value |
!key | !deprecated | the 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:
| Resource | Parameter | Backed by |
|---|---|---|
| namespaces, hosts, containers, users | labelSelector | metadata.labels |
| agent groups, packages, remote configs, certificates, endpoints, schemas | labelSelector | metadata.attributes |
| agents | attributeSelector | the reported agent description |
| roles, role bindings | neither | — |
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.
| Resource | Selectable fields |
|---|---|
| agents | metadata.namespace, status.connected, status.healthy |
| agent groups, remote configs, certificates | metadata.namespace |
| agent packages | metadata.namespace, spec.packageType, spec.version |
| remote config schemas | metadata.namespace, spec.binary, spec.version |
| endpoints | metadata.namespace, spec.protocol |
| hosts, containers | spec.platform |
| namespaces | metadata.name |
| users | spec.isActive |
| roles | spec.isBuiltIn |
| role bindings | spec.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 succeeded201 Created— resource created204 No Content— succeeded with no body400 Bad Request— invalid parameters401 Unauthorized— missing or invalid authentication404 Not Found— resource not found500 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.
Feedback
Was this page helpful?