# Human-in-the-Loop Social Posting for AI Agents (2026) · PostLake > Markdown version of https://postlake.dev/guides/human-approval-drafts . The canonical page for humans. > PostLake is the social media API for AI agents: https://postlake.dev/llms.txt Agents draft. You *approve*. A prompt that says 'always create a draft' is guidance, not a security boundary. PostLake lets the owner enforce approval on each OAuth agent. Once enabled, publish requests enter `awaiting_approval` at the API boundary and the same agent cannot approve them. Existing agents keep direct publishing until the owner turns the policy on. **In short:** Turn on Require publishing approval for an agent. PostLake forces its publish requests into the approval queue and prevents that agent from approving its own work. API-key workflows can use `draft: true` for an ordinary personal draft. ## When this guide is for you You want an agent to draft social posts, and a person to approve them, without a second approval product. ## 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. ## Create a draft, then publish it The owner sets the rule once. The agent writes, PostLake saves a draft, and only the owner or an account-level workflow can send it. ``` # 1. Agent writes. Nothing goes live. Nothing is charged. curl -X POST https://api.postlake.dev/v1/posts \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: recap-draft-001" \ -d '{ "text": "Friday recap from the changelog", "profile": "my-brand", "scheduledAt": "2026-09-04T09:00:00", "timezone": "Europe/London", "draft": true }' # A guarded agent receives state "awaiting_approval" automatically. # 2. After you read it: publish. Keeps the scheduled time unless you override. curl -X POST https://api.postlake.dev/v1/posts/post_a1b2c3/publish \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' # MCP OAuth: turn on Require publishing approval under Agents. create_post # then saves an approval request automatically, and the owner decides in PostLake. ``` ## Step by step 1. Open **Agents**, edit the connection's guardrails, and turn on **Require publishing approval**. 1. The agent writes with the same fields as a live post (text, media, overrides, scheduledAt). 1. List requests with `GET /v1/posts?state=awaiting_approval` or open **Posts → Awaiting approval**. 1. Review the post, then choose **Approve and publish**, **Approve and schedule**, **Edit**, or **Reject**. An approval request can change destinations before the owner decides. 1. The guarded agent can read its state but cannot call `publish_draft` or `POST /v1/posts/{id}/publish` to approve itself. A failed approval attempt moves into normal failed-post handling rather than back into the queue. ## 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). ## Pitfalls specific to this path - A failed publish does not consume the draft. If you treat a 4xx as 'it's gone', you will rewrite work that is still there. - Pasting a live key into chat is how keys leak. Prefer MCP OAuth so the agent can draft without ever seeing a secret. - Do not rely on a system prompt as the approval control. Enforce the policy on the OAuth connection so an omitted field cannot publish. Want zero wrapper code? Connect the hosted [MCP server](https://docs.postlake.dev/mcp) (`https://api.postlake.dev/mcp`) over OAuth. Same accounts and responses as this API path. [Agents overview](/agents/). ## Common questions ### How do I stop an AI agent from publishing until I approve? Turn on Require my approval for the agent under Agents. PostLake then saves every publish request from that OAuth connection as a draft, even if the agent omits draft: true, and prevents it from self-approving. Publish from PostLake after review. ### Is this like pendpost or Rolino? Same job (fail-closed approval), different shape. Those are local-first apps. PostLake is a hosted API: the draft lives next to scheduling, analytics and MCP, so the agent does not need a second vendor for the gate. ### Can a draft also be scheduled? Yes. Save it with scheduledAt. publish_draft keeps that time unless you send a new one, or clear it to publish immediately. ## 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 - [Agent content calendar](https://postlake.dev/guides/ai-content-calendar.md) - [Build an AI social agent](https://postlake.dev/guides/ai-social-media-agent.md) - [Manage social media agents](https://postlake.dev/guides/manage-social-media-agents.md) - [Schedule posts via API](https://postlake.dev/guides/schedule-posts-api.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/human-approval-drafts.md · https://postlake.dev/llms.txt