These routes are REST compatibility endpoints for Unison. In the current Fabric PM2 stack, Unison runs on 127.0.0.1:8005; older standalone examples may still use :4100.
New internal work should prefer the Fabric resource/sync surface on /fabric when available.
Authentication
All endpoints except /api/v1/health and /api/v1/ready require a Bearer token in the Authorization header:
Authorization: Bearer <UNISON_API_KEY or UNISON_AGENT_API_KEY>
Requests with UNISON_AGENT_API_KEY have read-only access (used by Podium agents). Requests with UNISON_API_KEY have full read/write access (used by Diminuendo).
Health and Readiness
GET /api/v1/health
Health check. Always returns 200 if the process is running.
Response:
{ "ok": true }
GET /api/v1/ready
Readiness check. Returns 200 when the API process is connected to Valkey.
Response:
{ "ok": true }
Returns { "ok": false } only when the backing store is unavailable.
Key-Value Operations
GET /api/v1/kv/\{namespace\}
List all keys in a namespace. Optionally filter by key prefix.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
prefix | string | Optional key prefix filter |
Example:
curl -H "Authorization: Bearer $API_KEY" \
"http://localhost:4100/api/v1/kv/tenant%3Aacme%2Fsettings"
Response:
{
"items": [
{
"namespace": "tenant:acme/settings",
"key": "feature_flags",
"value": { "dark_mode": true, "beta_features": false },
"version": 5,
"updatedBy": "admin:a1",
"updatedAt": 1710700000000,
"expiresAt": null
},
{
"namespace": "tenant:acme/settings",
"key": "max_agents",
"value": 10,
"version": 1,
"updatedBy": "system",
"updatedAt": 1710600000000,
"expiresAt": null
}
]
}
With prefix filter:
curl -H "Authorization: Bearer $API_KEY" \
"http://localhost:4100/api/v1/kv/tenant%3Aacme%2Fsettings?prefix=feature"
GET /api/v1/kv/\{namespace\}/\{key\}
Get a single key-value entry.
Example:
curl -H "Authorization: Bearer $API_KEY" \
"http://localhost:4100/api/v1/kv/tenant%3Aacme%2Fuser%3Au1%2Fpreferences/theme"
Response (200):
{
"namespace": "tenant:acme/user:u1/preferences",
"key": "theme",
"value": "dark",
"version": 3,
"updatedBy": "user:u1",
"updatedAt": 1710700000000,
"expiresAt": null
}
Response (404):
{ "error": "Not found" }
PUT /api/v1/kv/\{namespace\}/\{key\}
Set a key-value entry. This is a write operation applied atomically by the Valkey-backed store.
Request body:
{
"value": "dark",
"actor": "user:u1"
}
| Field | Type | Required | Description |
|---|---|---|---|
value | any | Yes | The value to store (JSON-serialized internally) |
actor | string | No | Identity of the writer (defaults to "api") |
Example:
curl -X PUT \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"value": "dark", "actor": "user:u1"}' \
"http://localhost:4100/api/v1/kv/tenant%3Aacme%2Fuser%3Au1%2Fpreferences/theme"
Response (200):
{
"namespace": "tenant:acme/user:u1/preferences",
"key": "theme",
"value": "dark",
"version": 4,
"updatedBy": "user:u1",
"updatedAt": 1710700050000,
"expiresAt": null
}
Response (503 -- backing store unavailable):
{ "error": "Store unavailable" }
DELETE /api/v1/kv/\{namespace\}/\{key\}
Delete a key. The mutation is applied atomically by the Valkey-backed store.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
actor | string | Identity of the deleter (defaults to "api") |
Example:
curl -X DELETE \
-H "Authorization: Bearer $API_KEY" \
"http://localhost:4100/api/v1/kv/tenant%3Aacme%2Fuser%3Au1%2Fpreferences/theme?actor=user:u1"
Response (200):
{ "deleted": true }
Batch Operations
POST /api/v1/batch
Execute multiple set/delete operations atomically in one Valkey-backed batch.
Request body:
{
"operations": [
{ "op": "set", "namespace": "tenant:acme/settings", "key": "theme_default", "value": "light" },
{ "op": "set", "namespace": "tenant:acme/settings", "key": "max_sessions", "value": 50 },
{ "op": "delete", "namespace": "tenant:acme/settings", "key": "deprecated_flag" }
],
"actor": "admin:a1"
}
| Field | Type | Required | Description |
|---|---|---|---|
operations | array | Yes | Array of operations to execute atomically |
operations[].op | "set" or "delete" | Yes | Operation type |
operations[].namespace | string | Yes | Target namespace |
operations[].key | string | Yes | Target key |
operations[].value | any | For set only | Value to store |
actor | string | No | Identity of the writer (defaults to "api") |
Response (200):
{ "ok": true }
Change Polling
GET /api/v1/changes
Poll for recent changes. Useful for clients that cannot use WebSocket subscriptions.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
tenant | string | "" | Filter changes by tenant ID |
after | number | 0 | Return changes with seq greater than this value |
limit | number | 100 | Maximum number of changes to return |
Example:
curl -H "Authorization: Bearer $API_KEY" \
"http://localhost:4100/api/v1/changes?tenant=acme&after=100&limit=50"
Response:
{
"changes": [
{
"seq": 101,
"op": "set",
"namespace": "tenant:acme/settings",
"key": "max_agents",
"value": 20,
"version": 2,
"actor": "admin:a1",
"timestamp": 1710700100000,
"tenantId": "acme"
},
{
"seq": 102,
"op": "delete",
"namespace": "tenant:acme/settings",
"key": "deprecated_flag",
"value": null,
"version": 3,
"actor": "admin:a1",
"timestamp": 1710700100001,
"tenantId": "acme"
}
],
"lastSeq": 102
}
Use lastSeq as the after parameter in subsequent polls to get only new changes.
Compatibility Cluster Routes
GET /api/v1/cluster/status
Get compatibility status for the current service.
Response:
{
"ok": true,
"mode": "valkey"
}
| Field | Type | Description |
|---|---|---|
ok | boolean | Whether the service is healthy |
mode | string | Current backing mode; valkey in the current implementation |
GET /api/v1/cluster/leader
Compatibility endpoint for old cluster-aware clients.
Response:
{
"leader": null,
"address": null,
"mode": "valkey"
}
POST /api/v1/cluster/join
Compatibility endpoint. Unison topology is managed by Valkey, not by Unison peer membership.
Request body:
{
"nodeId": "node-3",
"address": "10.0.1.3:4100"
}
Response (410):
{ "error": "Cluster membership is managed by Valkey, not Unison" }
POST /api/v1/cluster/remove
Compatibility endpoint. Unison topology is managed by Valkey, not by Unison peer membership.
Request body:
{
"nodeId": "node-3"
}
Response (410):
{ "error": "Cluster membership is managed by Valkey, not Unison" }
OpenAPI Spec
GET /api/v1/openapi.json
Returns an OpenAPI 3.0.3 specification for all endpoints.
Error Responses
All error responses use JSON with an error field:
{ "error": "Unauthorized" }
| Status | Meaning |
|---|---|
| 400 | Bad request (missing required fields) |
| 401 | Missing or invalid API key |
| 404 | Key not found |
| 410 | Compatibility cluster membership route is not available |
| 500 | WebSocket upgrade failure |
| 503 | Backing store unavailable |