Every endpoint is tenant-scoped via the API key
(Authorization: Bearer <key>); the tenant is resolved from the key, never from
the body. The live, always-current contract is the Swagger UI that
palumb-core serves:
| Method | Path | Purpose |
|---|
POST | /v1/events | Fire a trigger: { workflowId, to: { subscriberId } | { topics }, payload? } → 202 |
POST | /v1/events/:runId/signal | Deliver an external event to a waiting step.waitForEvent |
| Method | Path | Purpose |
|---|
PUT | /v1/bridge | Register/update your Bridge URL; returns a generated signingSecret once |
POST | /v1/sync | Discover your workflows from the Bridge + arm crons for schedule-triggered ones |
POST | /v1/digests/:digestId/items | Push an item into a digest: { workflowId, subscriberId, data, windowSeconds } |
| Method | Path | Purpose |
|---|
GET | /v1/channels/catalog | List available channels |
GET | /v1/channels | List enabled channels (safe view — no secrets) |
POST | /v1/channels | Enable + configure (idempotent on key) |
PATCH | /v1/channels/:key | Replace config / toggle enabled |
DELETE | /v1/channels/:key | Remove |
| Method | Path | Purpose |
|---|
PUT | /v1/subscribers/:externalId | Identify / upsert a subscriber |
DELETE | /v1/subscribers/:externalId | Delete a subscriber |
PUT | /v1/subscribers/:externalId/subscriptions/:topicKey | Subscribe |
DELETE | /v1/subscribers/:externalId/subscriptions/:topicKey | Unsubscribe |
PUT | /v1/topics/:key | Create / update a topic |
DELETE | /v1/topics/:key | Delete a topic |
| Method | Path | Purpose |
|---|
POST | /v1/deliver | Deliver a rendered message. Internal: palumb’s engine calls it when your workflow declares a step.send — it is not part of the tenant API. |
| Method | Path | Purpose |
|---|
GET | /health/live | Liveness |
GET | /health/ready | Readiness |
GET | /health/deep | Deep dependency check |
Errors follow a naming-to-status convention: not-found → 404,
already-exists/constraint → 409, permissions → 403, other domain validation
→ 422, infrastructure → 500.