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.

Modescheduled_atInitial post statusWhen it publishes
ImmediateomittedqueuedRight away (typically within seconds)
Scheduleda future UTC timestampscheduledWithin ~30s of the time you set
Draftignored while is_draft: truedraftNever, 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

For a batch of posts, call 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 — add to the next queue slot
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.

A correct scheduled_at value
{
  "scheduled_at": "2026-06-10T15:00:00.000Z"
}

Always send UTC

Send 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
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"
  }'
Node / TypeScript
// 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"
Python
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"
1
Create the post at POST /posts
Omit scheduled_at to publish immediately, or set a future UTC time to schedule it.
2
PostFuze runs the clock
Scheduled posts wait — PostFuze checks roughly every 30 seconds for posts whose time has arrived. No polling from your code.
3
Publishing begins
When a post is due it moves to queued and publishes to each connected account independently.
4
Settles into published, partial, or failed
Subscribe to webhooks to learn the outcome as each target completes.

How 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

You do not need to poll to learn when a scheduled post lands. Subscribe to webhooks and react to 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.

curl — reschedule by cancel + recreate
# 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 — cancel a scheduled post
curl -X DELETE https://api.postfuze.com/api/v1/posts/p1a2b3c4-… \
  -H "Authorization: Bearer sk_live_…"
200 OK
{ "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 — scheduled post with a first comment
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 Z and send it as scheduled_at.
Python — local wall-clock time → UTC
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

Convert to UTC at schedule time, not at display time, and store the original IANA timezone if you intend to show or edit the time later. Computing the UTC instant from the timezone (rather than a fixed offset) keeps daylight-saving transitions correct.

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

Best-time scheduling resolves a suggestedtime and hands it back to you — it never publishes on its own. You stay in control: take the returned 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 — set posting slots
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 — find the next best 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
  }'
200 OK
{
  "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 config and 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.