API reference
Human-readable reference for the Outemit REST API. Auth with Bearer sk_test_ or sk_live_. Base URL https://outemit.dev.
Jump to endpoint
Authentication
Authenticate with a Bearer API key. Keys are livemode-scoped: sk_test_... for Test and sk_live_... for Live. Base URL https://outemit.dev.
Authorization: Bearer sk_test_...Common headers
| Header | Required | Description |
|---|---|---|
| Authorization | Yes | Bearer sk_test_... or sk_live_... |
| Content-Type | On JSON bodies | application/json |
| Idempotency-Key | Recommended on emit | Dedupes POST /api/v1/messages |
Idempotency
On create message, pass Idempotency-Key (header preferred). Identical keys within the retention window return the original message and delivery ids instead of creating duplicates.
webhook-id as your idempotency key when applying side effects.Error envelope
Errors use a consistent JSON envelope. Include request_id when contacting support.
400validation / invalid body401missing or invalid API key404resource missing in this environment409conflict (for example duplicate uid)429rate limited5xxtransient; retry emits with Idempotency-Key
{
"error": {
"code": "invalid_request",
"message": "app_id is required",
"request_id": "req_..."
}
}Messages
List messages
/api/v1/messagesReturns up to 50 recent messages for the API key organization (livemode-scoped). Payload bytes live in object storage; this list returns metadata.
Response fields
| Field | Type | Description |
|---|---|---|
| object | string | Always "list" |
| data | Message[] | Recent messages, newest first |
Errors
401·Missing or invalid API key
curl -X GET https://outemit.dev/api/v1/messages \
-H "Authorization: Bearer sk_test_..."{
"object": "list",
"data": [
{
"id": "evt_01HABC",
"event_type": "invoice.paid",
"app_id": "app_01HXYZ",
"created_at": "2026-09-05T12:00:00.000Z"
}
]
}Create a message
/api/v1/messagesAccept a payload for an application, persist the immutable message, and fan out deliveries to subscribed endpoints. Safe to retry with the Idempotency-Key header.
Headers
| Name | Type | Required | Description |
|---|---|---|---|
| Idempotency-Key | string | No | Dedupes identical emit calls within the retention window |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| app_id | string | Yes | Target application (app_...) |
| event_type | string | Yes | Catalog name such as invoice.paid |
| payload | object | Yes | JSON object delivered to receivers |
| idempotency_key | string | No | Optional body alias for Idempotency-Key |
Response fields
| Field | Type | Description |
|---|---|---|
| id | string | Message id (evt_...) |
| event_type | string | |
| app_id | string | |
| delivery_ids | string[] | Deliveries enqueued for this emit |
| idempotent | boolean | True when this was a replay of a prior emit |
Errors
400·Invalid body or unknown event_type401·Missing or invalid API key404·Application not found in this environment429·Rate limited
curl -X POST https://outemit.dev/api/v1/messages \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: demo-1" \
-H "Content-Type: application/json" \
-d '{ "app_id": "app_01HXYZ", "event_type": "invoice.paid", "payload": { "id": "inv_1", "amount": 4900 }}'{
"id": "evt_01HABC",
"event_type": "invoice.paid",
"app_id": "app_01HXYZ",
"delivery_ids": ["dlv_01HDEF"],
"idempotent": false
}Retrieve a message
/api/v1/messages/{id}Fetch message metadata. Payload bytes live in object storage; this returns ids, type, and timestamps.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Message id (evt_...) |
Response fields
| Field | Type | Description |
|---|---|---|
| id | string | |
| event_type | string | |
| app_id | string | |
| created_at | string | ISO-8601 timestamp |
Errors
401·Unauthorized404·Message not found
curl -X GET https://outemit.dev/api/v1/messages/evt_01HABC \
-H "Authorization: Bearer sk_test_..."{
"id": "evt_01HABC",
"event_type": "invoice.paid",
"app_id": "app_01HXYZ",
"created_at": "2026-09-05T12:00:00.000Z"
}Replay a message
/api/v1/messages/{id}/replayRe-run the message through the pipeline for currently subscribed endpoints. Creates NEW delivery rows. Distinct from retry on a delivery.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Message id (evt_...) |
Response fields
| Field | Type | Description |
|---|---|---|
| message_id | string | |
| delivery_ids | string[] | New deliveries created by this replay |
| status | string | Typically "enqueued" |
Errors
401·Unauthorized404·Message not found
curl -X POST https://outemit.dev/api/v1/messages/evt_01HABC/replay \
-H "Authorization: Bearer sk_test_..."{
"message_id": "evt_01HABC",
"delivery_ids": ["dlv_01HNEW"],
"status": "enqueued"
}Send a test message
/api/v1/messages/send-testTest-only helper. Emits a sample event through the real pipeline. Prefer POST /api/v1/messages in production.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| app_id | string | Yes | |
| event_type | string | No | Defaults to invoice.paid |
| payload | object | No |
Errors
400·Invalid body401·Unauthorized404·Application not found
curl -X POST https://outemit.dev/api/v1/messages/send-test \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{ "app_id": "app_01HXYZ", "event_type": "invoice.paid"}'{
"id": "evt_01HTEST",
"object": "message",
"status": "accepted",
"event_type": "invoice.paid",
"app_id": "app_01HXYZ",
"delivery_ids": ["dlv_01H"],
"note": "Sample event accepted into the delivery pipeline."
}Applications
List applications
/api/v1/applicationsList applications (customer tenants) in the API key environment (test or live).
Response fields
| Field | Type | Description |
|---|---|---|
| data | Application[] |
Errors
401·Unauthorized
curl -X GET https://outemit.dev/api/v1/applications \
-H "Authorization: Bearer sk_test_..."{
"data": [
{
"id": "app_01HXYZ",
"name": "Acme",
"uid": "acme",
"created_at": "2026-09-01T00:00:00.000Z"
}
]
}Create an application
/api/v1/applicationsCreate an application for one customer tenant that will receive webhooks.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | |
| uid | string | No | Stable external id for your system |
Response fields
| Field | Type | Description |
|---|---|---|
| id | string | |
| name | string | |
| uid | string | |
| portal_path | string | Short path into the customer portal |
Errors
400·Missing name401·Unauthorized409·uid already exists
curl -X POST https://outemit.dev/api/v1/applications \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{ "name": "Acme", "uid": "acme" }'{
"id": "app_01HXYZ",
"name": "Acme",
"uid": "acme",
"portal_path": "/p/app_01HXYZ"
}Endpoints
List endpoints
/api/v1/endpointsList endpoints for the environment.
Response fields
| Field | Type | Description |
|---|---|---|
| data | Endpoint[] |
Errors
401·Unauthorized
curl -X GET https://outemit.dev/api/v1/endpoints \
-H "Authorization: Bearer sk_test_..."{
"data": [
{
"id": "ep_01H",
"app_id": "app_01HXYZ",
"url": "https://example.com/hooks",
"status": "active",
"event_types": ["invoice.paid"]
}
]
}Create an endpoint
/api/v1/endpointsCreate an endpoint. Live requires HTTPS. Test allows localhost. Response includes signing_secret once (whsec_). Store it immediately.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| app_id | string | Yes | |
| url | string | Yes | Receiver URL |
| description | string | No | |
| event_types | string[] | No | Subscribe to these catalog names |
Response fields
| Field | Type | Description |
|---|---|---|
| id | string | |
| app_id | string | |
| url | string | |
| signing_secret | string | Shown once; store securely |
| status | string | active | disabled | failing |
Errors
400·Invalid URL or missing app_id401·Unauthorized404·Application not found
curl -X POST https://outemit.dev/api/v1/endpoints \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{ "app_id": "app_01HXYZ", "url": "https://example.com/hooks", "event_types": ["invoice.paid", "invoice.failed"]}'{
"id": "ep_01H",
"app_id": "app_01HXYZ",
"url": "https://example.com/hooks",
"signing_secret": "whsec_...",
"status": "active"
}Rotate signing secret
/api/v1/endpoints/{id}/rotate-secretMint a new whsec_. Previous secret remains valid until previous_valid_until (dual-sign grace).
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Endpoint id (ep_...) |
Response fields
| Field | Type | Description |
|---|---|---|
| id | string | |
| signing_secret | string | New secret, shown once |
| previous_valid_until | string | Until when the old secret still verifies |
Errors
401·Unauthorized404·Endpoint not found
curl -X POST https://outemit.dev/api/v1/endpoints/ep_01H/rotate-secret \
-H "Authorization: Bearer sk_test_..."{
"id": "ep_01H",
"signing_secret": "whsec_new...",
"previous_valid_until": "2026-09-06T12:00:00.000Z"
}Deliveries
Retrieve a delivery
/api/v1/deliveries/{id}Returns delivery status and recent attempt summary. Public surface for status checks; retry is a separate POST.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Delivery id (dlv_...) |
Response fields
| Field | Type | Description |
|---|---|---|
| id | string | |
| object | string | Always "delivery" |
| message_id | string | |
| endpoint_id | string | |
| status | string | pending | success | failed | disabled |
| attempt_count | integer | |
| next_attempt_at | string | null |
Errors
401·Unauthorized404·Delivery not found
curl -X GET https://outemit.dev/api/v1/deliveries/dlv_01HDEF \
-H "Authorization: Bearer sk_test_..."{
"id": "dlv_01HDEF",
"object": "delivery",
"message_id": "evt_01HABC",
"endpoint_id": "ep_01H",
"status": "failed",
"attempt_count": 2,
"next_attempt_at": "2026-09-06T12:05:00.000Z"
}Retry a delivery
/api/v1/deliveries/{id}/retryRe-attempt the same delivery to the same endpoint. Adds a new attempt; does not create a new delivery. Distinct from message replay.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Delivery id (dlv_...) |
Response fields
| Field | Type | Description |
|---|---|---|
| delivery_id | string | |
| status | string | |
| attempt_number | integer |
Errors
401·Unauthorized404·Delivery not found
curl -X POST https://outemit.dev/api/v1/deliveries/dlv_01HDEF/retry \
-H "Authorization: Bearer sk_test_..."{
"delivery_id": "dlv_01HDEF",
"status": "enqueued",
"attempt_number": 3
}Event types
List event types
/api/v1/event-typesList the org event catalog used by the dashboard and customer portal.
Response fields
| Field | Type | Description |
|---|---|---|
| data | EventType[] |
Errors
401·Unauthorized
curl -X GET https://outemit.dev/api/v1/event-types \
-H "Authorization: Bearer sk_test_..."{
"data": [
{
"name": "invoice.paid",
"description": "Invoice settled successfully",
"version": 1,
"example_json": { "id": "inv_1", "amount": 4900 }
}
]
}API keys
Create an API key
/api/v1/api-keysCreates an additional key for the same org and livemode as the Bearer key. The full key value is returned once. Use /api/v1/dev/bootstrap for the first key in local/dev.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | No | Label for the key |
Response fields
| Field | Type | Description |
|---|---|---|
| id | string | |
| object | string | Always "api_key" |
| name | string | |
| prefix | string | |
| livemode | boolean | |
| key | string | Plaintext key, shown once |
Errors
401·Unauthorized
curl -X POST https://outemit.dev/api/v1/api-keys \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{ "name": "CI key" }'{
"id": "key_01H",
"object": "api_key",
"name": "CI key",
"prefix": "sk_test_",
"livemode": false,
"key": "sk_test_..."
}Machine-readable contract: /openapi.yaml. Human reference above stays in sync with that spec.