Approval-aware publishing, from scripts or AI assistants.
Use workspace-scoped API keys to create drafts, request client approval, schedule posts and inspect publishing jobs. The same keys work with the built-in MCP server, so an AI assistant follows the same approval rules as the app.
# Create a draft in the workspace this key belongs to
curl https://plansend.io/api/v1/posts \
-H "Authorization: Bearer $PLANSEND_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Launch week","body":"Ready to review.","channel_ids":[12,15]}'# Ask the client to approve it
curl -X POST https://plansend.io/api/v1/posts/481/request-approval \
-H "Authorization: Bearer $PLANSEND_KEY" \
-H "Content-Type: application/json" \
-d '{"reviewer_email":"[email protected]"}'# → { "data": { "review_url": "https://plansend.io/review/…", "expires_at": "…", "emailed": true } }
Authentication
Keys belong to one workspace
Create and revoke keys in the app under Developers → API keys, which also shows each key's last use and request count. Send the key as a Bearer token on every request: Authorization: Bearer ps_…. The raw key is shown once when it is generated; PlanSend stores only a hash. Every call, successful or not, is written to the workspace audit log with the key id.
Scope
Allows
read
Listing and reading: workspace, channels, posts, jobs, publishing health and AI credits. The read-only MCP tools.
write
Everything in read, plus the post lifecycle (create, update, request approval, approve, schedule, cancel) and AI Assist suggestions.
admin
Everything in write, plus creating brand workspaces and inviting members. The key acts as the owner who created it, so that person must still be an owner.
Scopes are checked on the server for each request; a key never inherits a signed-in session. Each key is limited to 120 requests in a rolling minute, and AI Assist is additionally bounded by the workspace's monthly credits.
Version 1
REST endpoints
Base URL https://plansend.io/api/v1. Send and receive JSON; responses arrive under a data key, errors as { "error": { "code", "message" } } with a matching HTTP status. Writes that change a post require the current revision, so two people can never overwrite each other unknowingly.
GET/api/v1/workspacesread
The workspace this key belongs to
POST/api/v1/workspacesadmin
Create another brand workspace on the same billing account
POST/api/v1/invitationsadmin
Invite an editor or reviewer by email
GET/api/v1/channelsread
Connected channels and their status
GET/api/v1/mediaread
Ready media items that can be attached by id
POST/api/v1/mediawrite
Upload an image or video by its public https URL
POST/api/v1/media/presignwrite
Create a 10-minute direct-upload URL for a file
POST/api/v1/media/:id/completewrite
Verify a direct upload and make the item ready
DELETE/api/v1/media/:idwrite
Delete an unused media object
GET/api/v1/postsread
List posts; filter by status, author_id, channel_id, network, from and to
GET/api/v1/posts/:idread
One post with variants, media, review history, comments and jobs
POST/api/v1/postswrite
Create a draft with one variant per channel
PATCH/api/v1/posts/:idwrite
Update a draft (requires the current revision); omitted fields keep their value
POST/api/v1/posts/:id/request-approvalwrite
Create a review link for the current revision; add reviewer_email to send it by email
POST/api/v1/posts/:id/approvewrite
Approve a specific revision
POST/api/v1/posts/:id/schedulewrite
Schedule an approved revision at a local time in a time zone
POST/api/v1/posts/:id/publish-nowwrite
Queue an approved revision for the next worker run
POST/api/v1/posts/:id/cancelwrite
Cancel a scheduled post before the worker claims it
GET/api/v1/jobsread
Inspect publishing jobs, retries and errors
POST/api/v1/jobs/:id/retrywrite
Retry a confirmed failed job; ambiguous results remain blocked
GET/api/v1/ai/creditsread
AI Assist credits used and remaining this month
POST/api/v1/ai/assistwrite
Generate caption, hook, hashtag, rewrite or shorten suggestions (one credit)
Reference
Requests and responses
The post lifecycle in four calls. Ids, tokens and dates are illustrative; scheduled_at and run_at are UTC, local_datetime and timezone are what you sent.
Create a draft
POST /api/v1/posts with a write key. channel_ids come from GET /api/v1/channels; every channel must belong to the workspace. Optional media_ids attach ready items from the media library. The draft starts at revision 1.
POST /api/v1/posts/:id/request-approval moves the post to in_review and returns a review link that expires after 7 days. Reviewers need no account. Add reviewer_email to have PlanSend email the link (emailed tells you whether it did) and passcode to require a passcode on the review page; the passcode is never emailed. A scheduled post must be cancelled first.
Clients normally approve through the review link. When you hold the approval yourself, POST /api/v1/posts/:id/approve with the exact revision. Then POST /api/v1/posts/:id/schedule with the same revision, a local wall-clock time as YYYY-MM-DDTHH:mm and an IANA time zone. Workspaces that require approval reject an unapproved revision with 409 approval_required; a stale revision is 409 stale_revision. Scheduling creates one queued job per channel; the worker publishes at that time.
GET /api/v1/jobs returns the 200 most recent jobs of the workspace, one per channel variant and revision. status is queued, processing, retry, published, failed, cancelled or reconciliation_required; the last one means the network may already have accepted the post and a person must check before retrying. GET /api/v1/posts/:id includes the same jobs for one post.
Create an endpoint under Developers → Webhooks and subscribe it to post.published, post.failed, post.approved or channel.disconnected. Each endpoint receives its own whsec_… signing secret once. Deliveries retry after about 1, 5 and 30 minutes, then remain visible as failed in the delivery history.
Verify PlanSend-Signature before parsing the JSON. Read PlanSend-Timestamp, concatenate it with a period and the exact raw request body, compute HMAC-SHA256 with the endpoint secret, and compare the hexadecimal digest to the value after v1=. Reject old timestamps to prevent replay.
verify.js
const expected = crypto
.createHmac('sha256', process.env.PLANSEND_WEBHOOK_SECRET)
.update(`${req.get('PlanSend-Timestamp')}.${req.rawBody}`)
.digest('hex');
const supplied = req.get('PlanSend-Signature').replace('v1=', '');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(supplied))) throw new Error('Invalid signature');
Errors
One shape, meaningful codes
Every error is { "error": { "code": "…", "message": "…" } } with an HTTP status you can branch on. code is stable; message is for people. MCP tools return the same codes inside a tool error (see below).
Status
Code
When
401
invalid_api_key
The Authorization header is missing, or the key is unknown or revoked.
403
insufficient_scope
The key's scope is below what the endpoint or tool needs.
403
forbidden, billing_owner_required
Admin endpoints when the key's creator is no longer an owner of the workspace or billing account.
404
not_found
The post, workspace or invitation does not exist in this workspace. Other workspaces' ids are never revealed.
409
stale_revision
The revision you sent is not the current one: someone edited the post. Read it again and retry with the new revision.
409
approval_required
The workspace requires approval and the current revision is not approved.
409
invalid_state
The action does not fit the post's status: editing a published post, cancelling a draft, reviewing a scheduled post.
409
reconciliation_required, publish_started
Publishing already started for this revision; a person must check the network before retrying or cancelling.
409
brand_limit, editor_limit, member_exists
Plan allowances and duplicates on the admin endpoints.
AI Assist: bad request, no credits left this month, or the provider is not configured or busy (the credit is refunded).
Model Context Protocol
Connect an AI assistant
The MCP server at https://plansend.io/mcp exposes PlanSend as tools over Streamable HTTP: list channels, create drafts, request approval, approve a revision, schedule, inspect jobs and draft copy with AI Assist. It authenticates with the same API keys, and scopes, revision checks and the audit log apply exactly as they do for the REST API. An assistant can prepare a week of posts, but every post still goes through the review and scheduling rules; nothing is published on the spot.
Create an API key under Developers → API keys. Use read scope for an assistant that only reports, write scope for one that drafts and schedules.
Add the server to your client with one of the snippets below, replacing YOUR_KEY.
Ask: “List my channels”, “Draft three LinkedIn posts about the launch and send them for approval”, “What is scheduled next week?”
Claude Code
One command; the header carries your key. Check with claude mcp list or /mcp inside a session.
Cursor reads .cursor/mcp.json in the project or ~/.cursor/mcp.json. Windsurf uses the same shape in ~/.codeium/windsurf/mcp_config.json, with serverUrl in place of url.
Bridge through mcp-remote (needs Node.js). In Claude Desktop open Settings → Developer → Edit Config and add the server to claude_desktop_config.json. On Windows, pass the header through an environment variable: "--header", "Authorization:${PLANSEND_AUTH}" with "env": { "PLANSEND_AUTH": "Bearer YOUR_KEY" }.
claude_desktop_config.json
{
"mcpServers": {
"plansend": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://plansend.io/mcp", "--header", "Authorization: Bearer YOUR_KEY"]
}
}
}
# the same bridge from a terminal:# npx mcp-remote https://plansend.io/mcp --header "Authorization: Bearer YOUR_KEY"
Any Streamable HTTP client
Send JSON-RPC 2.0 messages with POST. The server is stateless: no session id, no server-to-client stream (GET and DELETE answer 405), plain JSON responses. A missing or revoked key answers 401.
Each tool describes its arguments to the client, so the assistant fills them in itself. Results carry the same JSON as the REST data payload in the first text block and a one-sentence summary in the second. Failures come back as a tool error with { "error": code, "message": text } using the codes above, so a stale revision or a missing approval is explained rather than crashing the conversation.
Tool
What it does
Scope
list_workspaces
The workspace this key belongs to: time zone, plan and whether approval is required before scheduling
read
list_channels
Connected channels with the ids used as channel_ids
read
list_posts
Posts by status, channel, network or scheduled date range
read
get_post
One post with variants, media, review history, comments and jobs
read
list_jobs
Publishing jobs, newest first, with status, attempts and the last error
read
list_media
Media library items that can be attached by id
read
get_publish_health
Queue counts by status and the most recent failures
read
ai_credits
AI Assist credits used and remaining this month
read
upload_media
Add an image or video to the media library by its public https URL
write
create_draft
Create a draft with one variant per channel; nothing is scheduled
write
update_draft
Change title, body, channels or media; creates a new revision and clears approval
write
request_approval
Put a post in review and return the review link, optionally emailed to the reviewer
write
approve_post
Approve one exact revision
write
schedule_post
Schedule an approved revision at a local time; the worker publishes it
write
cancel_scheduled_post
Take a scheduled post off the queue before publishing starts
write
ai_assist
Caption, headline, hashtag, rewrite or shorten suggestions for a person to review (one credit)
write
Two resources are readable as well: plansend://posts/{postId} (the same object as get_post) and plansend://calendar/{from}/{to} for scheduled posts in an inclusive YYYY-MM-DD range. There is deliberately no “publish now” tool: publishing happens through scheduling, in the worker, with the idempotency guarantees below.
Guarantees
What the API promises
Revision safety
Approvals, schedules and edits reference a revision number. A stale revision is rejected with 409 stale_revision instead of silently winning, and every content edit clears earlier approvals.
Idempotent publishing
Each publish job carries an idempotency key per variant and revision, so retries after a network failure never double-post.
Tenant isolation
Every query is scoped to the key's workspace. There is no way to address another workspace's posts, channels or jobs.
Audit trail
Every API and MCP action is recorded with the key id, action and outcome, and is visible to workspace owners.
Cookies on PlanSend. Strictly necessary cookies keep you signed in. With your permission we also use Google Analytics to learn which pages are useful. How we use cookies.