# How to Run a Social Media Calendar with an AI Agent · PostLake > Markdown version of https://postlake.dev/guides/ai-content-calendar . The canonical page for humans. > PostLake is the social media API for AI agents: https://postlake.dev/llms.txt Let an agent fill the *calendar*. Round-ups keep sending this job to a calendar app you self-host. That is the wrong default when the agent is the one filling the week. PostLake already queues, timezones, cancels, reschedules, and keeps drafts fail-closed. The dashboard calendar is how a person checks the work. You do not install Postiz to get an agent-driven calendar. **In short:** Point Claude or Cursor at the hosted MCP. Ask it to draft a week of posts from a changelog, adapt per network, schedule with timezone, and keep them as drafts until you approve. The dashboard calendar is the review surface. No second scheduler product. ## When this guide is for you You want an agent to fill the content calendar, and a person to review it, without self-hosting Postiz or living in a drag-and-drop UI. ## Before you start Same setup the [docs quickstart](https://docs.postlake.dev/quickstart.md) uses. Do this once: - Sign up at [app.postlake.dev](https://app.postlake.dev) and verify your email (unlocks free credits). - On **Channels**, create a **profile** (e.g. `my-brand`) and connect at least one account. **Bluesky is the fastest first channel**: no app review. Instagram/TikTok/Facebook need each platform's review before API posting. - Account menu → **API Keys** → create a key (`sk_live_…`). It shows once; treat it like a password. ## The week, as drafts Same publish contract as the docs, plus scheduledAt, timezone, and draft: true. Credits do not charge until a draft is published and actually fires. ``` import os, uuid, requests from datetime import datetime, timedelta from zoneinfo import ZoneInfo HEADERS = { "Authorization": f"Bearer {os.environ['POSTLAKE_API_KEY']}", "Content-Type": "application/json", } ZONE = "Europe/London" def queue_week(items: list[dict]) -> list[dict]: """items: [{text, day_offset}] e.g. day_offset=1 is tomorrow 09:00 local.""" out = [] today = datetime.now(ZoneInfo(ZONE)).date() for i, item in enumerate(items): when = datetime( today.year, today.month, today.day, 9, 0, 0 ) + timedelta(days=item["day_offset"]) body = { "text": item["text"], "profile": "my-brand", "scheduledAt": when.strftime("%Y-%m-%dT09:00:00"), "timezone": ZONE, "draft": True, # nothing ships until you approve } r = requests.post( "https://api.postlake.dev/v1/posts", headers={**HEADERS, "Idempotency-Key": f"cal-{today}-slot-{i}"}, json=body, ) r.raise_for_status() post = r.json() print(post["id"], post["state"], post.get("scheduledAtLocal")) out.append(post) return out # MCP path (no key in chat): connect https://api.postlake.dev/mcp # then: "Draft 5 posts for this week from the changelog, adapt length # per platform, schedule them 24h out as drafts, do not publish until I approve." ``` ## Step by step 1. Connect accounts under one profile. Bluesky is the fastest smoke test. 1. Prefer MCP over a pasted key: add `https://api.postlake.dev/mcp` and approve over OAuth. 1. Call `validate_post` (free) before queueing a week, especially for TikTok photos. 1. Create each slot with `scheduledAt`, `timezone`, and `draft: true`. State should be `draft`. Cost is zero. 1. Open the dashboard calendar. Edit, move, or delete. When a slot is right, `publish_draft` (it keeps the scheduled time unless you clear it). 1. After they fire, `get_analytics` / `GET /v1/analytics` so next week's drafts are not a guess. ## Read the response (don't skip this) You get one `Post` with an overall `state` and a `targets[]` array. One entry per account. **Always check each target**; partial success is normal. ``` { "id": "post_a1b2c3", "state": "partial", "targets": [ { "platform": "bluesky", "state": "published", "url": "https://bsky.app/…" }, { "platform": "linkedin", "state": "failed", "error": { "type": "invalid_request", "message": "…", "retryable": false } } ] } ``` - `published`: every target succeeded. `partial`: some published, some failed. `failed`: none published. `processing`: still going (async networks like TikTok). - **You're only charged for targets that actually publish.** Failed targets cost nothing. - Send an `Idempotency-Key` header on writes so a retry never double-posts. - Full detail: [Publishing](https://docs.postlake.dev/publishing) · [Errors & retries](https://docs.postlake.dev/errors) ## Where the post goes Same rules as the docs. Pick one addressing style: - **By profile**: `"profile": "my-brand"` posts to every account under that profile (the name on Channels). - **Filter networks**: `"platforms": ["bluesky", "linkedin"]` narrows that set. It is a **filter, not a selector**: if you have two Pinterest boards, both match `pinterest`. - **By account id**: `"accounts": ["acc_…"]` for exact channels (copy an id on Channels, or `GET /v1/social-accounts`). - One profile and you omit `profile`? PostLake uses that profile. Multiple profiles and you omit it? You'll get an error that names them. See [Publishing: where to post](https://docs.postlake.dev/publishing). ## Do more (same API) - **Schedule**: add `scheduledAt` as UTC (trailing `Z`), or a naive local time plus `timezone` (IANA, e.g. `Europe/London`). Credits charge when it fires. [Scheduling docs](https://docs.postlake.dev/scheduling) · [scheduling guide](/guides/schedule-posts-api). - **Media**: `POST /v1/media`, then pass the `med_…` id in `media`. [Media docs](https://docs.postlake.dev/media). - **Per-network caption**: `textOverrides` (e.g. shorter text for X). **Per-network options**: `platformOptions` (Pinterest `boardId`, TikTok privacy, …). Live option lists: `GET /v1/platforms/{platform}`. - **Validate first**: `POST /v1/posts/validate` runs the same checks without publishing (free). - Approval-only path: [human approval for agents](/guides/human-approval-drafts). - Field reference: [scheduling docs](https://docs.postlake.dev/scheduling). - Why not self-host the calendar app: [Postiz alternative](/compare/postiz-alternative). ## Pitfalls specific to this path - A naive datetime without `timezone` is a 400. Trailing `Z` with no zone still fires at the wrong hour for half the year. - Do not start with immediate publish. Immediate auto-post is how accounts get restricted. Drafts first, for weeks. - Do not treat schedule-time 200 as live. Wait for fire-time target states, or a webhook. Prefer an assistant over AI content calendar code? Point it at [MCP](https://docs.postlake.dev/mcp) (`https://api.postlake.dev/mcp`). Prefer the dashboard? Use [Quickstart → No code](https://docs.postlake.dev/quickstart). ## Common questions ### How do I let an AI agent run a social media calendar? Connect PostLake's hosted MCP, ask it to draft and schedule the week with timezone, keep posts as drafts, and review them on the dashboard calendar. You do not need to self-host Postiz for this. ### Is PostLake a Postiz alternative for calendars? If an agent is filling the week: yes. If a person wants to live in a drag-and-drop calendar with RSS and in-app image generation: Postiz is still the better calendar app, and we would rather say so. ### What instruction should I give the agent? Draft 5 posts for this week from the changelog, adapt length per platform, schedule them 24 hours out as drafts, and do not publish until I approve. ### Can I cancel or move a slot? Yes, while it is draft or scheduled: PATCH to change copy or time, DELETE to pull it. Cancelling a scheduled post frees the Idempotency-Key. ## Go deeper in the docs These guides stay short on purpose. Canonical behaviour lives here: [Docs ### Quickstart Account, first channel, first post, MCP or API.](https://docs.postlake.dev/quickstart) [Docs ### Publishing Request body, addressing. Partial success, lifecycle.](https://docs.postlake.dev/publishing) [Docs ### Scheduling UTC or local time plus timezone, edit/cancel, fire-time credits.](https://docs.postlake.dev/scheduling) [Docs ### Platforms Limits, media rules, live platformOptions.](https://docs.postlake.dev/platforms) Also: [Media](https://docs.postlake.dev/media) · [Errors](https://docs.postlake.dev/errors) · [MCP](https://docs.postlake.dev/mcp) · [Analytics](https://docs.postlake.dev/analytics) ## Related guides - [Schedule posts via API](https://postlake.dev/guides/schedule-posts-api.md) - [Human approval (drafts)](https://postlake.dev/guides/human-approval-drafts.md) - [Build an AI social agent](https://postlake.dev/guides/ai-social-media-agent.md) - [Social media API for AI agents](https://postlake.dev/guides/social-media-api-for-ai-agents.md) [All guides](https://postlake.dev/guides/index.md) · [Full docs](https://docs.postlake.dev/) · [llms.txt](https://postlake.dev/llms.txt) Plain markdown for agents: https://postlake.dev/guides/ai-content-calendar.md · https://postlake.dev/llms.txt