Schedule a campaign
Schedule materializes local occurrence posts from a campaign’s stored content and cadence. It queues work; it does not confirm a social platform has published anything.
Before you start
Read the campaign in the intended authorized brand. It must be draft or scheduled, have an enabled platform and a content variant whose post exists in the same workspace. Verify the original authenticated user context, the app_topolo_socialize.calendar:write permission and the workspace grant. Review and confirm content, window and write effects before running the action.
The supplied window must start before it ends, end in the future and last 1 hour–365 days. Cadence, platform and timezone settings come from the stored campaign, not this request.
Request
POST /api/campaigns/{id}/schedule
| Field | Required | Meaning |
|---|---|---|
id | Yes | Existing campaign id. |
windowStartDate | Yes | UTC date-time string. |
windowEndDate | Yes | UTC date-time string after start; future-ended valid window. |
How posts are created
- With no cadence days/times, the window start is the single slot. Otherwise both weekday names and strict HH:MM times must be configured in a valid timezone and yield at least one slot. Unresolvable local daylight-saving times are skipped.
- Each slot/platform uses the first variant with a post id as its content/media template. This scheduling path does not split occurrences by A/B weights.
- Stable campaign/platform/timestamp identities and
INSERT OR IGNOREreuse identical existing occurrence rows. Changing the window or cadence can add posts without deleting older ones; an existing matching post is not rewritten from a changed template.
Worked examples
These examples are synthetic and unrun. They assume valid access, original user identity and stored campaign/template/platform settings; no real post has been scheduled.
Schedule a draft with one enabled platform
Synthetic and unrun. Assume verified original user/workspace access, a draft campaign with one same-workspace template variant, enabled LinkedIn, UTC, no cadence days/times and a clock before the window end. Post-run id follows the reviewed deterministic identity rule; no live record exists.
Request
{
"id": "campaign_example",
"windowStartDate": "2026-10-22T12:00:00.000Z",
"windowEndDate": "2026-10-22T14:00:00.000Z"
}
Response illustration
{
"id": "campaign_example",
"brandId": "ws_socialize_example",
"name": "Example campaign",
"createdBy": "user_example",
"status": "scheduled",
"windowStartDate": "2026-10-22T12:00:00.000Z",
"windowEndDate": "2026-10-22T14:00:00.000Z",
"paused": false,
"pausedAt": null,
"resumedAt": null,
"variants": [
{
"id": "campaign_example_Control",
"name": "Control",
"weight": 100,
"postId": "post_example",
"hashtags": []
}
],
"createdAt": "2026-10-21T00:00:00.000Z",
"updatedAt": "2026-10-21T00:00:00.000Z",
"platforms": [
{
"platform": "linkedin",
"enabled": true,
"settings": {}
}
],
"timezone": "UTC",
"scheduledPosts": [
{
"id": "post_run_campaign_example_linkedin_1792670400000",
"platform": "linkedin",
"scheduledFor": "2026-10-22T12:00:00.000Z",
"status": "scheduled"
}
]
}
The campaign is scheduled and one occurrence is queued at the window start. Re-read the campaign and linked post; queued state is not provider publication.
Repeat the exact window after reconciliation
Synthetic and unrun. Assumes an owner-approved repair after current-state reconciliation, unchanged campaign/window/template and existing matching occurrence rows. This illustrates INSERT OR IGNORE identities, not a broad concurrency/rollback guarantee.
Request
{
"id": "campaign_example",
"windowStartDate": "2026-10-22T12:00:00.000Z",
"windowEndDate": "2026-10-22T14:00:00.000Z"
}
Response illustration
{
"id": "campaign_example",
"brandId": "ws_socialize_example",
"name": "Example campaign",
"createdBy": "user_example",
"status": "scheduled",
"windowStartDate": "2026-10-22T12:00:00.000Z",
"windowEndDate": "2026-10-22T14:00:00.000Z",
"paused": false,
"pausedAt": null,
"resumedAt": null,
"variants": [
{
"id": "campaign_example_Control",
"name": "Control",
"weight": 100,
"postId": "post_example",
"hashtags": []
}
],
"createdAt": "2026-10-21T00:00:00.000Z",
"updatedAt": "2026-10-21T00:00:00.000Z",
"platforms": [
{
"platform": "linkedin",
"enabled": true,
"settings": {}
}
],
"timezone": "UTC",
"scheduledPosts": [
{
"id": "post_run_campaign_example_linkedin_1792670400000",
"platform": "linkedin",
"scheduledFor": "2026-10-22T12:00:00.000Z",
"status": "scheduled"
}
]
}
Existing matching occurrence ids are reused; the response can include all campaign occurrence rows. Repetition is not a way to update existing post content or guarantee provider delivery.
Verify the result
Read the campaign again and verify its brand, scheduled status, exact window and pause state. Inspect the returned post ids in the same workspace. scheduledPosts can include older campaign occurrences; it is not limited to rows created by this request. Scheduling does not automatically clear the pause flag.
Failure and recovery
The service writes scheduled status and dates before validating/materializing every occurrence. A later HTTP400 or storage error can leave a scheduled campaign or some posts already stored. Preserve request evidence and read the campaign and related posts before any repair; a failed response is not proof of rollback.
On the first transition to scheduled, the route evaluates original user identity before entering the best-effort notification helper. Missing identity can produce HTTP400 Authentication context missing after state/posts were written. The helper itself catches failures and its Boolean is ignored; neither a successful response nor a replay proves notification delivery.
Already active/completed/cancelled campaigns are refused with HTTP409 problem details. Other validation uses HTTP400 problem details; generic failures use HTTP500. Preserve actual detail and request id. There is no automatic inverse: changing windows, cancelling or deleting is a separate decision and does not by itself recall provider content.
Change log
- 2026-10-21: Added source-specific scheduling examples, first-template behavior, stable occurrence identity limits, additive windows, post-update validation/authentication effects and persisted-state recovery.
Source
Reviewed route/service/domain, exact slot/identity algorithm, brand access and notification/error source at f8e30bad6947f868302f24e9cb31b6a2bdd1fbf6. Examples validate against the refreshed declaration; no business action or provider test ran, and serving-source association remains unknown.