Content Calendar

Creating a post

How to create a content slot, pick platforms, attach media, and schedule it.

A content slot is the core unit of the Content Engine — it's a piece of content tied to one or more platform accounts and a scheduled time.

Basic structure

Field Required Description
caption Yes The post text
contentType Yes text, image, link (video not yet supported)
scheduledAt Yes ISO 8601 datetime in UTC when the post should fire
timezone Yes IANA timezone name (e.g., Asia/Kolkata) — for display in the calendar
targets Yes Array of at least one { connectionId, platformCaption? }
mediaIds No Array of media IDs from the Media Service
mediaAltText No Object mapping mediaId → alt text string (required for Twitter accessibility)
campaignId No Associate this slot with a campaign
labelIds No Array of label IDs to tag this slot
status No draft (default) or scheduled (skips the approval step)

Platform captions

Each target can have its own caption override via platformCaption. If not provided, all platforms use the slot's main caption.

{
  "caption": "Check out our new feature — link in bio!",
  "targets": [
    {
      "connectionId": "conn_twitter_01",
      "platformCaption": "Check out our new feature! 🚀 https://vlozi.app/features/new"
    },
    {
      "connectionId": "conn_instagram_01"
    }
  ]
}

In this example, Twitter uses the custom caption (with link and emoji); Instagram uses the main caption.

Scheduling time

scheduledAt must be a future timestamp. The engine validates this at create time.

The timezone field is used for calendar display only — it does not affect when the post fires. Publishing always uses the UTC scheduledAt time.

Creating via the dashboard

Content Engine → Calendar → + New post

Select your platform accounts, write your caption, attach media, pick a time, then choose Save as draft or Schedule directly.

Creating via the API

POST /content/slots
Authorization: Bearer <token>
Content-Type: application/json
 
{
  "caption": "Exciting news — our API is live!",
  "contentType": "text",
  "scheduledAt": "2026-07-15T09:00:00Z",
  "timezone": "Asia/Kolkata",
  "targets": [
    { "connectionId": "conn_twitter_abc123" },
    { "connectionId": "conn_linkedin_xyz789" }
  ],
  "status": "scheduled"
}

Response:

{
  "slotId": "slot_01j...",
  "status": "scheduled",
  "scheduledAt": "2026-07-15T09:00:00Z",
  "targets": [
    { "targetId": "tgt_01j...", "connectionId": "conn_twitter_abc123", "publishStatus": "pending" },
    { "targetId": "tgt_01j...", "connectionId": "conn_linkedin_xyz789", "publishStatus": "pending" }
  ]
}

Attaching media

Media must first be uploaded to the Vlozi Media Service. The Content Engine does not accept raw file uploads directly.

  1. Upload your image via the Media Service and receive a mediaId
  2. Pass the mediaId in the mediaIds array when creating the slot
  3. Optionally set alt text via mediaAltText: { "<mediaId>": "Description of the image" }

At publish time, the engine resolves the actual media URL from the Media Service and uploads it to the platform.

Twitter: Accepts up to 4 images per tweet. Alt text is set via Twitter's media metadata API.
LinkedIn / Facebook / Instagram: Accepts images; video not yet supported.

Slot states

A slot moves through a state machine from creation to publication:

draft ──► pending_review ──[approve]──► approved ──► scheduled ──► publishing ──► published

          └──[reject]──► draft

                                                                 └──[fail]──► failed
State Meaning
draft Created but not yet submitted for review or scheduled
pending_review Submitted for approval, waiting on a reviewer
approved Approved; auto-transitions to scheduled
scheduled Durable Object alarm armed at scheduledAt
publishing Alarm fired; currently being processed by the queue
published All targets published successfully
failed All targets failed after retries

You can skip the approval step entirely by creating a slot with status: "scheduled" directly. This requires the content:slots.write permission.

Editing and rescheduling

You can edit a slot only when it is in draft or failed status. Scheduled slots must be rescheduled via the dedicated endpoint — this updates scheduledAt and re-arms the Durable Object alarm.

POST /content/slots/:slotId/reschedule
Content-Type: application/json
 
{ "scheduledAt": "2026-07-16T09:00:00Z" }

Deleting a slot

Only draft and scheduled slots can be deleted. Deleting a scheduled slot cancels its Durable Object alarm before the row is removed. Published slots are permanent (but can be individually deleted on the platform side).

Content Engine · Content CalendarEdit on GitHub