Scheduling
Every post you create either publishes immediately or at a future time you choose. The only difference is one field: scheduled_at. Omit it and the post publishes the moment it is created; include it and PostFuze holds the post until it is due, then publishes it the same way. Scheduling works identically across every connected platform and fans out to one target per account, just like an immediate post.
Immediate vs. scheduled
When you POST /posts without a scheduled_at, the post is created with status queued and its targets start publishing right away. When you include scheduled_at, the post is created with status scheduled and its targets sit in pending until their scheduled time arrives. A scheduled post is a normal post in every other respect — same containers, same media, same per-platform config.
| Mode | scheduled_at | Initial post status | When it publishes |
|---|---|---|---|
| Immediate | omitted | queued | Right away (typically within seconds) |
| Scheduled | a future UTC timestamp | scheduled | Within ~30s of the time you set |
| Draft | ignored while is_draft: true | draft | Never, until you turn it into an immediate or scheduled post |
Add to queue
If you want PostFuze to pick the next available recurring posting slot, send queued_from_account instead of scheduled_at. The value must identify one of the accounts in accounts[] (id, username, or network_unique_id). PostFuze resolves that account's channel-specific slots plus any org-wide slots, skips occupied times, and stores the chosen UTC scheduled_at on the created post.
Use queue mode for drip campaigns
POST /posts repeatedly with the same queued_from_account. Each create reserves the next free slot as part of the post insert, so two concurrent requests do not have to preview and coordinate times themselves.curl https://api.postfuze.com/api/v1/posts \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"content": "Post 1 of 50",
"accounts": ["a1b2c3d4-…"],
"queued_from_account": "a1b2c3d4-…",
"queue_timezone": "America/New_York"
}'Queue mode is mutually exclusive with scheduled_at and is_draft. Use queue_timezone (or timezone) to tell PostFuze which wall-clock zone the recurring slots are expressed in; when omitted, UTC is used.
The scheduled_at field
scheduled_at is an ISO-8601 timestamp. Always send it in UTC with an explicit Z suffix (or a numeric +00:00 offset). PostFuze stores and evaluates the moment in UTC, so a value with a Z is unambiguous and behaves the same no matter where your servers or your users are.
{
"scheduled_at": "2026-06-10T15:00:00.000Z"
}Always send UTC
scheduled_at as UTC with a trailing Z (for example 2026-06-10T15:00:00.000Z). A bare timestamp like 2026-06-10T15:00:00 with no offset is ambiguous, and a local-time value such as 2026-06-10T15:00:00-07:00 will publish at the equivalent UTC instant — which is rarely the wall-clock time you meant. Convert to UTC before calling the API and the post always fires when you expect.Schedule a post
Add scheduled_at to a normal create-post request. The response comes back with status scheduled and echoes the stored UTC timestamp.
curl https://api.postfuze.com/api/v1/posts \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: weekly-digest-2026-06-10" \
-d '{
"content": "This week in product: three new integrations are live 🚀",
"accounts": ["a1b2c3d4-…", "b7c8d9e0-…"],
"scheduled_at": "2026-06-10T15:00:00.000Z"
}'// Convert a local time to a UTC ISO string before sending.
const when = new Date('2026-06-10T15:00:00Z').toISOString(); // "2026-06-10T15:00:00.000Z"
const res = await fetch('https://api.postfuze.com/api/v1/posts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTFUZE_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
content: 'This week in product: three new integrations are live 🚀',
accounts: ['a1b2c3d4-…', 'b7c8d9e0-…'],
scheduled_at: when,
}),
});
const post = await res.json();
console.log(post.status); // "scheduled"import os, requests
from datetime import datetime, timezone
# Build a UTC ISO-8601 string — note the explicit timezone.
when = datetime(2026, 6, 10, 15, 0, tzinfo=timezone.utc).isoformat() # "2026-06-10T15:00:00+00:00"
res = requests.post(
"https://api.postfuze.com/api/v1/posts",
headers={"Authorization": f"Bearer {os.environ['POSTFUZE_KEY']}"},
json={
"content": "This week in product: three new integrations are live 🚀",
"accounts": ["a1b2c3d4-…", "b7c8d9e0-…"],
"scheduled_at": when,
},
)
post = res.json()
print(post["status"]) # "scheduled"POST /postsscheduled_at to publish immediately, or set a future UTC time to schedule it.published, partial, or failedHow scheduled posts are published
Scheduled posts are not polled by your code — PostFuze runs the clock for you and continuously checks for posts whose scheduled_at has arrived. When a post becomes due it goes live: the post and its targets move from scheduled to queued and publish just like immediate posts. From there each target is delivered to its platform independently, exactly as described in the post lifecycle.
PostFuze checks roughly every 30 seconds, so in practice a scheduled post begins publishing within about 30 seconds of the time you set. Treat scheduled_atas a “not before” time, not a to-the-millisecond guarantee — it is the earliest moment the post will go out, and a small amount of dispatch latency on top is normal.
Watch results without polling
post.published and post.error events as each target completes.Reschedule or cancel before it runs
While a post is still scheduled you can cancel it. Once it has started publishing (or already published), it is on its way to the platform and can no longer be canceled.
Reschedule or edit
Posts are immutable once created — there is no PATCH /posts/{id} endpoint. To change the time or content of a scheduled post, delete it and create a new one: the delete cancels the still-pending targets, and the fresh POST /posts carries the new scheduled_at (in UTC) and content.
# 1. Cancel the original scheduled post
curl -X DELETE https://api.postfuze.com/api/v1/posts/p1a2b3c4-… \
-H "Authorization: Bearer sk_live_…"
# 2. Recreate it with the new time
curl https://api.postfuze.com/api/v1/posts \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"content": "Launch day is here 🚀",
"accounts": ["a1b2c3d4-…"],
"scheduled_at": "2026-06-16T09:30:00.000Z"
}'Cancel
Deleting a post that is still scheduled, queued, or a draft cancels it: its status moves to canceled and it will never publish. Anything already delivered to a platform stays live there — cancellation only stops targets that have not yet gone out.
curl -X DELETE https://api.postfuze.com/api/v1/posts/p1a2b3c4-… \
-H "Authorization: Bearer sk_live_…"{ "id": "p1a2b3c4-…", "canceled": true }Schedule a first comment or thread alongside
Scheduling and first comments & threads compose cleanly. Pass a containers array instead of a flat content string and add scheduled_atas usual. When the post becomes due, the root post publishes first and each following container is posted as a native reply — the whole sequence is scheduled as one unit, so a “link in the first comment” goes out together with the main post.
curl https://api.postfuze.com/api/v1/posts \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"containers": [
{ "content": "Launch day is here 🚀", "media": [{ "id": "m_9f8e7d6c" }] },
{ "content": "Read the full announcement: https://acme.com/blog/launch", "media": [] }
],
"accounts": ["a1b2c3d4-…"],
"scheduled_at": "2026-06-10T15:00:00.000Z"
}'Near-future vs. far-future
There is no minimum lead time. You can schedule a post a few seconds out — but because PostFuze checks about every 30 seconds, a time only moments away may publish almost as quickly as an immediate post. If you genuinely want it to go out now, omit scheduled_at entirely rather than setting a time in the past.
At the other end, scheduled_at must be within 30 days of now. A timestamp further out than that is rejected. For longer lead times, store the post as a draft and set a real scheduled_at once it falls inside the window.
Timezone guidance
PostFuze only ever sees UTC, so converting your users' local times correctly is your application's job. A reliable pattern:
- Capture the user's intended wall-clock time and their IANA timezone (for example
America/Los_Angeles). - Convert that local time to a UTC instant in your backend, using a timezone-aware date library.
- Serialize the result as an ISO-8601 string ending in
Zand send it asscheduled_at.
from datetime import datetime
from zoneinfo import ZoneInfo
# User wants 9:00 AM in Los Angeles on June 10.
local = datetime(2026, 6, 10, 9, 0, tzinfo=ZoneInfo("America/Los_Angeles"))
scheduled_at = local.astimezone(ZoneInfo("UTC")).isoformat()
# → "2026-06-10T16:00:00+00:00" (PDT is UTC-7)Let the platform handle daylight saving
Best-time scheduling
Rather than picking a timestamp by hand every time, you can define a set of posting slots— recurring preferred times of the week in local wall-clock terms — and let PostFuze resolve the next open one for you. A slot is a weekday (or every day) plus a minute-of-day, optionally scoped to a single account; org-wide slots apply to all of them. The resolver walks your slots forward from now in the timezone you pass, skips any that collide with a post you have already scheduled, and returns a concrete UTC scheduled_at you then send to POST /posts exactly as above.
Find-slot is advisory, not auto-pilot
scheduled_at, adjust it if you like, then create the post with it. Slots resolve against the IANA timezone you supply (the dashboard passes your profile timezone), so daylight-saving stays correct.Define your slots
PUT /scheduling/slotsreplaces the whole set in one declarative call — send the full desired schedule and PostFuze converges to it. Use weekday (0 = Sunday … 6 = Saturday, or null for every day), minute_of_day (minutes since local midnight, e.g. 570 = 9:30am), and an optional social_account_id to scope a slot to one account.
curl -X PUT https://api.postfuze.com/api/v1/scheduling/slots \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"slots": [
{ "weekday": 1, "minute_of_day": 540 },
{ "weekday": 3, "minute_of_day": 540 },
{ "weekday": null, "minute_of_day": 1020 }
]
}'Resolve the next open slot
POST /scheduling/find-slot returns the next free slot as a UTC instant. Pass your IANA timezone; optionally restrict to specific accounts, set a from instant, or require a min_lead_minutes buffer. If every slot inside the 30-day window is already taken, you get a 422 so you can fall back to a manual time.
curl https://api.postfuze.com/api/v1/scheduling/find-slot \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"timezone": "America/Los_Angeles",
"accounts": ["a1b2c3d4-…"],
"min_lead_minutes": 30
}'{
"scheduled_at": "2026-06-15T16:00:00.000Z",
"timezone": "America/Los_Angeles",
"slot": { "social_account_id": null, "weekday": 1, "minute_of_day": 540 }
}Feed the returned scheduled_at straight into POST /posts and the post fires at your next preferred time, already deconflicted from everything else on the books.
Next steps
- Posts — the full create-post reference, including
configand idempotency. - First comments & threads — how containers become replies.
- Post lifecycle — every status a post and its targets move through.
- Webhooks — get notified the instant a scheduled post publishes.