Skip to content

API conventions

These rules apply to every endpoint under /v1. Read this once and the per-endpoint reference becomes mechanical. For the full interactive request and response schemas, see the API reference.

All requests go to:

https://api.schedstack.com/v1

There is no separate test host. Test and live are selected by your API key, not by the URL — see Modes.

Send your API key as a bearer token on every request:

Terminal window
curl https://api.schedstack.com/v1/schedules \
-H "Authorization: Bearer sk_test_…"

Keys are prefixed by mode — sk_test_… or sk_live_… — and each key is scoped to a single project and mode. Every read and write is automatically confined to that (project, mode) pair; you cannot reach another project’s data, and a test key never sees live data or vice versa.

A missing, malformed, or revoked key returns 401 with type: authentication_error. A missing key uses code: missing_api_key (message Provide an API key via Authorization: Bearer <key>.); an invalid or revoked key uses code: invalid_api_key:

{
"error": {
"type": "authentication_error",
"code": "invalid_api_key",
"message": "The API key is invalid or has been revoked.",
"request_id": "req_…"
}
}

The major version lives in the path: /v1. That prefix is the contract. We ship backward-compatible additions continuously without a version bump — so write your client to tolerate new fields:

  • New response fields, new enum values, and new endpoints can appear at any time.
  • Existing fields keep their name, type, and meaning.
  • A breaking change, if one ever happens, ships under a new path prefix (/v2); /v1 keeps working.

Pin to /v1 and ignore unknown JSON keys. That is the entire versioning story you need.

Every response — success or error — carries a Sched-Request-Id header:

Sched-Request-Id: req_…

On an error, the same id is also echoed inside the body as error.request_id. Log it. It is the fastest way for you or for us to trace a single call end to end.

List endpoints (GET /v1/schedules, /v1/deliveries, /v1/endpoints, and the nested .../deliveries and .../attempts collections) return a cursor-paginated envelope:

{
"object": "list",
"data": [ { "...": "..." } ],
"has_more": true,
"next_cursor": "eyJ…"
}
Field Meaning
object Always "list" for a collection response.
data The page of objects, newest first.
has_more true if more pages remain after this one.
next_cursor Opaque cursor for the next page. null on the last page.

Control the page with two query parameters:

Parameter Default Notes
limit 20 Number of objects per page.
cursor The next_cursor from the previous response.

Treat next_cursor as opaque — do not parse or construct it. Walk pages until has_more is false:

Terminal window
cursor=""
while : ; do
resp=$(curl -s "https://api.schedstack.com/v1/deliveries?limit=50&cursor=$cursor" \
-H "Authorization: Bearer sk_test_…")
echo "$resp" | jq '.data[]'
[ "$(echo "$resp" | jq -r '.has_more')" = "true" ] || break
cursor=$(echo "$resp" | jq -r '.next_cursor')
done

Every error — at any status code — uses the same typed envelope:

{
"error": {
"type": "invalid_request_error",
"code": "url_blocked",
"message": "The destination resolves to a blocked address.",
"param": "endpoint",
"request_id": "req_…"
}
}
Field Always present Meaning
type yes Broad category — branch on this. See the enum below.
code yes Stable machine-readable specifier (e.g. url_blocked, invalid_cron).
message yes Human-readable explanation. Display it; don’t match on it.
param when applicable The request field that caused the error.
request_id yes Same value as the Sched-Request-Id header.

type is one of:

type Typical status Meaning
invalid_request_error 400 / 422 Malformed or invalid input — the request as sent will never succeed unchanged.
authentication_error 401 Missing, malformed, or revoked API key.
rate_limit_error 429 Too many requests — back off and retry.
idempotency_error 409 An Idempotency-Key conflict (see below).
not_found_error 404 No such object in this (project, mode).
api_error 5xx Something failed on our side — safe to retry.

Send an Idempotency-Key header on any mutating call (such as POST /v1/schedules) to make a network retry safe — a replayed request returns the original result instead of creating a second schedule. Keys are stored for 24 hours, scoped to (project, mode), and matched against the method, path, and body.

This is one of two distinct mechanisms named Idempotency-Key in SchedStack (the other is the dedup key on deliveries to your endpoint). The full rules — fingerprinting, conflict codes, and how to dedup on the receiving side — are in Idempotency.

Limit Value Applies to
Request body ≤ 1 MB The JSON you POST/PATCH to the API.
Delivery body ≤ 256 KB (262144 bytes) The body SchedStack sends to your endpoint.
Minimum delay ~1 second The soonest a schedule can fire; a smaller delay is rejected.

The headers you attach to a delivery have no separate cap — they count toward the 1 MB request body above.

Exceed a limit and you get an invalid_request_error naming the offending param. The minimum delay reflects the dispatch model: SchedStack publishes a 100–300 ms p99 dispatch-initiation SLO, not sub-second precision — see Modes for what “dispatch-initiation” measures.