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.networkOIDCid_tokenfor a relay session JWT viaPOST /v1/auth/session, then sends it asAuthorization: Bearer <session-jwt>. Full access. - API key — a revocable key,
Authorization: Bearer slk_..., scoped toread,manage, orfull. Created via the operator console (there is no self-serve public registration route — operator accounts are provisioned through the OIDC handoff, not aPOST /v1/operatorscall).
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 group | Path | Auth |
|---|---|---|
| DIDComm wire | POST /didcomm | None (JWE envelope) |
| OOB invitation | GET /invite | None (public bootstrap) |
| Health | GET /health, GET /v1/health | None |
| Operator session | /v1/auth/* | Mixed (see below) |
| Mediations | /v1/mediations/* | requireAuth reads, requireManage writes |
| Agents | GET /v1/agents | requireAuth |
| Messages / queue | /v1/messages, /v1/queue/status | requireAuth |
| 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/billing | requireAuth reads, requireManage writes |
| Delivery log | GET /v1/delivery-log | requireAuth |
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.jsonThe 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
| Route | Purpose |
|---|---|
GET /v1/mediator | Mediator identity + config |
PUT /v1/mediator/config | Update endpoint, retention, purge-on-ack, rate limit (requireManage) |
GET /v1/mediator/throughput?window=1h|6h|24h|7d | Throughput chart data |
GET /v1/team | Team members |
POST /v1/team | Not available in v0 — returns 501 { "error": "operator invite is not available in v0" } |
GET/PUT /v1/notifications | Notification channels (Email, Webhook, Slack, PagerDuty) |
GET /v1/billing | Billing/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.