How to bulk schedule posts from a CSV
Send a CSV with the header text,connections,scheduledAt,title to POST /api/content/bulk-schedule, or pass the same rows as JSON. Each row becomes its own scheduled text post. One bad row never stops the others, and the response lists exactly which rows failed and why.
Bulk scheduling creates text posts only, up to 500 rows per call, and needs a plan that includes scheduling. For images and video, schedule each post with Schedule posts.
What does the CSV look like?
text,connections,scheduledAt,title
"Monday: shipping update",linkedin-myhandle x-myhandle,2026-12-07T09:00:00Z,Weekly update
"Wednesday: deep dive into the queue",linkedin-myhandle,2026-12-09T09:00:00Z,
"Friday: recap, with a comma inside",x-myhandle,2026-12-11T09:00:00Z,- The header row is required and the column order is fixed.
connectionstakes slugs separated by spaces, pipes (|), semicolons, or commas if the cell is quoted.scheduledAtmust be ISO 8601 and in the future.- Quote any cell that contains a comma, a quote (double it) or a line break.
titleis optional. The CSV can be up to 500,000 characters.
How do I run a bulk schedule?
1. Sidebar > Content.
2. Click Bulk CSV. The dialog is titled "Bulk schedule from CSV".
3. Click Upload .csv or paste into the box. The dialog shows how many posts it detected.
4. Confirm. The dialog lists how many were scheduled and a "row N: error" line for each failure.socifyr schedule posts.csvimport { readFileSync } from 'node:fs'
const res = await socifyr.uploads.bulkSchedule({
csv: readFileSync('posts.csv', 'utf8'),
})
console.log(`${res.created} scheduled, ${res.failed.length} failed`)
// Or pass rows directly
await socifyr.uploads.bulkSchedule({
rows: [
{ text: 'Morning post', connections: ['x-myhandle'], scheduledAt: '2026-12-07T09:00:00Z' },
{ text: 'Evening post', connections: ['x-myhandle'], scheduledAt: '2026-12-07T18:00:00Z' },
],
})from pathlib import Path
res = client.uploads.bulk_schedule(csv=Path("posts.csv").read_text())
print(res["created"], res["failed"])
# Or rows (use the API's camelCase keys)
client.uploads.bulk_schedule(rows=[
{"text": "Morning post", "connections": ["x-myhandle"], "scheduledAt": "2026-12-07T09:00:00Z"},
])# JSON rows
curl -X POST https://api.socifyr.com/api/content/bulk-schedule \
-H "Authorization: Bearer $SOCIFYR_API_KEY" \
-H "X-Workspace-Id: $SOCIFYR_WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"rows": [
{"text": "Morning post", "connections": ["x-myhandle"], "scheduledAt": "2026-12-07T09:00:00Z"},
{"text": "Evening post", "connections": ["x-myhandle"], "scheduledAt": "2026-12-07T18:00:00Z", "title": "Evening"}
]
}'
# Raw CSV in a JSON string
curl -X POST https://api.socifyr.com/api/content/bulk-schedule \
-H "Authorization: Bearer $SOCIFYR_API_KEY" \
-H "Content-Type: application/json" \
--data "$(jq -Rs '{csv: .}' posts.csv)"{
"tool": "schedule_posts",
"arguments": {
"posts": [
{ "text": "Morning post", "connections": ["x-myhandle"], "scheduledAt": "2026-12-07T09:00:00Z" },
{ "text": "Evening post", "connections": ["x-myhandle"], "scheduledAt": "2026-12-07T18:00:00Z", "title": "Evening" }
]
}
}Prompt for your agent: "Write five LinkedIn posts about our new queue feature and schedule them one per weekday at 9am UTC starting next Monday." The agent drafts the copy, then makes one schedule_posts call. The MCP tool takes posts (1 to 500) instead of a CSV; each item has text, connections, scheduledAt and an optional title.
What does the response look like?
{
"success": true,
"data": {
"created": 2,
"failed": [
{ "row": 3, "error": "scheduledAt must be in the future" }
]
}
}row counts data rows starting at 1, so the header does not count. The CLI prints 2 scheduled · 1 failed and then Row 3: scheduledAt must be in the future.
What errors can I get, and how do I fix them?
| Error | Where | Fix |
|---|---|---|
Provide "rows" or a non-empty "csv". | 400 on the call | The CSV had only a header, or the body was empty. |
A bulk schedule is limited to 500 rows. | 400 on the call | Split the file into batches. |
text is required | failed row | Fill the text cell. |
at least one connection is required | failed row | Add one or more slugs. |
scheduledAt is required | failed row | Add an ISO 8601 date-time. |
scheduledAt must be in the future | failed row | Move the time later. |
Connection "..." not found or not active... | failed row | Check the slug with list connections. |
Scheduling is not available on your current plan... | 403 | Upgrade the plan. |
Failed rows are not retried for you. Fix them and send a new CSV with only those rows.
How does this interact with approvals?
If the API key has requireApproval, every created row lands in pending_approval instead of the queue, and a teammate has to approve each post. See Approvals and guardrails.
FAQ
Can I bulk schedule images or video?
Not through this endpoint. It creates text posts. Loop over photos() or video() with scheduledAt.
Are rows processed in order?
Yes, one after another. The batch is not transactional, so rows before a failure stay scheduled.