Timezones & Scheduling
How scheduledFor and the timezone field interact, how queue slots handle timezones and DST, and the pitfalls that make posts publish at the wrong hour.
Posts publishing at the wrong hour almost always trace back to one thing: sending a local wall-clock time without saying which wall clock. Here's exactly how the API interprets times.
TL;DR: send scheduledFor as UTC ISO 8601 with a Z suffix (or an explicit offset) and forget the timezone field exists. Everything below is for when you'd rather think in your user's local time.
How scheduledFor is parsed
Create post accepts three formats:
| You send | Interpreted as |
|---|---|
"2026-08-01T10:00:00Z" | Absolute UTC instant. The timezone field is ignored. |
"2026-08-01T10:00:00+02:00" | Absolute instant with explicit offset. The timezone field is ignored. |
"2026-08-01T10:00:00" (no zone info) | Local wall-clock time in the request's timezone field (IANA name, default UTC). |
So these three requests schedule the exact same instant:
{ "scheduledFor": "2026-08-01T08:00:00Z" }
{ "scheduledFor": "2026-08-01T10:00:00+02:00" }
{ "scheduledFor": "2026-08-01T10:00:00", "timezone": "Europe/Madrid" }And this is the classic bug: a naive local time with no timezone field is treated as UTC, so the post goes out hours off:
// User picked 10:00 Madrid time, but this publishes at 10:00 UTC (12:00 in Madrid)
{ "scheduledFor": "2026-08-01T10:00:00" }timezone must be a valid IANA name (Europe/Madrid, America/New_York, ...). Invalid values return a 400.
Scheduling in the user's local time
If your UI lets users pick a local date and time, pass it through with their timezone instead of converting yourself:
// In the browser, detect the user's IANA timezone:
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
await zernio.posts.createPost({
body: {
content: 'Good morning!',
platforms: [{ platform: 'instagram', accountId: 'acc_xxx' }],
scheduledFor: '2026-08-01T09:00:00', // what the user picked
timezone: tz, // e.g. "America/New_York"
},
});client.posts.create(
content="Good morning!",
platforms=[{"platform": "instagram", "accountId": "acc_xxx"}],
scheduled_for="2026-08-01T09:00:00", # what the user picked
timezone="America/New_York",
)Reading times back
The API stores and returns scheduledFor and publishedAt as UTC ISO 8601. Convert to the viewer's timezone at display time; don't assume the value comes back in the timezone you sent.
Queue timezones
Queues carry their own timezone, independent of any post-level field. Slots are defined as a day of week plus a wall-clock time in the queue's timezone:
{
"profileId": "664a0b1c2d3e4f0011223344",
"name": "Evening Posts",
"timezone": "America/New_York",
"slots": [
{ "dayOfWeek": 1, "time": "18:00" },
{ "dayOfWeek": 3, "time": "18:00" },
{ "dayOfWeek": 5, "time": "18:00" }
]
}dayOfWeekis0(Sunday) through6(Saturday).timeisHH:mmin the queue's timezone, not UTC.- Slots follow the queue's local wall clock through DST changes: an 18:00 New York slot stays at 18:00 New York time year-round, even though its UTC instant shifts by an hour.
Posts created with queuedFromProfile get their scheduledFor computed from these slots; you never send a time yourself.
Per-platform schedule overrides
Each entry in platforms[] accepts its own scheduledFor, overriding the top-level one for that platform (stagger the same post across networks). For these overrides, send an absolute time (Z or explicit offset) so there's no ambiguity about which timezone applies.
Checklist
- Prefer UTC with
Z. It's unambiguous and survives every layer. - If you send naive local times, always send
timezone(IANA) in the same request. - Detect the user's timezone in the browser with
Intl.DateTimeFormat().resolvedOptions().timeZone; don't guess from locale. - Display times by converting the returned UTC values, not by echoing what you sent.
- For recurring local-time posting (every weekday at 9:00 local), use a queue with the right timezone instead of computing times yourself.