Skip to Content
API ReferenceRelay API

Relay API

Solidus Relay is a DIDComm v2 messaging relay / mediator for relay.solidus.network. It is a chain-anchored mediator that routes encrypted DIDComm messages between Solidus DIDs — agents (wallets, credential issuers, verifiers) register for mediation (CoordinateMediation 2.0), and the relay holds and delivers their encrypted messages (Pickup Protocol 3.0) so they don’t need to be online at the same time. The relay routes JWE-encrypted envelopes it cannot read.

Relay is a message rail, not a KYC, wallet, payment, or agent product — those are sibling surfaces whose DIDComm messages it carries.

Base URL: https://relay.solidus.network

The DIDComm wire (POST /didcomm, GET /invite) is unprefixed. The operator management REST lives under /v1/*.


Authentication

Two mechanisms, for two different callers:

  • Operator session — the operator console exchanges a verified auth.solidus.network OIDC id_token for a relay session JWT via POST /v1/auth/session, then sends it as Authorization: Bearer <session-jwt>. Full access.
  • API key — a revocable key, Authorization: Bearer slk_..., scoped to read, manage, or full. Created via the operator console (there is no self-serve public registration route — operator accounts are provisioned through the OIDC handoff, not a POST /v1/operators call).

Routes marked requireAuth accept either. Routes marked requireManage additionally need write scope (an operator session is always full-access; a read-scoped API key is rejected with 403).

curl https://relay.solidus.network/v1/mediations \ -H "Authorization: Bearer slk_..."

POST /didcomm and GET /invite need no bearer auth — the DIDComm envelope itself carries the proof of who’s talking (it’s encrypted to the mediator’s key), and the OOB invitation is a public bootstrap document by design.


Route index

Route groupPathAuth
DIDComm wirePOST /didcommNone (JWE envelope)
OOB invitationGET /inviteNone (public bootstrap)
HealthGET /health, GET /v1/healthNone
Operator session/v1/auth/*Mixed (see below)
Mediations/v1/mediations/*requireAuth reads, requireManage writes
AgentsGET /v1/agentsrequireAuth
Messages / queue/v1/messages, /v1/queue/statusrequireAuth
Resolutions/v1/resolutions/*requireAuth reads, requireManage re-resolve
Policies/v1/policies/*requireAuth reads, requireManage writes
API keys/v1/api-keys/*requireAuth reads, requireManage writes
Webhooks/v1/webhooks/*requireAuth reads, requireManage writes
Settings/v1/mediator, /v1/team, /v1/notifications, /v1/billingrequireAuth reads, requireManage writes
Delivery logGET /v1/delivery-logrequireAuth

The DIDComm wire

OOB invitation

Unauthenticated bootstrap document for external agents wanting to establish mediation.

curl https://relay.solidus.network/invite
// Response 200 — measured live { "type": "https://didcomm.org/out-of-band/2.0/invitation", "id": "relay-invite-23c69ca7", "from": "did:key:z6LSsMkRXWM9YLF3f15cEvBtjWneDrbLjto2S53mDJqy8aT8", "body": { "goal_code": "request-mediate", "goal": "Coordinate mediation with the Solidus relay", "accept": ["didcomm/v2"] }, "services": [ { "id": "#relay", "type": "DIDCommMessaging", "serviceEndpoint": { "uri": "https://relay.solidus.network/didcomm", "accept": ["didcomm/v2"], "routingKeys": ["did:key:z6LSsMkRXWM9YLF3f15cEvBtjWneDrbLjto2S53mDJqy8aT8"] } } ] }

POST /didcomm

The one machine endpoint. Accepts a raw DIDComm encrypted envelope (Content-Type: application/didcomm-encrypted+json, body unparsed — the mediator engine unwraps the exact bytes). Dispatches by the inner message type:

  • https://didcomm.org/routing/2.0/forward — queue the message for an offline recipient. No DIDComm reply; the HTTP response is just a status code.
  • https://didcomm.org/coordinate-mediation/2.0/* — mediation request/grant/deny/keylist-update. Requires an identifiable sender.
  • https://didcomm.org/messagepickup/3.0/* — pick up queued messages. Requires an identifiable recipient.

Any other message type returns 400. A GET /didcomm returns 404 — this is correct; the wire is POST-only.

curl -X POST https://relay.solidus.network/didcomm \ -H "Content-Type: application/didcomm-encrypted+json" \ --data-binary @envelope.json

The response, when there is one, is itself a packed DIDComm envelope (application/didcomm-encrypted+json) — authcrypt back to the sender when the mediator can resolve them, anoncrypt otherwise.

Per-DID rate limiting

Every envelope with an identifiable sender DID is checked against a per-tenant rate_limit_per_did_per_min before dispatch. Over the limit returns 429.


Operator session

curl -X POST https://relay.solidus.network/v1/auth/session \ -H "content-type: application/json" \ -d '{"id_token": "<verified auth.solidus.network id_token>"}'
// Response 200 { "token": "eyJhbGciOiJIUzI1NiIs...", "expiresIn": "12h", "operator": { "id": "...", "did": "did:solidus:testnet:...", "email": "[email protected]", "role": "admin" } }
curl https://relay.solidus.network/v1/auth/me \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
// Response 200 { "authenticated": true, "tenantId": "...", "operator": { "id": "...", "did": "did:solidus:testnet:...", "email": "[email protected]", "role": "admin", "plan": "..." }, "via": "session" }

POST /v1/auth/logout is stateless — the console just discards the token; there is nothing server-side to revoke for a short-lived JWT in v0.


Mediations

curl https://relay.solidus.network/v1/mediations \ -H "Authorization: Bearer slk_..."
// Response 200 — shape [ { "id": "...", "recipientDid": "did:solidus:testnet:...", "state": "granted", "gate": { "policy": "kyc-gate", "state": "pass" }, "keylistSize": 2, "queued": 0, "delivered24h": 5, "grantedAgo": "3h ago", "protocols": ["forward", "pickup"] } ]

state is one of requested, granted, denied, revoked. Grant/deny/revoke are all requireManage:

curl -X POST https://relay.solidus.network/v1/mediations/<id>/grant -H "Authorization: Bearer slk_..." curl -X POST https://relay.solidus.network/v1/mediations/<id>/deny -H "Authorization: Bearer slk_..." \ -H "content-type: application/json" -d '{"reason": "credential check failed"}' curl -X POST https://relay.solidus.network/v1/mediations/<id>/revoke -H "Authorization: Bearer slk_..."

GET /v1/mediations/:id/keylist lists the routing keys registered for a mediation; DELETE /v1/mediations/:id/keylist/:key removes one (requireManage).


Agents

Read-only composed view — no write endpoints.

curl https://relay.solidus.network/v1/agents -H "Authorization: Bearer slk_..."
// Response 200 — shape [ { "did": "did:solidus:testnet:...", "presence": "offline", "mediationState": "granted", "queued": 0, "ratePerMin": 0, "rateLimit": 60, "lastSeenAgo": "3h ago" } ]

presence is always "offline" today — v0 has no WebSocket presence channel, so this is an honest static value, not a bug.


Messages and queue status

curl "https://relay.solidus.network/v1/messages?state=queued&limit=50" -H "Authorization: Bearer slk_..."

Query params: state (queued, live-delivered, delivered, acknowledged, expired, failed), protocol (forward, pickup, coordinate-mediation), recipient, limit, offset.

curl https://relay.solidus.network/v1/queue/status -H "Authorization: Bearer slk_..."

Resolutions — chain-anchored routing visibility

Resolves a DID’s mediator/service endpoints from the on-chain DID document — this is the “chain-anchored routing” the product’s wedge is built on, not an off-chain registry lookup.

curl https://relay.solidus.network/v1/resolutions/did:solidus:testnet:... -H "Authorization: Bearer slk_..."

GET /v1/resolutions resolves every currently-granted mediation’s recipient DID for the tenant. POST /v1/resolutions/:did/re-resolve forces a fresh on-chain read (requireManage), bypassing the resolver’s own cache.


Policies — credential-gated delivery

curl -X POST https://relay.solidus.network/v1/policies \ -H "Authorization: Bearer slk_..." -H "content-type: application/json" \ -d '{"name":"KYC gate","credentialType":"KycLevel1Credential","disclosure":"selective (BBS+)","appliesTo":"mediation"}'

disclosure is full or selective (BBS+); appliesTo is mediation, delivery, or both.

curl -X POST https://relay.solidus.network/v1/policies/test \ -H "Authorization: Bearer slk_..." -H "content-type: application/json" \ -d '{"policyId":"...","credentialRef":"urn:solidus:credential:..."}'

POST /v1/policies/test runs the same gate check the live mediate/delivery path uses — there is no separate simulated implementation to drift from production behavior.

GET/PUT /v1/policies/rate-limit and GET/PUT /v1/policies/retention manage the per-DID rate limit and message-retention window.


API keys

curl -X POST https://relay.solidus.network/v1/api-keys \ -H "Authorization: Bearer slk_..." -H "content-type: application/json" \ -d '{"name":"CI integration key","scope":"read"}'

scope is read, manage, or full. DELETE /v1/api-keys/:id revokes a key.


Webhooks

curl -X PUT https://relay.solidus.network/v1/webhooks \ -H "Authorization: Bearer slk_..." -H "content-type: application/json" \ -d '{"url":"https://example.com/hooks/relay","events":["mediation.granted","delivery.failed"]}'

Available events: mediation.granted, mediation.denied, message.queued, message.delivered, delivery.failed, gate.blocked, resolution.failed.

POST /v1/webhooks/test sends a test delivery for one event.


Settings

RoutePurpose
GET /v1/mediatorMediator identity + config
PUT /v1/mediator/configUpdate endpoint, retention, purge-on-ack, rate limit (requireManage)
GET /v1/mediator/throughput?window=1h|6h|24h|7dThroughput chart data
GET /v1/teamTeam members
POST /v1/teamNot available in v0 — returns 501 { "error": "operator invite is not available in v0" }
GET/PUT /v1/notificationsNotification channels (Email, Webhook, Slack, PagerDuty)
GET /v1/billingBilling/usage summary

Delivery log

curl "https://relay.solidus.network/v1/delivery-log?recipient=did:solidus:testnet:...&limit=50" \ -H "Authorization: Bearer slk_..."
// Response 200 — shape [{ "id": "...", "at": "2026-07-29T11:00:00.000Z", "level": "info", "message": "message delivered" }]

Health

curl https://relay.solidus.network/v1/health
// Response 200 — measured live { "status": "ok", "service": "relay.solidus.network", "version": "0.1.0", "network": "testnet", "time": "2026-07-29T11:23:56.521Z", "checks": { "postgres": { "ok": true, "ms": 5 }, "redis": { "ok": true, "ms": 3 }, "solidusRpc": { "ok": true, "ms": 7 } } }

Error format

RFC 7807 Problem Details, the same shape used across every Solidus backend:

{ "type": "https://solidus.network/errors/validation", "title": "Validation Error", "status": 400, "detail": "name is required", "instance": "/v1/policies" }

Vocabulary

Relay is a DIDComm mediator: mediation, message pickup/delivery, keylist, forward/routing, encrypted (JWE) envelopes, DID-to-DID messaging, chain-anchored routing, credential-gated delivery. It is not a gas-sponsorship, paymaster, or meta-transaction relayer — that was a different, archived product that previously occupied this subdomain — and it is not itself a KYC, wallet, payment, or agent product.

Last updated on