/api/v1 on your Command Center host is the stable, documented HTTP API: what you script against to drive 2501 from a pipeline, a CMDB sync, or a shell script. It is a real contract, not a private back end that happens to be reachable - Command Center’s own hosts and agents screens are built on these same endpoints.
200 is how you confirm a key works. What the key can reach is fixed at creation - see Authentication.
What is in the API
Every endpoint - with its request and response schema and a live request builder - is in the API Reference in the sidebar.
Other things Command Center manages - tickets, jobs, knowledge - are still served from unversioned routes that exist for the UI and are not part of the
v1 contract yet. They can change in any release. Resources move under /api/v1 release by release, and only what is documented here is stable.Authentication
Two ways in, and every/api/v1 endpoint but one accepts either:
- An API key as a bearer token. This is the one for scripts and integrations.
- A Command Center session cookie. This is what the web UI uses.
POST /api/v1/signals, where Splunk sends its alerts: it takes only a signal key, created with POST /api/v1/webhooks (source_type splunk), and refuses an API key or a session with 401.
Generate a key in Settings → API Keys, or read API Keys for the full walkthrough of scopes, expiry, and revocation. The raw key is shown once, at creation.
- A key is an administrator inside its own scope. Full read and write on its organization, or on every organization in the tenant for a tenant-scoped key. That reach is frozen at creation: it does not follow the person who created it.
- A key never reaches anything outside
/api/v1. The bearer header is ignored elsewhere, and the CC-only/api/internalroutes reject it outright. Notably, a key cannot create, list, or revoke keys - that stays session-only, so a leaked key cannot mint a successor. - A bad key is a hard failure, never a downgrade. An invalid, revoked, or expired key gets
401 UNAUTHORIZED; it never falls back to an anonymous request.
Organizations
UseGET /api/v1/organizations to list organizations you can access, and
GET /api/v1/organizations/{id} to read one.
A tenant-scoped key creates an organization with POST /api/v1/organizations
and archives one with POST /api/v1/organizations/{id}/archive. An org-scoped
key gets 403 on both. A name already used by an active organization is a
409, and so is archiving the tenant’s last active organization. Renaming and
unarchiving stay in Command Center, for a tenant-level administrator.
- reading a collection (
GET /hosts,/hosts/search,/hosts/export) carriesorg_idin the query string, - creating (
POST /hosts) carriesorg_idin the body, or in the query string for a CSVbatch, - anything addressed by id - a read, an update, a delete, an action, a sub-resource - carries none. The id already fixes the organization.
400. Naming one you cannot reach fails in one of two ways, and the difference is deliberate: an org-scoped caller pointing anywhere outside its own organization gets 403 ORG_ACCESS_DENIED, while a tenant-scoped caller naming an organization that is not in its tenant gets 404 NOT_FOUND - another tenant’s organizations are invisible, not merely forbidden.
org_id as a filter, not a requirement: with it you get that organization’s rows plus the tenant-wide ones, and without it everything the caller can see. Creating one with no organization makes it tenant-wide, which takes a tenant-scoped caller; an org-scoped caller gets 403. An org-scoped caller can read tenant-wide rows but cannot change or delete them.
Reading lists
Every list endpoint answers with the same envelope:limit rows, then pass the id of the last row you got as starting_after to get the next page. has_more tells you when to stop.
page parameter anywhere in v1. A cursor id that does not resolve - a deleted row, a row in another organization - is a 400 INVALID_CURSOR rather than a silent jump back to page one.
Writing
POST creates and POST updates. POST /api/v1/hosts creates a host; POST /api/v1/hosts/{id} updates that one. There is no PUT in v1 - sending one is a 405 METHOD_NOT_ALLOWED that names the verbs the endpoint does have, never a silent no-op.
An update is a partial update, and the rules are the same everywhere:
Creating or updating a plugin, and updating an agent, refuse a field the endpoint does not know with
400 VALIDATION_FAILED, for example enable is not a known field, so a misspelt field never answers 200 while changing nothing. An agent update still takes host_id when it names the agent’s current host, as a client that sends the whole agent back does; an agent never changes host, so another one answers 400.
POST actions on an item: POST /api/v1/agents/{id}/archive, POST /api/v1/agents/{id}/test-connection.
Success is always 200, creates included. There is no 201 and no 204.
Technologies, applications and tags
A host’s technologies, applications and tags come back in full on every host read - a list, a search,GET /hosts/{id}, and the response to a create or an update:
known says whether a technology is a known one or a custom one. logo names its logo, or is null when it shows the generic technology icon, as every custom technology does. A tag’s category is { "id": "tcat_...", "name": "Environment" } when it is in one, or null.
Writing a host’s lists
POST /hosts and POST /hosts/{id} take technologies, applications and tags, each a list of entries named by id or by name:
- A list you send replaces the host’s list, and a list you leave out keeps it. The host above ends with exactly PostgreSQL and Kafka as technologies and Payroll and Billing as applications, and its tags are untouched.
- An
idmust exist in the host’s organization, or the write fails. When an entry carries both, theidwins and thenameis ignored, so a list read from the API can be written back as it is, even after a rename. - A
nameis matched whatever its case, and created when missing. A technology name that is a known technology’s name or other name becomes that known technology, sopostgresgives PostgreSQL. Any other technology name becomes a custom technology. Names follow the name rule. - A write that fails creates nothing.
- A tag sent by
namecan carry thecategory_idit is created in. A tag that already exists keeps its own category, because a tag’s name is unique in the organization whatever its category.
Filtering hosts and agents
GET /hosts and GET /agents filter by entry, each parameter taking comma-separated ids:
The groups combine:
technology=<PostgreSQL id>,<Redis id>&application=<Payroll id> is hosts that have PostgreSQL or Redis, and Payroll. Each tag category is a group of its own, so prod and staging in Environment match either, while prod and dba in Team match both. An agent matches through its host, so an agent with no host never matches a filter.
Errors
Every error, on every endpoint, has the same flat body:
The codes you will actually meet:
code is drawn from a closed set, so it is safe to branch on. The table above is the part of that set a normal caller meets; a few others (TIMEOUT, PAYLOAD_TOO_LARGE, RATE_LIMITED) exist for the same reasons they do in any HTTP API. Treat an unrecognised code by its status class.
Rate limiting
v1 sets no rate limit of its own, but Command Center applies a per-deployment one in front of every route, and a request over it comes back 429 RATE_LIMITED. The default allows a few hundred requests a minute per client, which no ordinary integration approaches - it is there to stop a runaway loop, not to meter you. Because an operator can change it, treat the exact number as a property of the deployment you are calling, not of the API: back off on a 429 rather than pacing yourself to a constant.
Two things do have fixed limits, and they are the ones a bulk caller hits first: a list page is capped at 100 rows (limit), and import and export are capped at 10000 rows.
Bulk import is the one deliberate exception: POST /{resource}/batch returns 200 with a verdict per row even when every row failed, because the per-row results are the answer. A 4xx there means the request itself was wrong.
Versioning
v1 is a promise about shapes, not a frozen file. Fields and optional parameters get added; nothing documented here is renamed, removed, or given a new meaning without a new version. So:
- read fields by name and ignore the ones you do not know,
- do not depend on field order or on the absence of a field,
- do not depend on undocumented fields. A response may carry extras the UI needs; those are not part of the contract,
- treat a documented enum as open unless it is stated closed. A host’s
target_typeis a closed set; a task’sstatusis a state machine that gains steps.
v2 would be a new path served beside v1. Watch the release notes for the release you upgrade to.
Host
tags changed once inside v1, when hosts gained technologies and applications: it was a list of axis:value strings such as tech:postgres, and is now a list of tag entries beside the technologies and applications lists. Old values are not translated. A script written against the strings needs updating.Next
API Keys
Generating, scoping, and revoking keys.
Import and Export
Bulk CSV and JSON, in and out.

