Skip to content

Limits & constraints

Every floor, cap, and validation rule SchedStack enforces — and the typed error you get when you cross one. Each error follows the typed error envelope ({ "error": { "type", "code", "message", "param", "request_id" } }); the code is shown below.

Rule Limit Error
Minimum delay >= 1s 422 sub_floor_delay
delay format valid duration (30s, 5m, 24h, 1h30m) 400 invalid_duration
fire_at must be in the future >= now + 1s 422 fire_at_in_past
fire_at horizon <= now + 10 years 422 fire_at_too_far
fire_at format strict RFC 3339 instant 400 (message)
local_fire_at format wall-clock 2006-01-02T15:04:05 (with timezone) 400 invalid_duration
local_fire_at bounds same future / 10-year rules 422 fire_at_in_past / fire_at_too_far
Exactly one timing source one of delay · fire_at · local_fire_at · cron 422 missing_timing / 400 multiple_timing
cron 5-field standard cron (no seconds field) 422 invalid_cron
timezone a valid IANA name (e.g. America/New_York); default UTC 422 invalid_cron
Rule Limit Behavior
Request body <= 1 MB larger → 400 invalid_json
Delivery body <= 256 KB larger → 422 payload_too_large
Custom delivery headers keep them small no enforced total-size cap today — don’t rely on one

Two header rules make a delivery terminal (dead-lettered, never retried):

  • No control characters. A header name or value containing CR, LF, or other control characters is rejected.
  • No hop-by-hop / reserved names. host, content-length, connection, keep-alive, proxy-*, te, trailer, transfer-encoding, and upgrade are not allowed.

SchedStack sets its own Sched-* and Idempotency-Key headers after yours, so you cannot override them. See the delivery contract.

All bounds are enforced; any violation returns 422 invalid_retry_policy.

Field Range Default
max_attempts 1–50 8
base 0–24h 5s
max 0–168h (7 days) 1h
factor 1–100 2
strategy exponential only exponential
jitter boolean true

The next attempt is scheduled at base × factor^attempt, capped at max. See Retries & dead-letter.

ttl is any valid duration with no minimum or maximum. The delivery deadline is first fire + ttl, and it caps retries: a delivery that can’t land before the deadline won’t be retried past it — it finalizes as expired (a terminal, replayable state).

The optional request Idempotency-Key header makes a mutation safe to retry.

Rule Behavior
Window the first response is stored for 24 hours
Scope (project, mode, key) — test and live never collide
“Same request” a fingerprint of method + path + body
Same key, different body 409 idempotency_key_reuse
Same key, still in flight 409 idempotency_in_progress
What’s cached only 2xx/3xx; a 4xx/5xx releases the key so you can retry
Replay returns the stored response with Idempotent-Replayed: true
Key length a free-form string (a UUID is ideal) — keep it short; no enforced maximum

Applies to: POST /schedules, PATCH /schedules/{id}, /reschedule, POST /endpoints, PATCH /endpoints/{id}, and /replay. See Idempotency.

Rule Behavior
Page size (limit) default 20, max 100, min 1
Out-of-range limit silently clamped to 20 — not an error
Cursor opaque; pass next_cursor back via ?cursor=
Invalid cursor 400 invalid_cursor
Rule Requirement Error / outcome
Scheme HTTPS required 422 url_blocked
Reachability must be publicly routable — private, loopback, link-local, carrier-grade-NAT, and cloud-metadata addresses (e.g. 169.254.169.254) are rejected delivery terminal
Redirects not followed — a 3xx is a terminal misconfiguration, fix the URL delivery terminal
DNS rebinding the resolved address is re-checked on every connection delivery terminal
HTTP method one of POST, PUT, PATCH, GET, DELETE (default POST) 400 invalid_method
Rule Requirement Error
url required, HTTPS 422 missing_url / url_blocked
timeout > 0 and <= 1h 422 invalid_timeout
method with endpoint_id omit it — the method comes from the profile 400 method_on_profile
endpoint + endpoint_id mutually exclusive 400 multiple_endpoint
Unknown / archived endpoint_id must exist and be active 422 invalid_endpoint / endpoint_archived
Archiving a profile in use blocked while a schedule references it 409 profile_in_use
retry_budget rate >= 0, burst >= 0 (default 10 / 100) 422 invalid_retry_budget
rate_limit per_second >= 0 — accepted but not yet enforced 422 invalid_rate_limit
breaker_policy threshold 1–1000; base_open / max_open / probe_timeout all required, each > 0 and <= 24h 422 invalid_breaker_policy

See Endpoint profiles.

Rule Behavior Error
Header Authorization: Bearer sk_<mode>_…
Missing key 401 missing_api_key
Invalid or revoked key 401 invalid_api_key
Cross-scope access a key only sees its own (project, mode) 404 not_found
  • Per-attempt delivery timeout: 30 seconds.
  • Retry budget: 10 retries/second, burst 100, per (project, endpoint).
  • Circuit breaker: opens after 5 consecutive failures; open window 30s → 1h; probes every 30s.
  • Retry policy: max_attempts defaults to 8 when you don’t supply a policy.

Every typed code you can receive, with its HTTP status. Branch on type for control flow and code for specifics (see Conventions).

code HTTP When
invalid_json 400 Malformed JSON, or request body over 1 MB
invalid_duration 400 delay / local_fire_at isn’t a valid duration/format
multiple_timing 400 More than one timing source given
multiple_endpoint 400 Both endpoint and endpoint_id given
method_on_profile 400 method sent alongside endpoint_id
invalid_method 400 HTTP method not one of POST/PUT/PATCH/GET/DELETE
invalid_cursor 400 Pagination cursor is not valid
missing_api_key 401 No Authorization bearer key
invalid_api_key 401 Key is unknown or revoked
not_found 404 No such object in this (project, mode)
idempotency_key_reuse 409 Same Idempotency-Key, different request body
idempotency_in_progress 409 A request with the same Idempotency-Key is still in flight
not_replayable 409 The delivery isn’t in a terminal state, so it can’t be replayed
profile_in_use 409 Can’t archive an endpoint profile a schedule still references
sub_floor_delay 422 delay below the 1 s floor
fire_at_in_past 422 fire_at / local_fire_at is not in the future
fire_at_too_far 422 fire_at / local_fire_at is more than 10 years out
missing_timing 422 No timing source given
invalid_cron 422 cron expression or timezone is invalid
payload_too_large 422 Delivery body over 256 KB
url_blocked 422 Destination isn’t HTTPS or isn’t publicly routable
invalid_retry_policy 422 A retry_policy field is out of range
missing_url 422 Endpoint profile has no url
invalid_endpoint 422 endpoint_id doesn’t exist
endpoint_archived 422 endpoint_id refers to an archived profile
invalid_timeout 422 Profile timeout is <= 0 or > 1h
invalid_retry_budget 422 Profile retry_budget values are invalid
invalid_rate_limit 422 Profile rate_limit value is invalid
invalid_breaker_policy 422 Profile breaker_policy is incomplete or out of range