API reference
The cronpad REST API lives under /api/v1 on the same host as the dashboard. It speaks JSON, authenticates with a workspace API key in the Authorization header, returns errors in one consistent shape, and uses cursor pagination. The dashboard uses exactly this API, so anything you can do in the UI you can do with it. A machine-readable OpenAPI 3.1 spec is served at /api/v1/openapi.yaml.
| Section | Endpoints |
|---|---|
| Jobs | Create, list, get, update, delete, pause, resume, enable, trigger, test, rotate signing secret |
| Runs | List runs, get a run with attempts, retry a failed run |
| Schedules | Validate and preview schedules (no auth required) |
| Workspace | API keys, secrets, alert channels and rules, stats, audit log, health |
Server features
GET /api/v1/features needs no authentication and returns what this server offers; the dashboard uses it to decide which sign-in buttons and sink types to show. It never includes secrets or client IDs, and clients may cache it for a minute.
{
"public_url": "https://cronpad.example.com",
"auth": {"providers": ["github", "google"], "dev_login": false},
"sinks": {"webhook": true, "command": false}
}
Base URL
https://app.cronpad.com/api/v1 # cronpad Cloud
https://<your public_url>/api/v1 # self-hosted
Authentication
Send an API key as a bearer token:
curl https://app.cronpad.com/api/v1/jobs -H "Authorization: Bearer cp_ab12cd34_…"
- Create keys under Settings → API keys or with
POST /api/v1/api-keys. The full key is shown once; cronpad stores only a hash. - A key belongs to one workspace and can only see that workspace.
- Scopes:
*(default, full access),jobs:read,jobs:write,runs:read. A request outside the key’s scopes gets403 forbidden.
The dashboard authenticates with a session cookie instead, and sends the active workspace in a Cronpad-Workspace header. Cookie-authenticated requests that change data also need the X-CSRF-Token header; API-key requests don’t.
Conventions
- Times are RFC 3339 in UTC (
2026-11-10T03:30:00Z). - Durations are Go-style strings:
"30s","10m","1h30m". - IDs are prefixed ULIDs:
job_…,run_…,ws_…,key_…,ch_…,rule_…. They sort by creation time. - Request bodies are limited to 1 MiB.
- Every response has an
X-Request-IDheader (echoed if you send one). Include it when reporting problems.
Errors
{
"error": {
"code": "validation_failed",
"message": "schedule: unknown word \"tuesdy\"",
"fields": {"schedule.expr": "unknown word \"tuesdy\" at position 6; did you mean \"tuesday\"?"}
}
}
| HTTP | code | Meaning |
|---|---|---|
| 400 | validation_failed | The request is malformed; fields says which inputs and why. |
| 401 | unauthorized | Missing, invalid or revoked API key or session. |
| 403 | forbidden | Authenticated, but the key’s scopes or workspace membership don’t allow it. |
| 403 | limit_exceeded | A plan limit was reached; limit and value say which. |
| 404 | not_found | Doesn’t exist, or belongs to another workspace. |
| 409 | conflict | State conflict, e.g. enabling a job that isn’t disabled. |
| 409 | idempotency_conflict | Idempotency-Key reused with a different body. |
| 429 | rate_limited | Too many requests; wait for Retry-After. |
| 500 | internal | A bug. Report it with the request ID. |
| 503 | unavailable | The server is starting or a dependency is down; retry. |
Pagination
List endpoints return a page plus a cursor:
{"data": [ … ], "next_cursor": "eyJpZCI6InJ1bl8wMUo5…"}
Pass ?cursor=<next_cursor> for the next page and ?limit= to set the page size (default 50, max 200). next_cursor is null on the last page.
Idempotent creates
POST endpoints that create things accept an Idempotency-Key header. Repeating the same request with the same key within 24 hours returns the original response instead of creating a duplicate. Reusing the key with a different body returns 409 idempotency_conflict.
Rate limits
| Caller | Default limit |
|---|---|
| API key or session | 600 requests per minute (limits.api_rate_limit_per_minute) |
| Unauthenticated schedule endpoints | 60 requests per minute per IP |
Responses include RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers.