Skip to content

API Reference

The Core API (server-core) exposes REST endpoints for managing all domain entities. Each service also generates Swagger/OpenAPI documentation when running.

Base URL

http://localhost:4000

Authentication

All endpoints (except health checks) require authentication via Authup. Include a Bearer token in the Authorization header:

Authorization: Bearer <token>

Response Shapes

Every entity endpoint answers with one of two envelopes. Both carry the payload under data and response-scoped extras under meta.

Record responses

Every single-record endpoint — getOne, create, update, delete, and the entity command routes — wraps the record in { data, meta }:

jsonc
// GET /nodes/1f9c0b7a-...
{
    "data": { "id": "1f9c0b7a-...", "name": "node-a", "online": true },
    "meta": { "schema": { /* see Query Capability Discovery */ } }
}

Mutations carry the same envelope with an empty meta{}, never omitted, never null:

jsonc
// POST /nodes  ->  201 Created
{ "data": { "id": "1f9c0b7a-...", "name": "node-a" }, "meta": {} }

Breaking change

Record endpoints previously answered with the bare entity. Consumers must now unwrap data. See @privateaim/core-http-kit for the client-side migration.

Collection responses

Collections were already enveloped, so their shape is unchanged apart from the new optional meta.schema:

jsonc
// GET /nodes
{
    "data": [ /* ... */ ],
    "meta": { "limit": 50, "offset": 0, "total": 128, "schema": { /* ... */ } }
}

total is always present. limit and offset report the pagination the server actually applied, so they are absent when no pagination was applied.

Endpoints that stay flat

The governing rule: entity-record shapes get the envelope; protocol, credential, stream and bespoke shapes stay flat. These endpoints answer with their own shape and never with { data, meta }:

EndpointShapeWhy it stays flat
GET / (core, storage, telemetry){ version, timestamp }Service metadata, not a record
POST /services/:id/hooknull (202)Harbor webhook protocol
POST /services/:id/commandnull (202)Harbor command protocol
GET/POST /nodes/:id/client/credentialsClientCredentialsOAuth2 credential payload
GET/POST /analyses/:id/client/credentialsClientCredentialsOAuth2 credential payload
GET /nodes/:id/registry/credentialsRegistryCredentialsProjection over registry + registry project, not a persisted record
DELETE /analysis-logsnull (202)Bulk delete, nothing to return
DELETE /analysis-node-logsnull (202)Bulk delete, nothing to return
DELETE /logs (telemetry)null (202)Bulk delete, nothing to return
GET /buckets/:id/streambinary streamContent-Type + attachment headers
GET /bucket-files/:id/streambinary streamContent-Type + attachment headers
POST /buckets/:id/upload{ data: files, meta: { total } }Returns many files — stays a collection, it is not a record response
entire server-messenger message surfaceid list / { messages } / voidNo entity records at all

Query Capability Discovery

Every query-capable GET describes its own queryable vocabulary under meta.schema — which filter, fields, sort and include keys the endpoint accepts, plus the pagination cap — so a consumer never has to read server source to build a valid query.

Collection reads advertise the full vocabulary:

jsonc
// GET /nodes  ->  meta.schema
{
    "name": "node",
    "strict": true,
    "fields": {
        "default": ["id", "name", "clientId", "externalName", "hidden", "type", "online", "publicKey", "robotId", "realmId", "registryId", "registryProjectId", "createdAt", "updatedAt"],
        "allowed": ["id", "name", "clientId", "externalName", "hidden", "type", "online", "publicKey", "robotId", "realmId", "registryId", "registryProjectId", "createdAt", "updatedAt"]
    },
    "filters": { "allowed": ["id", "name", "online", "hidden", "clientId", "realmId", "robotId"] },
    "pagination": { "maxLimit": 50 },
    "relations": {
        "allowed": ["registryProject", "registry"],
        "schemas": { "registryProject": "registryProject", "registry": "registry" }
    },
    "sort": { "allowed": ["name", "updatedAt", "createdAt"], "default": null }
}

Single-record reads advertise only the subset a record read processes — fields and relations. The filters, sort and pagination keys are absent (not null):

jsonc
// GET /nodes/1f9c0b7a-...  ->  meta.schema
{
    "name": "node",
    "strict": true,
    "fields": { "default": ["id", "name", /* ... */], "allowed": ["id", "name", /* ... */] },
    "relations": {
        "allowed": ["registryProject", "registry"],
        "schemas": { "registryProject": "registryProject", "registry": "registry" }
    }
}

Mutations (POST, DELETE) describe nothing — their meta is exactly {}.

Reading rules

  • The description is the static upper bound — the allow-list declared by the schema, not an actor-aware view. Actor-dependent gates (the accountSecret field gate on registries, realm scoping) are deliberately not reflected and may still strip individual keys per request. A name appearing in fields.allowed means the query parser accepts it, not that you will get it.
  • The shape is normalized — every described parameter carries every constraint key. null means the constraint was never declared and the server-side fallback applies; an empty array means an explicit "nothing allowed". These two are easy to confuse and mean opposite things.
  • Relation vocabulary is referenced, not expanded. relations.schemas names the schema governing each relation instead of inlining it, so dotted keys such as filter[registry.name] are discovered from the registry entity's own endpoints.
  • sort.allowed is derived from sort.default when no explicit allow-list is declared (e.g. master images declare sort: { default: { path: 'ASC' } } and describe as { allowed: ["path"], default: { path: "ASC" } }).

Endpoints without capability discovery

EndpointWhy
GET /logs (telemetry)Decoded as an open query — its filters are dynamic VictoriaLogs labels rather than a declared vocabulary, so there is nothing to describe. This is the one query endpoint without meta.schema.
GET /analyses/:id/client/permissionsProxies Authup ClientPermission records; no Hub-side schema exists.
POST /buckets/:id/uploadA write that answers with a collection; it advertises total only.

Core Entities

Updates are issued as POST /<collection>/:id — there is no PUT route. The Shape column tells you which envelope from Response Shapes to expect.

Analyses

MethodEndpointDescriptionShape
GET/analysesList analysescollection + schema
GET/analyses/:idGet analysis by IDrecord + schema
POST/analysesCreate analysisrecord, meta: {}
POST/analyses/:idUpdate analysisrecord, meta: {}
POST/analyses/:id/commandRun an analysis commandrecord, meta: {}
DELETE/analyses/:idDelete analysisrecord, meta: {}

Projects

MethodEndpointDescriptionShape
GET/projectsList projectscollection + schema
GET/projects/:idGet project by IDrecord + schema
POST/projectsCreate projectrecord, meta: {}
POST/projects/:idUpdate projectrecord, meta: {}
DELETE/projects/:idDelete projectrecord, meta: {}

Nodes

MethodEndpointDescriptionShape
GET/nodesList nodescollection + schema
GET/nodes/:idGet node by IDrecord + schema
POST/nodesCreate noderecord, meta: {}
POST/nodes/:idUpdate noderecord, meta: {}
DELETE/nodes/:idDelete noderecord, meta: {}

Registries

MethodEndpointDescriptionShape
GET/registriesList registriescollection + schema
GET/registries/:idGet registry by IDrecord + schema
POST/registriesCreate registryrecord, meta: {}
POST/registries/:idUpdate registryrecord, meta: {}
DELETE/registries/:idDelete registryrecord, meta: {}

Storage Service

Base URL: http://localhost:4001

Buckets

MethodEndpointDescriptionShape
GET/bucketsList bucketscollection + schema
GET/buckets/:idGet bucket by IDrecord + schema
POST/bucketsCreate bucketrecord, meta: {}
POST/buckets/:idUpdate bucketrecord, meta: {}
DELETE/buckets/:idDelete bucketrecord, meta: {}
POST/buckets/:id/uploadUpload files to a bucketcollection, no schema
GET/buckets/:id/streamStream the bucket contents as a tarflat (binary)

Bucket Files

MethodEndpointDescriptionShape
GET/bucket-filesList bucket filescollection + schema
GET/bucket-files/:idGet bucket file by IDrecord + schema
DELETE/bucket-files/:idDelete bucket filerecord, meta: {}
GET/bucket-files/:id/streamStream file contentsflat (binary)

Telemetry Service

Base URL: http://localhost:4002

MethodEndpointDescriptionShape
GET/eventsList eventscollection + schema
GET/events/:idGet event by IDrecord + schema
POST/eventsCreate eventrecord, meta: {}
DELETE/events/:idDelete eventrecord, meta: {}
GET/logsQuery logscollection, no schema
POST/logsWrite a logrecord, meta: {}
DELETE/logsDelete logsflat (null, 202)

Swagger Documentation

Each service generates OpenAPI documentation at runtime. When a service is running in development mode, visit:

  • Core API: http://localhost:4000/docs
  • Storage: http://localhost:4001/docs
  • Telemetry: http://localhost:4002/docs