Automations
Blueprints that regenerate a fresh clip from one brief and publish it on a recurring schedule, plus their readiness checklist and stop reasons.
An automation is a blueprint: a video whose brief is reused to generate a fresh clip on a schedule, which is then published to a publishing profile. The blueprint itself is never published; it is the recipe.
| Blueprint | Clip | |
|---|---|---|
| Where it is listed | GET /automations | GET /automations/{id}/clips |
On GET /videos | Excluded | Excluded |
| Flags | isAutomation: true | isClip: true, parentVideoId points at the blueprint |
| Carries | The schedule, the destination, the health | One generation, one render, one publish |
Create a blueprint
POST /automations takes the same brief as POST /videos, flagged as a
blueprint, and queues its first generation. Send the brief flat or nested under
brief; it must carry at least a prompt or a text script.
curl -X POST https://app.shortfast.com/api/v1/automations \
-H "Authorization: Bearer sf_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"prompt": "One productivity tip for founders, different angle each time",
"type": "faceless",
"videoDuration": "short",
"aspectRatio": "9:16",
"voiceId": "Enceladus",
"voiceProvider": "gemini"
}'A new blueprint has no schedule and no destination, so it starts paused with
its whole checklist outstanding:
{
"data": {
"id": "aQ8vN2mK5tR9wL3xc",
"title": "One productivity tip for founders",
"status": "paused",
"schedule": { "frequency": null, "timezone": "UTC", "times": [], "days": [] },
"publishing": { "profileId": null, "platforms": [] },
"autoRefill": false,
"lastRunAt": null,
"stopped": null,
"health": {
"ready": false,
"blockers": [
{ "id": "no-schedule", "label": "Set a publishing schedule" },
{ "id": "no-profile", "label": "Choose where to publish" },
{ "id": "no-platforms", "label": "Select at least one platform" }
],
"nextScheduledAt": null
}
}
}An existing video can be promoted instead, with POST /videos/{id}/automation
and { "enabled": true }. Promoting needs a video whose generation finished
(warning-source-not-ready otherwise) and room under the plan's automation limit
(warning-plan-automations). A clip cannot be promoted: promote the video it was
made from, or the call is a 422. Sending { "enabled": false } turns a
blueprint back into a plain video, pausing it first so the clips it had already
armed are unscheduled.
The schedule
PATCH /automations/{id} updates any of schedule, profileId, platforms,
autoRefill and status. Provide at least one. schedule carries the whole
timing block, so send it complete rather than a partial edit.
frequency | Slots | Where the slots go |
|---|---|---|
daily | 1 | times |
twice-daily | 2 | times |
thrice-daily | 3 | times |
weekly | 1 | schedule |
twice-weekly | 2 | schedule |
thrice-weekly | 3 | schedule |
A daily frequency needs exactly as many entries in times as it has slots; a
weekly frequency needs the same count of day and time pairs in schedule.
Setting one clears the other. timezone is an IANA name such as Europe/Paris
and defaults to UTC.
Each slot has a time on a 24-hour clock and a mode: fixed posts at the
stated time, random jitters the slot around it so a feed does not look
machine-timed. mode defaults to fixed, and a bare "09:30" string is
accepted in times and read as fixed.
curl -X PATCH https://app.shortfast.com/api/v1/automations/aQ8vN2mK5tR9wL3xc \
-H "Authorization: Bearer sf_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"schedule": {
"frequency": "twice-daily",
"timezone": "Europe/Paris",
"times": [
{ "time": "09:30", "mode": "fixed" },
{ "time": "18:00", "mode": "random" }
]
},
"profileId": "pR4kM8xT2vQ6nL9wz",
"platforms": ["tiktok", "youtube"],
"autoRefill": true,
"status": "active"
}'A weekly schedule sends pairs instead:
{
"schedule": {
"frequency": "twice-weekly",
"timezone": "Europe/Paris",
"schedule": [
{ "day": "tuesday", "time": "09:30", "mode": "fixed" },
{ "day": "thursday", "time": "18:00", "mode": "random" }
]
}
}The weekly pairs are written under schedule.schedule but read back under
schedule.days. A round trip that posts the automation's own schedule object
straight back therefore drops a weekly timing block: rebuild it from days
before sending.
autoRefill decides whether the automation generates a fresh clip when its queue
of armed clips runs dry, which is what keeps a schedule fed without a call from
you.
Readiness and blockers
An automation only runs when three things are set. Anything missing is a blocker,
and health.ready is false while any remain:
| Blocker | Label | Fix |
|---|---|---|
no-schedule | Set a publishing schedule | Send a schedule with a frequency and its slots |
no-profile | Choose where to publish | Send a profileId from GET /profiles |
no-platforms | Select at least one platform | Send platforms from GET /platforms |
Activating (status: "active") while any blocker stands is refused with 400
warning-publish-not-configured, and the message names them:
{
"error": {
"status": 400,
"code": "warning-publish-not-configured",
"message": "Finish setup first: Choose where to publish, Select at least one platform"
}
}GET /automations/{id}/health is the full picture, including counts the
automation shape omits:
{
"data": {
"ready": false,
"blockers": [{ "id": "no-platforms", "label": "Select at least one platform" }],
"status": "paused",
"autoRefill": true,
"lastRunAt": "2026-08-05T18:00:12.000Z",
"nextScheduledAt": null,
"stopped": {
"reason": "publish_no_accounts",
"stoppedAt": "2026-08-05T18:01:44.000Z",
"kind": "config",
"label": "No account connected",
"detail": "None of the selected platforms has a connected account. Connect one in Settings, then resume."
},
"clips": { "total": 12, "failed": 1 }
}
}nextScheduledAt is the earliest future clip slot, and null while the
automation is paused or blocked. clips.failed counts clips whose generation,
render or publish failed.
Stop reasons
An automation that cannot do its job is stopped automatically and stopped says
why. kind is what decides your response: a config reason cannot clear on a
retry and needs a fix from you, a transient one may clear on its own.
reason | kind | What happened |
|---|---|---|
publish_not_configured | config | The publishing destination went missing, so nothing can be posted |
publish_auth | config | The connected social account rejected the post. Reconnect it in Settings, then resume |
publish_no_accounts | config | None of the selected platforms has a connected account |
source_video_failed | config | The blueprint could not be processed, so no new clips can be made from it |
plan_downgraded | config | Automations are not available on the current plan |
consecutive_failures | transient | Several clips in a row failed to generate or publish |
Subscribe to automation.blocked to hear about a missing
checklist item before it stops anything, and to automation.stopped for the
stop itself. Fix the cause, then PATCH the automation back to
status: "active".
Run one on demand
POST /automations/{id}/run produces one clip immediately, outside the schedule:
curl -X POST https://app.shortfast.com/api/v1/automations/aQ8vN2mK5tR9wL3xc/run \
-H "Authorization: Bearer sf_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Idempotency-Key: 2b7d51e4-9f30-4c88-91a6-6f0b3d5c7e21"Asynchronous: the response is the new clip with process.status: "QUEUED", and
generating it charges credits like any other generation. The blueprint must be
fully set up, or the call is refused with warning-publish-not-configured
naming the blockers, exactly as activation is.
Listing and deleting
GET /automations lists the blueprints in the workspace, newest activity first,
and accepts ?status=active or ?status=paused. paused is the implied
default: a blueprint that was never activated carries no stored status at all.
DELETE /automations/{id} deletes the blueprint, every clip it produced and the
files they own, and returns the storage to the workspace quota. It cannot be
undone, and the response reports deletedClips and deletedBytes.