Scheduling
Schedule a post, repeat it, and import a batch from a spreadsheet.
Scheduling is a field on a post, not a separate endpoint. Set status to scheduled and give it a schedule_at, and the worker picks it up at that time. Everything here needs the posts scope.
Schedule a post
curl -X POST https://api.fopost.com/v1/posts \
-H "X-API-Key: $FOPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"workspace_id": "7d2b8c11-4e5a-4a8f-b0d9-3c5e6f7a8b90",
"accounts": ["3a9f1d20-6c77-4b2e-9a01-8f5d2c3b4e10"],
"content": [{ "text": "Doors open Monday." }],
"status": "scheduled",
"schedule_at": "2026-09-02T09:00:00.000Z"
}'schedule_at is ISO 8601 and is read as UTC. Convert from the poster's local time before you send it, or you will publish at the right number and the wrong hour.
Reschedule and cancel
Move a scheduled post with PUT /v1/posts/{id}, sending a new schedule_at. Call POST /v1/posts/{id}/cancel to pull it out of the queue. To shift many posts at once, POST /v1/posts/bulk with the shift action moves each schedule_at by offset_minutes, negative to move earlier, and refuses an offset that would land in the past.
Add to the queue
Instead of picking a time, let the post take the next free posting slot. Slots are recurring weekly times set per workspace under Settings → Posting Schedule, for one account or for every account, each in its own timezone.
curl -X POST https://api.fopost.com/v1/posts \
-H "X-API-Key: $FOPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"workspace_id": "7d2b8c11-4e5a-4a8f-b0d9-3c5e6f7a8b90",
"accounts": ["3a9f1d20-6c77-4b2e-9a01-8f5d2c3b4e10"],
"content": [{ "text": "Doors open Monday." }],
"schedule": "queue"
}'schedule: "queue" replaces status and schedule_at. The response is an ordinary scheduled post: status is scheduled, schedule_at is the slot it took, and schedule_mode is queue. From there it is delivered exactly like a post you scheduled by hand. A workspace with no slot that applies to the post's accounts answers 400.
A slot is free per account, so two posts to the same account take the next two occurrences while a post to another account can share the first. PUT /v1/posts/{id} with schedule: "queue" puts an existing draft on the queue; sending a schedule_at instead takes it back off.
Manage slots
GET /v1/queue/slots?workspace_id={id}
POST /v1/queue/slots
PUT /v1/queue/slots/{id}
DELETE /v1/queue/slots/{id}A slot is weekday (0 Sunday to 6 Saturday), time (HH:MM, 24-hour), timezone (an IANA zone name) and an optional account_id; omit it for a slot every account shares.
Changing or removing a slot reflows the queue: every queued post whose time no longer matches a free occurrence moves to the next one, in queue order, and the response lists each move in moved with its from and to times. Hand-scheduled posts are never moved.
Look ahead
GET /v1/queue/next?workspace_id={id}&account_id={id}
GET /v1/queue/preview?workspace_id={id}&account_id={id}&count=5next answers the one time a post added now would get, null when no slot applies. preview answers the next count free occurrences in order, up to 20, each one taken as it is handed out. Leave account_id off for the whole workspace's view.
Repeat a post
A post can republish on a cadence without you scheduling each occurrence:
{
"status": "scheduled",
"schedule_at": "2026-09-02T09:00:00.000Z",
"repeatable": true,
"repeatable_times": 6,
"repeatable_gap": 2,
"repeatable_gap_unit": "weeks"
}repeatable_gap_unit is hours, days, weeks, or months. The post reports remaining_posts as it works through the run.
For resurfacing your best evergreen posts with fresh variants rather than repeating one verbatim, use content recycling instead.
Bulk import
Three endpoints turn a spreadsheet into a schedule. Validate first, commit second, and roll back by batch if you need to.
POST /v1/posts/bulk-import/validate
POST /v1/posts/bulk-import/commit
DELETE /v1/posts/bulk-import/{batchId}Upload a CSV or TSV, up to 500 data rows and 2 MB. Columns:
| Column | Required | Notes |
|---|---|---|
content | Yes | The post text |
schedule_at | No | ISO 8601, or YYYY-MM-DD HH:mm in the workspace timezone |
accounts | No | Comma-separated account ids or platform names |
labels | No | Comma-separated labels |
media_url | No | A URL to attach |
caption_<platform> | No | Overrides the text for one platform, for example caption_twitter |
validate dry-runs the file and returns per-row results without creating anything. commit re-validates and creates one scheduled post per row, all stamped with a fresh batch id. Commit is all or nothing: if any row fails, nothing is created and the failing rows come back.
DELETE /v1/posts/bulk-import/{batchId} removes the batch's posts that are still draft or scheduled and cancels their queued deliveries. It refuses with 409 once any post in the batch has published, because that half of the batch is already public.
What happens at publish time
The scheduler queues the post, then each account is delivered independently. Transient platform failures are retried with exponential backoff. If the queue loses its connection, orphaned deliveries are recovered and re-queued rather than dropped.
Watch for the result with webhooks rather than polling on a timer.
Next
Related documentation
- API Overview
Base URL, envelopes, pagination, and errors for the FoPost REST API.
- Authentication
API keys, scopes, and workspace binding.
- Publishing
Create a post, target accounts, publish it, and read the per-account result.
- Media
Upload files, list the media library, and attach media to a post.
- Validation
Check content, text length, and media against platform rules before a post exists.
- Accounts
List connected social accounts, check their health, and refresh credentials.
- Workspaces
Workspaces, labels, and how isolation works across them.
- Analytics
Overview totals, time series, top posts, demographics, and label roll-ups.