API Reference
Full reference for the Verne Relay HTTP API. All endpoints are served from https://api.vernesoft.com.
Examples are shown as raw curl requests and as calls through the official SDKs — see Installation to set one up. SDK snippets assume an initialized verne client.
1. Authentication
Every request must include a Bearer token in the Authorization header. A key is vrn_ followed by random characters; it does not name an API. The Relay API needs a key with the relay scope (shown as Events in the dashboard), chosen when the key is created. A key without it is refused with 403 Forbidden and the code forbidden (This API key is not authorized for Relay.). Keys created before scopes existed and named vrn_relay_... still work and open the Relay API only. There is no sandbox mode: every key delivers events for real. See API Keys.
Example header:
Authorization: Bearer vrn_9f8a7...
Keys are scoped to a single tenant, and you create and revoke them on the Dashboard → Keys page.
2. Send Event
POST /v1/relay/messages
Publishes a new event to all endpoints subscribed to the given event_type.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
event_type | string | Yes | Dot-notated event name (e.g. user.created). |
payload | object | Yes | Arbitrary JSON payload delivered to subscribers. |
idempotency_key | string | No | Sending the same key again within 12 hours returns the original message instead of creating a second one. |
channels | string[] | No | Restrict delivery to endpoints listening on these channels. |
Example Request
curl -X POST https://api.vernesoft.com/v1/relay/messages \
-H 'Authorization: Bearer vrn_9f8a7...' \
-H 'Content-Type: application/json' \
-d '{
"event_type": "user.created",
"payload": { "id": "123" },
"idempotency_key": "evt_abc",
"channels": ["team-a"]
}'
Response
{
"id": "msg_2hV9kLmNpQ",
"event_type": "user.created",
"status": "accepted",
"timestamp": "2026-03-17T12:00:00Z"
}
Retries are safe. A second send with an idempotency_key that was already
accepted does not create a second event and does not fail — it returns 202
with the originally accepted message, same id and same timestamp. A client
that retries a request whose response it never saw ends up in the right state
without having to tell a duplicate apart from a success. The window is 12 hours, counted from the first request that used the key; repeating the call does not extend it. This deduplicates the send, not the delivery: delivery is at-least-once, so the receiving endpoint should still deduplicate on svix-id.
| Status Code | Meaning |
|---|---|
202 Accepted | Event queued for delivery — or already was, if you sent this idempotency_key before. |
400 Bad Request | Invalid payload or missing required fields. |
401 Unauthorized | Missing or invalid Bearer token. |
402 Payment Required | The account is suspended for billing, or a renewal is unpaid. Retrying does not help — settle the balance. |
429 Too Many Requests | RELAY_MESSAGE_LIMIT_REACHED: your plan's monthly Relay message limit is reached. Retry after the Retry-After header — the first of next month — or move to a higher plan. |
Monthly message limit
Every plan includes a number of Relay messages a month, counted when a send is accepted: Free 10,000, Pro 1,000,000, Advanced 5,000,000 (a Contract plan has none). Reading or listing messages is not counted, and there is no monthly quota of API requests. At the limit a new send is refused until the next calendar month (UTC), with Retry-After set to the seconds until it starts:
HTTP/1.1 429 Too Many Requests
Retry-After: 1209600
{
"error": {
"code": "RELAY_MESSAGE_LIMIT_REACHED",
"message": "You have sent the 10000 messages the Free plan includes this month. New messages are accepted again when the month ends (see Retry-After) or move to Pro for a higher limit.",
"request_id": "req_…",
"plan": "free",
"limit": 10000,
"current": 10000
}
}
Moving to a higher plan lifts it at once. You are emailed at 80 % and at 100 % of the limit. The delivery log is kept for 7 days on Free, 90 on Pro and 180 on Advanced. The full list of limits and refusals is on Plans, Limits and Refusals.
3. List Events
GET /v1/relay/messages
Returns a paginated list of previously sent events for the current tenant.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 20 | Items per page. Values above 100 are clamped to 100 rather than rejected. |
cursor | string | — | Pagination cursor from a previous response. |
event_type | string | — | Filter by event type. |
Example Request
curl 'https://api.vernesoft.com/v1/relay/messages?limit=5&event_type=user.created' \
-H 'Authorization: Bearer vrn_9f8a7...'
Response
{
"data": [
{
"id": "msg_2hV9kLmNpQ",
"event_type": "user.created",
"status": "accepted",
"timestamp": "2026-03-17T12:00:00Z"
}
],
"has_more": false,
"next_cursor": null
}
status is always accepted — it records that Relay took the event, not what
each subscriber endpoint did with it afterwards. Per-endpoint delivery state
lives in the Console under Dashboard → Events.
next_cursor is null on the last page, so paginate until has_more is
false rather than until the list comes back empty.
Error Format
All errors follow a consistent structure. The SDKs surface these fields as a typed error:
{
"error": {
"code": "invalid_payload",
"message": "Field 'event_type' is required.",
"request_id": "req_abc123"
}
}
Include the request_id when contacting support for faster resolution.