# Scheduling

Schedule posts for a future time with scheduledFor, and understand time zones and delivery timing.


Every post either publishes now or at a time you choose. Scheduling uses the same `POST /v1/posts` endpoint, with `scheduledFor` in place of `publishNow`.

## Schedule a post

```bash tab="cURL"
curl -X POST https://social-publish-api.onrender.com/v1/posts \
  -H "x-access-key: $RELAYCAST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Webinar starts in one hour. Join us!",
    "targets": [{ "accountId": "8f1c2a4e-5b6d-4e7f-9a0b-1c2d3e4f5a6b" }],
    "scheduledFor": "2026-10-20T15:00:00+02:00"
  }'
```

```ts tab="Node.js"
const publishAt = new Date("2026-10-20T13:00:00Z");

await fetch("https://social-publish-api.onrender.com/v1/posts", {
  method: "POST",
  headers: { "x-access-key": process.env.RELAYCAST_API_KEY!, "Content-Type": "application/json" },
  body: JSON.stringify({
    content: "Webinar starts in one hour. Join us!",
    targets: [{ accountId: "8f1c2a4e-5b6d-4e7f-9a0b-1c2d3e4f5a6b" }],
    scheduledFor: publishAt.toISOString(), // "2026-10-20T13:00:00.000Z"
  }),
});
```

```python tab="Python"
from datetime import datetime, timezone

publish_at = datetime(2026, 10, 20, 13, 0, tzinfo=timezone.utc)

requests.post(
    "https://social-publish-api.onrender.com/v1/posts",
    headers={"x-access-key": os.environ["RELAYCAST_API_KEY"]},
    json={
        "content": "Webinar starts in one hour. Join us!",
        "targets": [{"accountId": "8f1c2a4e-5b6d-4e7f-9a0b-1c2d3e4f5a6b"}],
        "scheduledFor": publish_at.isoformat(),  # "2026-10-20T13:00:00+00:00"
    },
)
```

The post is created with `status: "scheduled"` and its targets are `queued`. At the scheduled time the targets publish and the post moves through `publishing` to `published`, `partial` or `failed`.

## Timestamp format

`scheduledFor` must be an ISO 8601 date-time **with a time zone**: either `Z` for UTC or an offset like `+02:00` or `-05:00`.

| Value | Accepted |
| --- | --- |
| `2026-10-20T13:00:00Z` | Yes |
| `2026-10-20T13:00:00.000Z` | Yes |
| `2026-10-20T15:00:00+02:00` | Yes |
| `2026-10-20T13:00:00` | No, missing offset |
| `2026-10-20` | No, not a date-time |

The time is stored as an absolute instant and returned in UTC (`"scheduledFor": "2026-10-20T13:00:00.000Z"`). To post at a local wall-clock time, convert it in your code with the correct offset for that date, since daylight-saving changes alter the offset.

## Rules

- **Future times only.** A `scheduledFor` more than a minute in the past is rejected with `400 validation_error` and the message `scheduledFor must be in the future`. Times within that one-minute grace window publish right away.
- **`publishNow` wins.** If you send `publishNow: true`, any `scheduledFor` is ignored.
- **One of the two is required.** Sending neither returns `400` with `Either publishNow or scheduledFor is required`.
- **Credits are reserved at creation**, not at publish time. A scheduled post that can't be paid for is rejected immediately with `402`. See [Credits & Usage](/docs/credits).
- **Validation happens at creation.** Content, media and options are checked when you schedule, so a valid scheduled post won't fail validation later. It can still fail at publish time for runtime reasons, such as a media URL that has stopped working or an account whose access was revoked.

## Timing

Scheduled targets are queued with a delay and published by a background worker when that delay ends, usually within seconds of `scheduledFor`. If a job is missed (for example during an infrastructure restart), a sweeper re-queues targets that are still waiting a couple of minutes after their scheduled time.

Temporary platform errors are retried with backoff, so a target can publish some minutes after its scheduled time. See [Idempotency & Retries](/docs/idempotency#publish-retries).

## Media for scheduled posts

Media is downloaded by the platform at publish time, not when you schedule. Make sure public URLs stay reachable until then, or upload the file to the media library and use a `media://` reference. See [Media & Uploads](/docs/media).

## Changing or cancelling a scheduled post

The public API has no endpoint to edit or cancel a post. To cancel, delete the post from the **Posts** page in the dashboard before it publishes: its queued targets are removed and their credits refunded. To change it, delete it and create a new one. The post's **Open in playground** action loads the original request so you can adjust and resubmit it.

## Get notified

Create a notification rule for `post.scheduled` to confirm when posts are scheduled, and for `post.published`, `post.partial` and `post.failed` to hear the outcome. Rules can be limited to scheduled posts only. See [Notifications & Webhooks](/docs/notifications).

## In the dashboard

The [Playground](/docs/playground) has a date and time picker for scheduling, and the **Posts** page has a calendar view of everything scheduled and published.
