Matrix logo

API Reference

Chronos exposes an HTTP API on port 9096 with two-layer auth (transport bearer + agent-DID principal token). Endpoints cover health checks, agent auth, and alarm CRUD.

Chronos exposes an HTTP API on port 9096 (configurable) with two-layer auth: a shared transport bearer and an agent-DID principal token. All responses use the uniform {ok, data, error} envelope.

Source file: internal/server/server.go, pkg/types/types.go.


Response envelope

Every response uses the types.Envelope shape:

{
  "ok": true,
  "data": { ... }
}

or on failure:

{
  "ok": false,
  "error": {
    "code": "unauthorized",
    "message": "missing or invalid transport bearer",
    "retryable": false
  }
}

Error codes are stable constants the planner/agent may branch on:

CodeMeaning
invalid_requestMalformed request body or parameters
unauthorizedTransport or principal auth failed
not_foundAlarm ID unknown or not owned by caller
conflictIdempotency-key clash with a different alarm
internalServer-side failure

Public endpoints

GET /

Returns service identity.

{"ok": true, "data": {"service": "chronosd", "version": "0.1.0", "health": "/healthz"}}

GET /healthz

Returns database connectivity status. Returns 503 when the database is unreachable.

{"ok": true, "data": {"status": "ok", "version": "0.1.0", "db": true}}

Agent auth endpoints

POST /v1/agent/auth/challenge

Opens the challenge/verify flow. Requires transport auth.

Request:

{"did": "did:matrix:<uuid>:<keyfp16>"}

Response:

{
  "ok": true,
  "data": {
    "did": "did:matrix:...",
    "nonce": "<base64url>",
    "message": "matrix-chronos-auth:<did>:<nonce>",
    "expires_in": 120
  }
}

The agent must ed25519-sign the message string with the private key matching the DID's fingerprint.

POST /v1/agent/auth/verify

Proves possession of the DID's key. Requires transport auth.

Request:

{
  "did": "did:matrix:...",
  "public_key": "<hex ed25519 pubkey>",
  "nonce": "<nonce from challenge>",
  "signature": "<hex ed25519 signature>"
}

Response:

{
  "ok": true,
  "data": {
    "token": "<hmac principal token>",
    "owner_user_id": "<supabase uuid>",
    "expires_in": 86400
  }
}

The token is presented as X-Chronos-Agent on all alarm endpoints.


Alarm CRUD endpoints

All alarm endpoints require both transport auth and a valid X-Chronos-Agent principal token.

POST /v1/alarms

Creates a new alarm.

Request:

{
  "label": "Review PR #42",
  "kind": "once",
  "delay_seconds": 600,
  "conversation_id": "conv-abc",
  "wake_message": "Time to review PR #42. The CI passed.",
  "payload": {"pr_number": 42},
  "idempotency_key": "pr-review-42",
  "max_failures": 3
}

For once alarms, provide exactly one of delay_seconds or fire_at (RFC3339). For cron alarms:

{
  "kind": "cron",
  "cron_expr": "@every 5m",
  "timezone": "America/New_York",
  "conversation_id": "conv-abc",
  "wake_message": "HEARTBEAT: review your active goals..."
}

Response:

{
  "ok": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "next_fire_at": "2026-07-14T15:30:00Z",
    "status": "active"
  }
}

When the alarm carries an idempotency_key that already exists for the owner, the existing alarm is returned (no duplicate created).

GET /v1/alarms

Lists the caller's alarms, most recent first.

Query parameters:

ParamTypeDefaultDescription
limitint100Max alarms to return (capped at 500)

Response:

{
  "ok": true,
  "data": {
    "alarms": [/* array of View objects */],
    "count": 3
  }
}

GET /v1/alarms/{id}

Returns a single alarm by ID. Owner-checked: returns 404 if the alarm does not exist or belongs to a different agent.

Response:

{
  "ok": true,
  "data": {/* View object */}
}

DELETE /v1/alarms/{id}

Cancels an active alarm. Owner-checked. Already-fired or already-cancelled alarms return success (idempotent cancellation).

Response:

{
  "ok": true,
  "data": {/* View object with status="cancelled" */}
}

CreateAlarmRequest fields

FieldTypeRequiredDescription
labelstringNoHuman-readable label
kindstringYes"once" or "cron"
delay_secondsint64Once*Relative delay from now (seconds)
fire_atstringOnce*Absolute RFC3339 instant. *Exactly one of delay_seconds or fire_at required for once
cron_exprstringCron5-field expression, @descriptor, or @every Nm
timezonestringNoIANA timezone for cron evaluation (default UTC)
conversation_idstringNoConversation to resume into (empty = fresh)
wake_messagestringYesThe contextful turn delivered on wake
payloadJSONNoOpaque state echoed back on wake
idempotency_keystringNoPer-owner dedup key
max_failuresintNoWake-delivery retry ceiling (default 5)

Request size limits

The maximum request body is 256 KB (maxBodyBytes). Larger bodies are rejected with invalid_request.