Docs : inventory-api
Inventory API
RackPeek exposes a small HTTP API for upserting inventory from scripts and external systems — useful for automatic ingestion from Ansible, Terraform, discovery scanners, or any other tool that can produce a YAML or JSON description of your infrastructure.
The API is served by the same process as the Web UI, on the same port.
What you need
- The server URL — wherever RackPeek is running, e.g.
http://localhost:8080. - An API key — a shared secret you choose, set via the
RPK_API_KEYenvironment variable on the server. Requests without a matching key are rejected.
Configuring the API key
Set RPK_API_KEY on the RackPeek container or process. Until you set it,
the inventory endpoint returns 503 Service Unavailable for every request.
| Variable | Required | Description |
|---|---|---|
RPK_API_KEY |
Yes (to use the API) | Shared secret. Clients must send this in the X-Api-Key header. |
Example with Docker Compose:
version: "3.9"
services:
rackpeek:
image: aptacode/rackpeek:latest
container_name: rackpeek
ports:
- "8080:8080"
volumes:
- rackpeek-config:/app/config
environment:
- RPK_API_KEY=change-me-to-a-long-random-string
restart: unless-stopped
volumes:
rackpeek-config:
Pick a long random value (e.g. openssl rand -hex 32). The server compares
keys in constant time, so timing-based guessing is not viable, but a weak
key still loses to brute force — treat it like a password.
Endpoints
GET /health
Liveness check. No authentication. Returns 200 OK with body rackpeek as
text/plain. Used by the Docker healthcheck.
curl http://localhost:8080/health
# rackpeek
POST /api/inventory
Upsert resources and connections. Requires the X-Api-Key header.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
yaml |
string | one of yaml or json |
Raw YAML payload, same schema as config.yaml. |
json |
object | one of yaml or json |
Structured JSON payload (see shape below). |
mode |
"Merge" | "Replace" |
No (default Merge) |
How incoming resources combine with existing ones. |
dryRun |
bool | No (default false) |
Compute the diff and return it without writing to disk. |
Exactly one of yaml or json must be supplied — sending both is rejected.
Merge semantics
For resources whose name doesn't already exist, the resource is added.
For resources whose name already exists:
Merge— fields present in the incoming payload overwrite the existing values; fields that are omitted,null, empty lists, or empty maps in the incoming payload are left untouched. Use this for partial updates.Replace— the incoming resource fully replaces the existing one, even for omitted fields. Use this when the incoming payload is the authoritative full picture.
If an incoming resource has a different kind than the existing one with
the same name (e.g. server → switch), the existing resource is fully
replaced regardless of mode.
JSON payload shape
The json field accepts the same shape as config.yaml — a root object
with version, resources, and optional connections. The canonical
schemas live in schemas/
in the repository.
{
"version": 3,
"resources": [
{
"kind": "Server",
"name": "web-01",
"tags": ["homelab", "prod"],
"labels": { "env": "production" },
"ipmi": true,
"ram": { "size": 64 }
}
],
"connections": []
}
The kind discriminator is case-sensitive. Use the exact value for each
resource type: Server, Switch, Router, Firewall, AccessPoint,
Ups, Desktop, Laptop, Other, Service, System.
Response
200 OK with a diff summary:
| Field | Description |
|---|---|
added |
Names of resources that were newly created. |
updated |
Names of existing resources whose fields changed under Merge mode. |
replaced |
Names of resources fully overwritten (either Replace mode, or a kind mismatch). |
oldYaml |
YAML snapshot of each updated/replaced resource before the change, keyed by name. |
newYaml |
YAML snapshot of each incoming resource after the change, keyed by name. |
oldYaml is omitted for newly-added resources (nothing existed to snapshot).
Error responses
| Status | When |
|---|---|
400 Bad Request |
Validation failure — neither yaml nor json supplied, both supplied, malformed payload, duplicate resource names, etc. Body: { "error": "..." }. |
401 Unauthorized |
X-Api-Key header missing or doesn't match RPK_API_KEY. |
503 Service Unavailable |
RPK_API_KEY is not configured on the server. |
Examples
Upsert from a YAML file with curl
curl -X POST http://localhost:8080/api/inventory \
-H "X-Api-Key: $RPK_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -Rs --arg mode Merge '{yaml: ., mode: $mode}' < inventory.yaml)"
Upsert a single resource as JSON
curl -X POST http://localhost:8080/api/inventory \
-H "X-Api-Key: $RPK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "Merge",
"json": {
"version": 3,
"resources": [
{
"kind": "Server",
"name": "web-01",
"tags": ["homelab"],
"ram": { "size": 64 }
}
]
}
}'
Preview a change with dryRun
curl -X POST http://localhost:8080/api/inventory \
-H "X-Api-Key: $RPK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dryRun": true,
"mode": "Replace",
"json": {
"version": 3,
"resources": [
{ "kind": "Server", "name": "web-01", "ram": { "size": 128 } }
]
}
}'
The response shows what would change without modifying config.yaml.
Bash helper
rpk_ingest() {
local file="$1"
curl -fsS -X POST "${RPK_URL:-http://localhost:8080}/api/inventory" \
-H "X-Api-Key: $RPK_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -Rs '{yaml: ., mode: "Merge"}' < "$file")"
}
rpk_ingest inventory.yaml
Tips
- Run with
dryRun: truefirst when wiring up a new pipeline — the response tells you exactly what would change. - Prefer
Mergewhen your script only knows about a subset of fields (e.g. a discovery scanner that only sees CPU/RAM but doesn't know about labels/tags). UseReplacewhen the script owns the full resource. - The
tagsarray (a list) is replaced wholesale by any non-empty incoming value, even inMergemode — if you want to add a single tag without overwriting the others, fetch the resource first or use the CLI (rpk <kind> tag add ...). - The
labelsmap (a dictionary) is merged key-wise inMergemode — existing keys absent from the incoming payload are preserved; keys present in the incoming payload overwrite the existing value. - The same YAML you commit to Git via the Git integration is what the API reads and writes — there is no separate API store.