Fourteen tools drive the social content calendar — scheduling, recurring schedules, human review, campaigns/labels, and AI-assisted captions/hashtags.
All endpoints follow the pattern POST https://mcp.vlozi.app/tools/content.<name> with Authorization: Bearer ls_xxx. All responses use this envelope:
{ "data": <payload>, "error": null } // success
{ "data": null, "error": "..." } // failure (with HTTP 4xx/5xx)NOTE
Supported platforms: twitter, instagram, linkedin, facebook. A slot's status moves through draft → pending_review → scheduled → publishing → published (or partially_failed/failed) — approve_slot/reject_slot are the human-review gate between draft and scheduled; there is no separate "publish now" tool, since going live is always either immediate auto-schedule or the review gate.
Calendar
content.get_calendar
Get the content calendar for a date range.
Scope: content:slots.read
Input: { from: string, to: string, status?: string } (from/to required ISO datetimes)
Response: { slots: ContentSlot[] }, newest-scheduled first
content.get_slot
Get one slot with its per-platform delivery targets.
Scope: content:slots.read
Input: { id: string } (required)
Response: { slot: ContentSlot, targets: SlotTarget[] } — each target: { id, socialConnectionId, platformCaption, publishStatus, externalPostId, externalPostUrl, errorMessage, retryCount }
NOTE
Returns 404 if the slot doesn't exist or belongs to a different tenant.
content.list_connections
List connected social accounts.
Scope: content:connections.read
Input: none
Response: { connections: { id, platform, platformUsername, displayName, status }[] } — status is active | expired | revoked | error
NOTE
Connections are set up through the dashboard's OAuth connect flow — no MCP tool creates one, only lists them.
content.list_campaigns
List content campaigns (groupings for related posts, e.g. a product launch).
Scope: content:campaigns.read
Input: none
Response: { campaigns: { id, name, color, status }[] } — status is active | completed | archived
content.list_labels
List content labels.
Scope: content:labels.read
Input: none
Response: { labels: { id, name, color }[] }
Scheduling
content.schedule_content
Schedule a social post across one or more connections.
Scope: content:slots.write
Input:
| Field | Type | Notes |
|---|---|---|
caption |
string (required) |
|
contentType |
"text" | "image" | "video" | "carousel" | "story" | "link" (required) |
|
connectionIds |
string[] (required) |
1–50 ids, each validated to belong to your workspace |
scheduledAt |
string |
ISO datetime with offset; defaults to now + 1h |
timezone |
string |
Default UTC |
mediaIds |
string[] |
Max 10 |
linkUrl |
string |
|
campaignId |
string |
|
autoApprove |
boolean |
Default false |
Response: { slotId: string, status: "scheduled" | "draft" }
IMPORTANT
autoApprove: true skips human review entirely — the slot lands directly in scheduled and a publish alarm is armed for scheduledAt. autoApprove: false (the default) leaves it as draft, awaiting approve_slot/reject_slot. Returns 400 if any connectionIds entry isn't a real connection in your workspace. If auto-approving and arming the schedule fails, the slot is demoted back to draft rather than left in a broken state — you'll get 500 with "Slot created but scheduling alarm failed — left as draft."
curl -X POST https://mcp.vlozi.app/tools/content.schedule_content \
-H "Authorization: Bearer $VLOZI_API_KEY" \
-d '{
"caption": "We just shipped OAuth login for MCP clients.",
"contentType": "text",
"connectionIds": ["conn_xxx"],
"scheduledAt": "2026-08-25T09:00:00+00:00",
"autoApprove": true
}'content.create_recurring_schedule
Create a repeating template that spawns a new slot on every cron fire — unlike schedule_content, which creates exactly one slot immediately.
Scope: content:recurring.write
Input:
| Field | Type | Notes |
|---|---|---|
name |
string (required) |
|
cronExpression |
string (required) |
Standard 5-field cron |
timezone |
string (required) |
IANA |
templateCaption |
string (required) |
|
templateMediaIds |
string[] |
Max 10 |
targetConnectionIds |
string[] (required) |
1–50 ids |
autoSchedule |
boolean |
Default false — mirrors schedule_content's autoApprove for each generated slot |
Response: { scheduleId: string }
NOTE
This creates the recurring template only — no slot exists yet until the cron actually fires and generates one from templateCaption/templateMediaIds/targetConnectionIds.
content.delete_slot
Delete a scheduled or already-published slot.
Scope: content:slots.write
Input: { id: string } (required)
Response: { slotId: string, deleted: true, platformWarnings: { target, platform, reason? }[] }
WARNING
Returns 409 if the slot is currently publishing — wait for it to finish first. If the slot was published, this also attempts to retract the live post on each platform; platformWarnings reports per-platform failures (a missing/expired connection, or the platform's API rejecting the delete). Not every platform supports deletion — Instagram's Graph API has no post-deletion endpoint at all (always warns), and LinkedIn has no delete support implemented. These warnings do not block deletion — the slot is removed from Vlozi's calendar regardless, so a "deleted" slot can still be live on Instagram/LinkedIn with only a warning to show for it.
Review
content.approve_slot
Approve a slot pending review, moving it to scheduled.
Scope: content:slots.approve
Input: { id: string, note?: string }
Response: { slotId: string, status: "scheduled" }
NOTE
Only works on a slot currently pending_review — 409 otherwise. Requires the slot to already have a scheduledAt set — 422 if not. This is the one human-review gate in the namespace; there's no separate "publish now" tool.
content.reject_slot
Reject a slot pending review, returning it to draft so it can be edited and resubmitted.
Scope: content:slots.approve
Input: { id: string, note: string } (note required — the rejection reason)
Response: { slotId: string, status: "draft" }
NOTE
Only works on a slot currently pending_review — 409 otherwise.
Campaigns & labels
content.create_campaign
Create a content campaign.
Scope: content:campaigns.write
Input: { name: string, color?: string, startDate?: string, endDate?: string } (name 1–200 chars; color hex, max 32 chars)
Response: { campaign: { id, name, status: "active" } }
content.create_label
Create a content label.
Scope: content:labels.write
Input: { name: string, color?: string } (name 1–100 chars; color hex, max 32 chars)
Response: { label: { id, name, color } }
NOTE
Returns 409 if a label with this name already exists (unique per workspace).
AI
content.generate_caption
AI-write a caption from a brief, tuned for a specific platform's length/style conventions.
Scope: content:slots.write
Input: { brief: string, platform?: "twitter" | "instagram" | "linkedin" | "facebook", tone?: string } (brief 1–2000 chars; tone max 100 chars)
Response: { caption: string, creditsCharged: number, model: string }
IMPORTANT
Charges a flat 5-credit fee, refunded automatically if generation fails. Returns 503 if no AI model key is configured, 402 if you're out of credits, 403 if the plan is inactive. Passing platform: "twitter" steers the model toward a strict under-280-character result.
curl -X POST https://mcp.vlozi.app/tools/content.generate_caption \
-H "Authorization: Bearer $VLOZI_API_KEY" \
-d '{"brief": "Announcing OAuth login support for MCP clients", "platform": "twitter", "tone": "excited"}'content.suggest_hashtags
AI-suggest hashtags for a topic.
Scope: content:slots.write
Input: { topic: string, count?: number } (topic 1–2000 chars; count 1–15, default 8)
Response: { hashtags: string[], creditsCharged: number, model: string } — each entry prefixed with #, deduplicated
IMPORTANT
Charges a flat 3-credit fee, refunded automatically if generation fails. Same 503/402/403 error conditions as generate_caption.