Subscriptions & Actions
Subscription endpoints manage event-driven actions — creating, pausing, and deleting subscriptions. Action endpoints provide observability into delivery runs, attempts, and terminal-failure notifications.
All paths are prefixed with /api/repos/:orgName/:repoName.
For conceptual background, see Subscriptions. For CLI and MCP interfaces, see Creating Subscriptions and Managing Subscriptions.
Subscription Endpoints
Section titled “Subscription Endpoints”GET /subs
Section titled “GET /subs”List all subscriptions in a repository.
Auth: None (public repos). Private repos require authentication.
Response 200
Section titled “Response 200”[ { "name": "signal-hook", "kind": "webhook", "active": true, "shapeName": "Signal", "webhookUrl": "https://example.com/hook" }, { "name": "daily-sync", "kind": "cron", "active": true, "executorKind": "sprite", "cronConfig": { "cronspec": "0 8 * * *", "timezone": "America/New_York" } }, { "name": "cross-repo-hook", "kind": "webhook", "active": true, "shapeName": "Signal", "webhookUrl": "https://example.com/hook", "sourceRepo": "myorg/other-repo" }]Cross-repo subscriptions include a sourceRepo string identifying the repository being watched (formatted as org/repo), when the caller has read access to the source repository. Subscriptions without sourceRepo either watch their own repository or the caller lacks access to the source repository metadata.
Example
Section titled “Example”curl "https://api.warmhub.ai/api/repos/myorg/myrepo/subs"GET /subs/:name
Section titled “GET /subs/:name”Get a single subscription by name.
Auth: None (public repos). Private repos require authentication.
Path Parameters
Section titled “Path Parameters”| Parameter | Type | Description |
|---|---|---|
name | string | Subscription name (URL-encoded) |
Response 200
Section titled “Response 200”{ "name": "signal-hook", "kind": "webhook", "active": true, "shapeName": "Signal", "webhookUrl": "https://example.com/hook", "executorKind": "webhook", "createdAt": 1741132800000}For subscriptions executed by sprite (including cron subscriptions with executorKind: "sprite"), the response also includes actionContainer (the derived container name), actionContainerConfig (the raw container configuration with command, githubRepo, ref, inputMode), and tokenScopes when sprite-minted WarmHub tokens are scoped:
{ "name": "sprite-hook", "kind": "sprite", "active": true, "shapeName": "Signal", "executorKind": "sprite", "actionContainer": "wh-warmdex-action-test", "actionContainerConfig": { "command": "bun run action", "githubRepo": "warmdex/action-test", "ref": "main", "inputMode": "stdin" }, "tokenScopes": [ { "permissions": ["repo:read"], "allowedMatches": ["Signal/*"] } ], "createdAt": 1741132800000}Cross-repo subscriptions include a sourceRepo string identifying the repository being watched (formatted as org/repo), when the caller has read access to the source repository:
{ "name": "cross-repo-hook", "kind": "webhook", "active": true, "shapeName": "Signal", "webhookUrl": "https://example.com/hook", "executorKind": "webhook", "sourceRepo": "myorg/other-repo", "createdAt": 1741132800000}Errors
Section titled “Errors”| Code | Status | Description |
|---|---|---|
NOT_FOUND | 404 | Subscription not found |
Example
Section titled “Example”curl "https://api.warmhub.ai/api/repos/myorg/myrepo/subs/signal-hook"POST /subs
Section titled “POST /subs”Create a new subscription. The request body varies by kind — see Creating Subscriptions for full configuration details.
Auth: Required, repo:write scope.
Webhook
Section titled “Webhook”| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Subscription name |
kind | string | Yes | "webhook" |
shapeName | string | Yes | Shape to subscribe to (also accepts on) |
filterJson | object | Yes | Filter for matching operations (also accepts filter) |
webhookUrl | string | Yes | Destination URL (also accepts url) |
Sprite
Section titled “Sprite”| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Subscription name |
kind | string | Yes | "sprite" |
shapeName | string | Yes | Shape to subscribe to |
filterJson | object | Yes | Filter for matching operations |
spriteConfig | object | Yes | Sprite execution configuration |
tokenScopes | array | No | Optional scoped WarmHub token permissions for sprite runs. Must be a non-empty array when provided |
The spriteConfig object:
| Field | Type | Required | Description |
|---|---|---|---|
command | string | Yes | Shell command to execute |
inputMode | string | Yes | "stdin", "json-file", or "env" |
githubRepo | string | No | GitHub repo to clone (owner/repo or "none") |
ref | string | If cloning | Git ref to checkout |
spriteName | string | No | Custom execution environment name |
inputPath | string | No | File path for json-file input mode |
spritePool | string | No | Repo-scoped execution pool name |
tokenTtlMinutes | number | No | WarmHub token TTL in minutes (default 10, range 5–60) |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Subscription name |
kind | string | Yes | "cron" |
executorKind | string | Yes | "webhook" or "sprite" (also accepts executor) |
cronConfig | object | Yes | Cron schedule configuration |
webhookUrl | string | If webhook | Destination URL |
spriteConfig | object | If sprite | Sprite execution configuration |
tokenScopes | array | No | Optional scoped WarmHub token permissions for sprite runs. Must be a non-empty array when provided |
The cronConfig object:
| Field | Type | Required | Description |
|---|---|---|---|
cronspec | string | Yes | Cron expression (5-field format, minimum 5-minute interval) |
timezone | string | No | IANA timezone (default UTC) |
Response 201
Section titled “Response 201”Returns the created subscription object.
When tokenScopes is provided:
- it must contain at least one entry
- each entry must include a non-empty
permissionsarray - a permission may appear in only one entry across the full array
allowedMatches, when provided, must be an array of valid non-empty glob strings
Errors
Section titled “Errors”| Code | Status | Description |
|---|---|---|
ALREADY_EXISTS | 409 | A subscription with this name already exists |
VALIDATION_ERROR | 400 | Invalid configuration, missing required fields, or invalid cronspec |
NOT_FOUND | 404 | Shape not found |
RATE_LIMITED | 429 | Too many subscription creation requests. Honor Retry-After before retrying. |
Example
Section titled “Example”# Create a webhook subscriptioncurl -X POST https://api.warmhub.ai/api/repos/myorg/myrepo/subs \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "name": "signal-hook", "kind": "webhook", "shapeName": "Signal", "filterJson": { "shape": "Signal" }, "webhookUrl": "https://example.com/hook" }'# Create a sprite subscription with scoped WarmHub token permissionscurl -X POST https://api.warmhub.ai/api/repos/myorg/myrepo/subs \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "name": "signal-sprite", "kind": "sprite", "shapeName": "Signal", "filterJson": { "shape": "Signal" }, "spriteConfig": { "command": "bun run action", "inputMode": "stdin" }, "tokenScopes": [ { "permissions": ["repo:read"], "allowedMatches": ["Signal/*"] } ] }'PATCH /subs/:name
Section titled “PATCH /subs/:name”Update an existing subscription. Provide only the fields you want to change. For nested spriteConfig and cronConfig, provided keys are merged into the existing config.
Auth: Required, repo:write scope.
Path Parameters
Section titled “Path Parameters”| Parameter | Type | Description |
|---|---|---|
name | string | Subscription name (URL-encoded) |
Body Fields
Section titled “Body Fields”| Field | Type | Required | Description |
|---|---|---|---|
shapeName | string | No | Replacement shape name (also accepts on) |
filterJson | object | No | Replacement filter object (also accepts filter) |
webhookUrl | string | No | Replacement webhook URL (also accepts url) |
spriteConfig | object | No | Sprite config patch |
cronConfig | object | No | Cron config patch |
executorKind | string | No | Replacement cron executor kind (also accepts executor) |
allowTraceReentry | boolean | No | Replacement same-trace reentry policy |
workspacePolicy | string | No | Replacement workspace commit policy: "ignore" or "include" |
Response 200
Section titled “Response 200”Returns the updated subscription object.
Example
Section titled “Example”curl -X PATCH https://api.warmhub.ai/api/repos/myorg/myrepo/subs/signal-hook \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "webhookUrl": "https://example.com/new-hook", "workspacePolicy": "include" }'POST /subs/:name/pause
Section titled “POST /subs/:name/pause”Pause a subscription. No new deliveries are created while paused. For cron subscriptions, the cron job is deregistered.
Auth: Required, repo:write scope.
Path Parameters
Section titled “Path Parameters”| Parameter | Type | Description |
|---|---|---|
name | string | Subscription name (URL-encoded) |
Response 200
Section titled “Response 200”{ "ok": true}Example
Section titled “Example”curl -X POST https://api.warmhub.ai/api/repos/myorg/myrepo/subs/signal-hook/pause \ -H "Authorization: Bearer <token>"POST /subs/:name/resume
Section titled “POST /subs/:name/resume”Resume a paused subscription.
Auth: Required, repo:write scope.
Path Parameters
Section titled “Path Parameters”| Parameter | Type | Description |
|---|---|---|
name | string | Subscription name (URL-encoded) |
Response 200
Section titled “Response 200”{ "ok": true}Example
Section titled “Example”curl -X POST https://api.warmhub.ai/api/repos/myorg/myrepo/subs/signal-hook/resume \ -H "Authorization: Bearer <token>"POST /subs/:name/bind
Section titled “POST /subs/:name/bind”Bind a credential set to a subscription for authenticated webhook delivery.
Auth: Required, repo:write scope.
Path Parameters
Section titled “Path Parameters”| Parameter | Type | Description |
|---|---|---|
name | string | Subscription name (URL-encoded) |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
credentialSetName | string | Yes | Name of the credential set to bind |
Response 200
Section titled “Response 200”{ "bound": true, "subscriptionName": "signal-hook", "credentialSetName": "webhook-keys"}Example
Section titled “Example”curl -X POST https://api.warmhub.ai/api/repos/myorg/myrepo/subs/signal-hook/bind \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"credentialSetName": "webhook-keys"}'DELETE /subs/:name/bind
Section titled “DELETE /subs/:name/bind”Unbind a credential set from a subscription.
Auth: Required, repo:write scope.
Path Parameters
Section titled “Path Parameters”| Parameter | Type | Description |
|---|---|---|
name | string | Subscription name (URL-encoded) |
Response 200
Section titled “Response 200”{ "unbound": true, "subscriptionName": "signal-hook"}Example
Section titled “Example”curl -X DELETE https://api.warmhub.ai/api/repos/myorg/myrepo/subs/signal-hook/bind \ -H "Authorization: Bearer <token>"DELETE /subs/:name
Section titled “DELETE /subs/:name”Delete a subscription. Removes the subscription, its credential bindings, and any associated cron schedule.
Auth: Required, repo:write scope.
Path Parameters
Section titled “Path Parameters”| Parameter | Type | Description |
|---|---|---|
name | string | Subscription name (URL-encoded) |
Response 200
Section titled “Response 200”{ "ok": true}Example
Section titled “Example”curl -X DELETE https://api.warmhub.ai/api/repos/myorg/myrepo/subs/signal-hook \ -H "Authorization: Bearer <token>"Action Endpoints
Section titled “Action Endpoints”Action endpoints provide observability into subscription delivery history. See Managing Subscriptions for the full observability surface including CLI and MCP access.
GET /actions/runs
Section titled “GET /actions/runs”List action runs for a repository, optionally filtered by status.
Auth: None (public repos). Private repos require authentication.
Query Parameters
Section titled “Query Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | No | Filter by status: pending, running, retry_wait, succeeded, failed_terminal, dead_letter |
since | integer | No | Runs created after this timestamp (epoch ms) |
limit | integer | No | Maximum runs to return |
Response 200
Section titled “Response 200”[ { "subscriptionName": "signal-hook", "commitId": "a1b2c3d4e5f6a7b8", "status": "succeeded", "executorKind": "webhook", "matchedOperationIndexes": [0, 1], "attemptCount": 1, "maxAttempts": 5, "createdAt": 1741132800000, "updatedAt": 1741132801000 }]Failed runs also include lastErrorCode and lastErrorMessage fields.
Example
Section titled “Example”# All failed runscurl "https://api.warmhub.ai/api/repos/myorg/myrepo/actions/runs?status=failed_terminal"
# Recent runscurl "https://api.warmhub.ai/api/repos/myorg/myrepo/actions/runs?limit=20"GET /actions/subs/:subName/commits/:commitId/attempts
Section titled “GET /actions/subs/:subName/commits/:commitId/attempts”Get the attempt history for a specific action run.
Auth: None (public repos). Private repos require authentication.
Path Parameters
Section titled “Path Parameters”| Parameter | Type | Description |
|---|---|---|
subName | string | Subscription name (URL-encoded) |
commitId | string | Commit identifier (16-char hex string) |
Response 200
Section titled “Response 200”[ { "attempt": 1, "status": "failed", "executorKind": "webhook", "startedAt": 1741132800000, "finishedAt": 1741132801000, "httpStatus": 503, "errorCode": "WEBHOOK_NETWORK_ERROR", "errorMessage": "Connection timed out" }, { "attempt": 2, "status": "succeeded", "executorKind": "webhook", "startedAt": 1741132802000, "finishedAt": 1741132803000, "httpStatus": 200 }]Errors
Section titled “Errors”| Code | Status | Description |
|---|---|---|
VALIDATION_ERROR | 400 | Invalid commit identifier |
Example
Section titled “Example”curl "https://api.warmhub.ai/api/repos/myorg/myrepo/actions/subs/signal-hook/commits/a1b2c3d4e5f6a7b8/attempts"GET /actions/notifications
Section titled “GET /actions/notifications”List repo-scoped action failure notification records for a repository.
Auth: None (public repos). Private repos require authentication.
Query Parameters
Section titled “Query Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
since | integer | No | Notifications after this timestamp (epoch ms) |
limit | integer | No | Maximum notifications to return |
Response 200
Section titled “Response 200”[ { "subscriptionName": "signal-hook", "commitId": "a1b2c3d4e5f6a7b8", "attempt": 2, "channel": "inbox", "status": "queued", "errorCode": "WEBHOOK_NETWORK_ERROR", "errorMessage": "Connection timed out", "createdAt": 1741132810000 }]status here is the notification dispatch state for the channel above (queued or delivered), not the action run outcome. This record exists because the action run already ended in a terminal failure state such as failed_terminal or dead_letter.
Example
Section titled “Example”curl "https://api.warmhub.ai/api/repos/myorg/myrepo/actions/notifications?limit=10"