Relay · Contract reference
HTTP operation index and compatibility
This page inventories the current HTTP surface. It is an operation reference, not a generated schema. Request fields, response examples, retry behavior, and known gaps remain documented in the task guides linked from each area.
Base URL and authentication
All JSON operations use your Relay origin, for example https://relay.example.com. There is no /v1 prefix today.
| Surface | Authentication |
|---|---|
Conversation /api/* | Authorization: Bearer <tenant-user-jwt> |
| Metadata update | Tenant JWT or X-Admin-Token |
Operator /api/admin/* | X-Admin-Token |
Host secret sync /api/host/* | HTTP Basic with tenant-scoped host-sync credentials |
| Metrics | Authorization: Bearer <metrics-token> when METRICS_TOKEN is configured |
| Health | None |
Responses normally wrap resources in { "data": ... }. Most conversation errors use { "errors": [...] }; search, attachments, and rate-limit paths also use { "error": "..." }. This inconsistency is part of the current contract and remains product work.
Tenant isolation is not selected by a query parameter on normal conversation routes. Relay derives the tenant from the verified JWT and routes database access to that tenant’s schema.
Channels and membership
| Method | Path | Purpose |
|---|---|---|
GET | /api/channels | List channels visible to the tenant user |
POST | /api/channels | Create a channel |
GET | /api/channels/{channel_id} | Read one channel |
POST | /api/channels/{channel_id}/close | Close a channel |
PATCH | /api/channels/{channel_id}/metadata | Merge metadata |
PUT | /api/channels/{channel_id}/metadata | Replace metadata |
GET | /api/channels/{channel_id}/members | List channel members |
POST | /api/channels/{channel_id}/members | Add a member |
DELETE | /api/channels/{channel_id}/members/{member_id} | Remove the permitted membership |
POST | /api/channels/{channel_id}/notification_mute | Mute notifications for the current member |
DELETE | /api/channels/{channel_id}/notification_mute | Remove the current member’s mute |
GET | /api/channels/unread?channel_ids={id} | Read unread counts for selected channels |
Channel discovery and membership authorization have current limitations. Read Channels and permissions before treating these operations as a private-channel or moderator model.
Messages, threads, pins, and reactions
| Method | Path | Purpose |
|---|---|---|
GET | /api/channels/{channel_id}/messages | List history with sequence pagination |
POST | /api/channels/{channel_id}/messages | Create a message with nonce idempotency |
PATCH | /api/channels/{channel_id}/messages/{message_id} | Edit the message body |
POST | /api/channels/{channel_id}/messages/{message_id}/retract | Retract a message |
POST | /api/channels/{channel_id}/messages/{message_id}/pin | Pin a message |
DELETE | /api/channels/{channel_id}/messages/{message_id}/pin | Unpin a message |
GET | /api/channels/{channel_id}/messages/pinned | List pinned messages |
GET | /api/channels/{channel_id}/messages/{message_id}/thread | List thread replies |
GET | /api/channels/{channel_id}/messages/{message_id}/reactions | Read reactions and summary |
PUT | /api/channels/{channel_id}/messages/{message_id}/reactions/{emoji} | Add the current user’s reaction |
DELETE | /api/channels/{channel_id}/messages/{message_id}/reactions/{emoji} | Remove the current user’s reaction |
Only message creation exposes a client idempotency key. Preserve the same nonce after an uncertain send. Channel-local seq, not timestamps or list position, defines message order. See Send a message and Realtime and reconnect.
Search and attachments
| Method | Path | Purpose |
|---|---|---|
GET | /api/channels/{channel_id}/messages/search?q={query} | Search one channel |
POST | /api/uploads/presign | Create a direct-upload attachment record and PUT URL |
GET | /api/attachments/{attachment_id}/url | Create a short-lived attachment download URL |
Search indexing is asynchronous. Attachment upload is a multi-step contract: presign, object PUT, then bind the attachment ID in the same message transaction. See Search and Attachments for cursor, safety, and lifecycle boundaries.
Tenant host-sync API
| Method | Path | Purpose |
|---|---|---|
POST | /api/host/tenants/{tenant_id}/jwt_secret/fetch | Fetch the active tenant signing secret with host-sync Basic credentials |
This response carries credential material and sets no-store cache headers. It is a server-to-server bootstrap path, not a browser API. See Authentication.
Operator API
Every operation in this section requires X-Admin-Token. It is a deployment-wide control plane, not tenant self-service.
Tenants, credentials, and limits
| Method | Path | Purpose |
|---|---|---|
GET | /api/admin/tenants | List tenants |
POST | /api/admin/tenants | Create a tenant and initial host-sync credential |
GET | /api/admin/tenants/{tenant_id} | Read tenant status |
POST | /api/admin/tenants/{tenant_id}/disable | Disable tenant authentication and host sync |
POST | /api/admin/tenants/{tenant_id}/jwt_secret/rotate | Rotate the JWT primary secret |
POST | /api/admin/tenants/{tenant_id}/jwt_secret/expire_previous | Remove the previous JWT secret |
POST | /api/admin/tenants/{tenant_id}/jwt_secret/revoke_all | Revoke existing JWT secrets and replace the primary |
POST | /api/admin/tenants/{tenant_id}/host_sync_secret/rotate | Rotate host-sync credentials |
POST | /api/admin/tenants/{tenant_id}/host_sync_secret/expire_previous | Remove the previous host-sync secret |
POST | /api/admin/tenants/{tenant_id}/host_sync_secret/revoke_all | Revoke host-sync credentials and issue a replacement |
GET | /api/admin/tenants/{tenant_id}/rate_limits | Read tenant rate-limit overrides |
PUT | /api/admin/tenants/{tenant_id}/rate_limits | Replace tenant rate-limit overrides |
Data governance and audit
| Method | Path | Purpose |
|---|---|---|
GET | /api/admin/tenants/{tenant_id}/data_retention | Read retention policy |
PUT | /api/admin/tenants/{tenant_id}/data_retention | Replace retention policy |
DELETE | /api/admin/messages/{message_id}?tenant_id={tenant_id} | Soft-delete a message and schedule related cleanup |
DELETE | /api/admin/users/{user_id}?tenant_id={tenant_id} | Delete or tombstone tenant-scoped user data |
POST | /api/admin/users/{user_id}/exports?tenant_id={tenant_id} | Queue a user export |
GET | /api/admin/user_exports/{job_id}?tenant_id={tenant_id} | Read export status |
GET | /api/admin/user_exports/{job_id}/download?tenant_id={tenant_id}&token={token} | Download a completed export |
GET | /api/admin/tenant_audit_events?tenant_id={tenant_id} | Query control-plane audit rows |
GET | /api/admin/message_audit_events?tenant_id={tenant_id} | Query message lifecycle audit rows |
Read Data lifecycle and audit before mapping these operations to erasure or compliance controls.
Agents, search, handoffs, and dead letters
| Method | Path | Purpose |
|---|---|---|
GET | /api/admin/bots | List registered agent users |
POST | /api/admin/bots | Register an agent user |
DELETE | /api/admin/bots/{user_id} | Revoke an agent user |
POST | /api/admin/tenants/{tenant_id}/search/reindex | Queue a tenant search backfill |
GET | /api/admin/tenants/{tenant_id}/search/reindex | Read reindex status |
GET | /api/admin/post_commit_handoffs | Inspect durable handoffs |
GET | /api/admin/post_commit_handoffs/channel_backlogs | Inspect channel-level fan-out backlog |
GET | /api/admin/post_commit_handoffs/{handoff_id} | Inspect one handoff |
GET | /api/admin/host_webhook/dead_letters | List exhausted webhook deliveries |
POST | /api/admin/host_webhook/dead_letters/{dead_letter_id}/replay | Requeue a dead letter |
Dead-letter replay has fidelity limits for some event types. See Webhooks and Deployment and operations.
Health, metrics, and adapter ingress
| Method | Path | Purpose |
|---|---|---|
GET | /health | Legacy liveness |
GET | /livez | Process liveness |
GET | /ready | Database-backed readiness |
GET | /readyz | Database-backed readiness alias |
GET | /metrics | Prometheus metrics, optionally protected by METRICS_TOKEN |
POST | /adapters/{source}/webhook | In-tree adapter ingress with source-specific verification |
Adapter ingress is an extension point, not a uniform customer webhook contract. Supported sources, signatures, and payloads depend on the installed in-tree adapter.
Current compatibility policy
The HTTP paths are unversioned and no canonical OpenAPI or AsyncAPI artifact is published. Therefore the REST surface is a contract beta, even where the underlying database invariants and SDK package are stronger.
During contract beta:
- message nonce idempotency, tenant isolation, the shared REST and WebSocket write path, and server-assigned channel sequence are the core invariants;
- clients must ignore unknown response fields and unknown WebSocket event fields;
- cursors are opaque and must not be parsed, altered, or constructed by clients;
- additive fields and endpoints may ship without a major path change;
- a changed or removed field, route, auth rule, status meaning, or idempotency behavior is breaking and must ship with updated docs and a migration note;
- the published
@tonetify/relaypackage version does not establish server compatibility until a tested compatibility matrix exists.
Before Relay can claim a stable public API, it still needs a versioned base path or media type, a canonical machine-readable schema, contract tests against that schema, a deprecation and overlap window, changelog and migration surfaces, and a server-to-SDK compatibility matrix.
Do not infer server API stability from the name v1 in internal documents or the SDK’s published 0.1.0-beta.1 version. Neither is a published server API version.
Related: Authentication · Limits and errors · Relay overview