# Scheduling and timezones

Schedule posts at a local wall-clock time with scheduledFor and an IANA timezone, give each platform its own time, and reschedule safely.

To publish later, send `scheduledFor`. Pozy stores the time as a UTC instant and publishes at that instant. The one thing to get right is how that instant is read from what you send, because a missing timezone is the most common reason a post goes out at the wrong hour.

## Schedule at a local time

Send the wall-clock time the person picked, without an offset, plus their IANA timezone:

```bash cURL
curl -X POST https://api.pozy.ai/v1/posts \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Doors open at 10. See you there.",
    "platforms": [{ "platform": "instagram", "accountId": "6710c3e2a9f14b0d8e5c7a22" }],
    "mediaItems": [{ "type": "image", "url": "https://pozy.ai/media/temp/1730289600_q8w1_doors.jpg" }],
    "scheduledFor": "2026-11-14T09:00:00",
    "timezone": "America/New_York"
  }'
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/posts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    content: "Doors open at 10. See you there.",
    platforms: [{ platform: "instagram", accountId: "6710c3e2a9f14b0d8e5c7a22" }],
    mediaItems: [{ type: "image", url: "https://pozy.ai/media/temp/1730289600_q8w1_doors.jpg" }],
    scheduledFor: "2026-11-14T09:00:00",
    // In a browser: Intl.DateTimeFormat().resolvedOptions().timeZone
    timezone: "America/New_York",
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);

console.log(data.post.scheduledFor); // "2026-11-14T14:00:00.000Z"
```
```python Python
resp = requests.post(
    "https://api.pozy.ai/v1/posts",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    json={
        "content": "Doors open at 10. See you there.",
        "platforms": [{"platform": "instagram", "accountId": "6710c3e2a9f14b0d8e5c7a22"}],
        "mediaItems": [{"type": "image", "url": "https://pozy.ai/media/temp/1730289600_q8w1_doors.jpg"}],
        "scheduledFor": "2026-11-14T09:00:00",
        "timezone": "America/New_York",
    },
    timeout=30,
)
print(resp.json()["post"]["scheduledFor"])  # 2026-11-14T14:00:00.000Z
```

The response returns `status: "scheduled"` and `scheduledFor` in UTC with a trailing `Z`. Convert it back to local time for display; Pozy never returns local times.

## How scheduledFor is read

| You send | Pozy reads it as |
| --- | --- |
| `"2026-11-14T14:00:00Z"` | That UTC instant. `timezone` is ignored. |
| `"2026-11-14T15:00:00+01:00"` | That instant at that offset. `timezone` is ignored. |
| `"2026-11-14T09:00:00"` (no `Z`, no offset) | A wall-clock time in `timezone`. Without `timezone`, in UTC. |

All three examples above are the same moment when `timezone` is `America/New_York` for the last one.

> **A missing timezone means UTC:**
> 
> If a person in Madrid picks 10:00 and you send only `"scheduledFor": "2026-11-14T10:00:00"`, the post goes out at 10:00 UTC, which is 11:00 in Madrid. Always send `timezone` with a local time, or convert to UTC yourself and send a `Z`.

`timezone` must be a canonical IANA name such as `Europe/Madrid`, `America/Sao_Paulo` or `Asia/Tokyo`. Abbreviations like `CET` or `PST` and misspellings are rejected with a `400`.

## Give each platform its own time

A `scheduledFor` on a `platforms[]` entry overrides the top-level one for that entry. The top-level `timezone` still applies to it. Here LinkedIn goes out at 08:30 and Instagram at 19:00, both New York time:

```json
{
  "content": "Our November product update is out.",
  "scheduledFor": "2026-11-05T08:30:00",
  "timezone": "America/New_York",
  "platforms": [
    { "platform": "linkedin", "accountId": "6710c3e2a9f14b0d8e5c7a21" },
    {
      "platform": "instagram",
      "accountId": "6710c3e2a9f14b0d8e5c7a22",
      "scheduledFor": "2026-11-05T19:00:00"
    }
  ]
}
```

The overall `post.scheduledFor` shows the top-level time. Each entry's own time is on `platforms[].scheduledFor`.

## Daylight saving time

Wall-clock times are converted with the offset in force on that date, so a 09:00 New York post is 14:00 UTC in winter and 13:00 UTC in summer. Two hours a year need care:

| Case | Example (`America/New_York`) | What to do |
| --- | --- | --- |
| A time that does not exist (clocks jump forward) | `2027-03-14T02:30:00` | Pick another time, or send an explicit offset. |
| A time that happens twice (clocks fall back) | `2027-11-07T01:30:00` | Send an explicit offset (`-04:00` or `-05:00`) to say which one you mean. |

On those two days, sending a `Z` time or an explicit offset is the only way to be exact. Queue slots follow the wall clock all year: an 18:00 slot stays 18:00 local after the change.

## Times in the past

A `scheduledFor` that has already passed is not an error. The post publishes during the request, exactly as if you had sent `publishNow: true`, and the response holds the outcome. If you build a scheduler that might send stale times, check the time yourself before calling.

## Reschedule a post

Change the time with `PUT /v1/posts/{postId}`. This works for `scheduled`, `draft`, `failed`, `partial` and `cancelled` posts:

```bash cURL
curl -X PUT https://api.pozy.ai/v1/posts/6721a8c04e9b2f1d7c3a5e88 \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "scheduledFor": "2026-11-15T09:00:00", "timezone": "America/New_York" }'
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/posts/6721a8c04e9b2f1d7c3a5e88", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ scheduledFor: "2026-11-15T09:00:00", timezone: "America/New_York" }),
});
if (!res.ok) throw new Error((await res.json()).error);
```
```python Python
requests.put(
    "https://api.pozy.ai/v1/posts/6721a8c04e9b2f1d7c3a5e88",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    json={"scheduledFor": "2026-11-15T09:00:00", "timezone": "America/New_York"},
    timeout=30,
).raise_for_status()
```

Things to know when you reschedule:

- **Drafts stay drafts.** To schedule a draft, send `isDraft: false` together with the time. A time alone is saved but the post keeps `status: "draft"`.
- **Queued posts keep their queue.** If the post came from a queue and the new time is already taken by another post in that queue, the update fails with `409` and `code: "queue_slot_conflict"`. Pick a free time, drop `scheduledFor` to let the queue choose, or send `queueId: null` to take the post out of the queue.
- **No new `post.scheduled` event.** Moving a post that is already scheduled does not fire the webhook again. Keep the new time from the update response.
- **Cancel instead of moving.** To stop a scheduled post, delete it with `DELETE /v1/posts/{postId}` or turn it back into a draft with `isDraft: true`.

## Let Pozy pick the time

Two features choose `scheduledFor` for you:

- **Queues** spread posts over recurring weekly slots per profile. See [Queues](https://pozy.ai/docs/publishing/queues).
- **Best time to post** suggests slots from each account's own engagement history. See [Best time to post](https://pozy.ai/docs/analytics/best-time).

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`POST /v1/posts`](https://pozy.ai/docs/api/createPost) | Create post |
| [`PUT /v1/posts/{postId}`](https://pozy.ai/docs/api/updatePost) | Update post |
| [`GET /v1/posts/{postId}`](https://pozy.ai/docs/api/getPost) | Get post |
| [`GET /v1/posts`](https://pozy.ai/docs/api/listPosts) | List posts |
