Skip to content

One-shot scheduling

A one-shot schedule fires once: you give SchedStack a destination and a time, and it owns delivery from there — retried until it lands, never silently lost. You pick the time in whichever form fits your code:

  • delay — a relative duration from now ("24h", "90s").
  • fire_at — an absolute RFC3339 instant ("2026-07-01T09:00:00Z").
  • local_fire_at + timezone — a wall-clock time in an IANA zone, mapped to the correct instant across DST.

A POST with a cron field instead makes a recurring schedule — see Recurring schedules.

delay, fire_at, local_fire_at, and cron are mutually exclusive. You must send exactly one:

  • Send none → 422 missing_timing.
  • Send more than one → 400 multiple_timing.

Everything else on the request (method, headers, body, ttl, retry_policy, metadata, idempotency_key) is shared across all three forms and optional. The only other required field is the destination: an inline endpoint (an https URL) or an endpoint_id referencing a saved endpoint profile.

The whole API request body is capped at 1 MB (400 invalid_json if larger), and the delivery body it carries is capped at 256 KB (422 payload_too_large). See Limits & constraints.

The simplest form: fire this many time-units from now. delay is a Go time.ParseDuration string, so units are ns, us/µs, ms, s, m, h and you can combine them ("1h30m").

Terminal window
curl -X POST https://api.schedstack.com/v1/schedules \
-H "Authorization: Bearer sk_test_…" \
-H "Content-Type: application/json" \
-d '{
"endpoint": "https://acme.dev/hooks/reminder",
"delay": "24h",
"body": "{\"kind\":\"trial_ending\"}"
}'
{
"id": "sch_01J9ZK3F8Q",
"object": "schedule",
"mode": "test",
"kind": "one_shot",
"state": "active",
"endpoint": "https://acme.dev/hooks/reminder",
"method": "POST",
"header_keys": [],
"fire_at": "2026-06-28T18:42:11Z",
"cron": null,
"timezone": null,
"next_fire_at": "2026-06-28T18:42:11Z",
"next_runs": ["2026-06-28T18:42:11Z"],
"ttl": null,
"retry_policy": {
"max_attempts": 8,
"strategy": "exponential",
"base": "5s",
"factor": 2,
"max": "1h",
"jitter": true
},
"metadata": {},
"created_at": "2026-06-27T18:42:11Z",
"updated_at": "2026-06-27T18:42:11Z"
}

The response resolves your delay to a concrete fire_at (now + the duration) so you can confirm the exact instant.

When you already know the exact UTC instant, send it as strict RFC3339. It must be in the future and no more than 10 years out.

Terminal window
curl -X POST https://api.schedstack.com/v1/schedules \
-H "Authorization: Bearer sk_test_…" \
-H "Content-Type: application/json" \
-d '{
"endpoint": "https://acme.dev/hooks/invoice",
"fire_at": "2026-07-01T09:00:00Z",
"body": "{\"invoice\":\"inv_204\"}"
}'
{
"id": "sch_01J9ZK7M2D",
"object": "schedule",
"kind": "one_shot",
"state": "active",
"fire_at": "2026-07-01T09:00:00Z",
"next_fire_at": "2026-07-01T09:00:00Z",
"next_runs": ["2026-07-01T09:00:00Z"],
"timezone": null,
"cron": null
}

When the time means “9am where the user is”, send an offset-less local timestamp plus an IANA timezone. SchedStack maps the wall-clock time to the correct absolute instant for that zone — including across daylight-saving transitions.

Terminal window
curl -X POST https://api.schedstack.com/v1/schedules \
-H "Authorization: Bearer sk_test_…" \
-H "Content-Type: application/json" \
-d '{
"endpoint": "https://acme.dev/hooks/digest",
"local_fire_at": "2026-07-01T09:00:00",
"timezone": "America/New_York",
"body": "{\"digest\":\"daily\"}"
}'
{
"id": "sch_01J9ZKA4WP",
"object": "schedule",
"kind": "one_shot",
"state": "active",
"fire_at": "2026-07-01T13:00:00Z",
"timezone": "America/New_York",
"next_fire_at": "2026-07-01T13:00:00Z",
"next_runs": ["2026-07-01T13:00:00Z"],
"cron": null
}

09:00 in America/New_York (EDT, UTC−4 in July) resolves to 13:00:00Z. The response always reports the resolved absolute fire_at in UTC alongside the timezone you sent.

A one-shot schedule is created in state: active and carries a single occurrence. Track that occurrence — scheduled → succeeded, or dead-lettered / expired — and replay it from the delivery, in Schedule lifecycle.

  1. Preview before committing. Want to confirm the instant without creating anything? The MCP preview_schedule tool validates timing and returns the next fire time. See MCP server.

  2. Make retries safe. Delivery is at-least-once, so your receiver should verify the Sched-Signature and dedupe on the Idempotency-Key header — see Verifying signatures.

  3. Bound how long it can run. Add a ttl (a duration) to set a deliver-by deadline; past it, the occurrence is marked expired rather than retried forever.