iGent Concert
/unison/api/rest/

REST API

HTTP REST endpoints for Unison key-value operations and compatibility cluster routes

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:

ParameterTypeDescription
prefixstringOptional 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"
}
FieldTypeRequiredDescription
valueanyYesThe value to store (JSON-serialized internally)
actorstringNoIdentity 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:

ParameterTypeDescription
actorstringIdentity 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"
}
FieldTypeRequiredDescription
operationsarrayYesArray of operations to execute atomically
operations[].op"set" or "delete"YesOperation type
operations[].namespacestringYesTarget namespace
operations[].keystringYesTarget key
operations[].valueanyFor set onlyValue to store
actorstringNoIdentity 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:

ParameterTypeDefaultDescription
tenantstring""Filter changes by tenant ID
afternumber0Return changes with seq greater than this value
limitnumber100Maximum 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"
}
FieldTypeDescription
okbooleanWhether the service is healthy
modestringCurrent 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" }
StatusMeaning
400Bad request (missing required fields)
401Missing or invalid API key
404Key not found
410Compatibility cluster membership route is not available
500WebSocket upgrade failure
503Backing store unavailable