---
name: posteveryplatform
description: Run a person's social media through PostEveryPlatform's REST API with curl. Plan, write, schedule and post to X, LinkedIn, Instagram, Facebook, TikTok, YouTube, Threads, Bluesky, Pinterest, Reddit, Google Business and Mastodon; upload images and video; keep an ideas board; read stats and the best time to post; read and reply to comments. Use when the user asks you to post, schedule, plan a content week, check how posts did, or answer comments. Needs the env var PEP_API_KEY.
---

# PostEveryPlatform for AI agents

PostEveryPlatform sends one post to many social platforms at the time you pick. You work through its REST API with
`curl`. Every call acts for one workspace with the permissions the owner gave your key. **The owner stays in charge:
unless they said otherwise, anything you schedule or post waits for their approval before it goes out.**

## Setup

- Key: `PEP_API_KEY` (starts with `pep_live_`). The owner makes it in PostEveryPlatform under Settings > AI agents.
  Never print it, never put it in a post, never send it anywhere except the API.
- Base URL: `${PEP_URL:-https://posteveryplatform.com}/api/v1`
- Every request: `-H "Authorization: Bearer $PEP_API_KEY"`. Send JSON with `-H "Content-Type: application/json"`.
- Full reference: https://posteveryplatform.com/docs/api (machine-readable: `/api/v1/openapi.json`).

```bash
PEP="${PEP_URL:-https://posteveryplatform.com}/api/v1"
curl -s "$PEP/me" -H "Authorization: Bearer $PEP_API_KEY"
```

**Call `GET /me` first, every session.** It tells you:

- `workspace.timezone`: the owner's timezone. Use it for every time you talk about or send.
- `now`: the current time in that timezone. Do not guess today's date; read it here.
- `token.scopes`: what you may do (`read`, `draft`, `schedule`, `publish`, `reply`).
- `approval.required`: whether your scheduled and post-now posts wait for the owner.
- `limits`: requests per minute, accounts and posts per month on their plan.

## What you can do

| Permission | Lets you |
|---|---|
| `read` | List accounts, posts, ideas, stats, best times and comments |
| `draft` | Write and change drafts, ideas and media; delete unsent posts |
| `schedule` | Schedule posts, use the posting queue |
| `publish` | Post right away (`when: "now"`); retry failed posts (only if this key's posts skip approval, because a retry sends at once) |
| `reply` | Reply to comments |

You can never connect or disconnect social accounts, change billing, change the workspace, or approve posts. Those are
the owner's, in the app. If a call answers `403` with code `missing_scope`, tell the user which permission the owner
would need to add, and stop; do not look for a way around it.

## The approval rule (read this)

- Drafts (`when: "draft"`) save right away and never need approval.
- When `approval.required` is true, a post you schedule (`when: "schedule"` or `"queue"`) or post now (`when: "now"`)
  comes back with `"status": "pending_approval"` and `"waiting_for_approval": true`. It goes out only after the owner
  approves it in the app. **Tell the user plainly**, for example: "I scheduled 5 posts for next week. They are waiting
  for your approval in PostEveryPlatform (Settings > AI agents)."
- Approved posts go out at their time (or within a minute when that time has passed). Rejected posts come back as
  `draft`, sometimes with `approval_note`: the owner's reason. Read it before you rewrite.
- Changing a scheduled post puts it back in front of the owner for approval.
- Never say a post "is live" or "went out" until `GET /posts/{id}` shows `published` and a `url` on its targets.

## Times and timezones

- Send `scheduled_at` as ISO 8601. **Without an offset it is read in the workspace timezone**: `"2026-11-03T09:00"`
  means 9:00 for the owner. With an offset or `Z` it is exact: `"2026-11-03T09:00:00-05:00"`. Unix seconds work too.
- Every time comes back in the workspace timezone with its offset, e.g. `"2026-11-03T09:00:00-05:00"`.
- When the user says "tomorrow at 9", work it out from `now` in `GET /me`, then send `"YYYY-MM-DDT09:00"`.
- No time given? Use `"when": "queue"`: the post takes the next free slot on the owner's posting schedule.
  `GET /queue/next?count=5` shows the next free slots.

## Accounts

```bash
curl -s "$PEP/accounts" -H "Authorization: Bearer $PEP_API_KEY"
```

Each account has an `id`, a `platform` (`x`, `linkedin`, `instagram`, `facebook`, `tiktok`, `youtube`, `threads`,
`bluesky`, `pinterest`, `reddit`, `google`, `mastodon`) and `can_post`. Pick accounts by id with `account_ids`, or by
platform with `platforms: ["x", "linkedin"]` (every connected account on those platforms). If the user asks for a
platform that is not connected, say so; only the owner can connect one, in the app.

## Write a post

```bash
curl -s -X POST "$PEP/posts" -H "Authorization: Bearer $PEP_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: launch-huila-2026-11-03" \
  -d '{"text": "New single origin from Huila just landed.", "platforms": ["x", "linkedin"], "scheduled_at": "2026-11-03T09:00"}'
```

- `when`: `draft` (default without a time), `schedule` (default with `scheduled_at`), `now`, or `queue` (needs `schedule` and `read`).
- Different text per platform: `"overrides": {"x": "Short version for X", "linkedin": "The longer version"}`
  (keys are platforms or account ids).
- `first_comment`: posted as the first comment where the platform allows it.
- Reddit needs `"reddit": {"subreddit": "coffee", "title": "..."}`.
- `Idempotency-Key`: send a unique value per post. If a request times out, send it again with the same key: you get the
  first answer back instead of a second post.

Per-platform limits are checked for you. A post that breaks one comes back as `422` with every problem listed:

```json
{"error": {"code": "validation_failed", "message": "X: 37 characters over the 280 limit", "problems": ["X: 37 characters over the 280 limit"]}}
```

Fix each problem (shorten with an override, add the missing image, pick a subreddit) and send it again. Instagram and
TikTok need media; YouTube needs a video; Pinterest needs an image.

## Images and video

```bash
curl -s -X POST "$PEP/media" -H "Authorization: Bearer $PEP_API_KEY" -F "file=@photo.jpg"
# -> {"media": {"id": "med_...", "url": "...", "mime": "image/jpeg"}}
```

Then pass `"media_ids": ["med_..."]`: up to 10 images, or 1 video. JPG, PNG, WebP, GIF, MP4, MOV, WebM, up to 95 MB.

## Other calls

| Call | What it does |
|---|---|
| `GET /posts?from=...&to=...&status=scheduled,pending_approval` | Posts whose time is in the window (default: 7 days back to 30 ahead) plus drafts without a time. Waiting posts have a time: widen the window to see them all |
| `GET /posts/{id}` | One post with status, live links and errors per account |
| `PATCH /posts/{id}` | Change only the fields you send |
| `DELETE /posts/{id}` | Remove a draft, scheduled or waiting post (sent and failed ones are only hidden; sent ones stay on the platforms) |
| `POST /posts/{id}/retry` | Send the failed accounts of a post again |
| `GET /ideas`, `POST /ideas`, `PATCH /ideas/{id}`, `DELETE /ideas/{id}` | The ideas board (`idea`, `drafting`, `ready`) |
| `GET /queue/next?count=5` | The next free slots on the posting schedule |
| `GET /analytics?days=30` | Posts sent, views, engagement, streak, top posts |
| `GET /analytics/posts?days=30` | Numbers per post per account |
| `GET /analytics/best-time` | Average engagement by weekday and hour |
| `GET /comments?filter=needs_reply` | Comments on posts from the last 14 days |
| `POST /comments/{id}/reply` | Reply from the account the comment is on |

## Errors and limits

Errors look like `{"error": {"code": "...", "message": "..."}}`. The message is written for people: you can show it.

| Status | Code | What to do |
|---|---|---|
| 400 | `invalid_request`, `invalid_time` | Fix the request (the message says what) |
| 401 | `unauthorized`, `invalid_token` | The key is missing, wrong or revoked. Ask the user for a new one |
| 402 | `plan_limit` | The plan's limit is reached (Free: 15 posts a month, 3 accounts). Tell the user; `upgrade: true` means a bigger plan removes it |
| 403 | `missing_scope`, `forbidden` | Not allowed for this key. Tell the user; do not retry |
| 404 | `not_found` | Wrong id, or it belongs to another workspace |
| 409 | `conflict` | The post already went out, or the queue is paused or full |
| 422 | `validation_failed` | Platform limits: fix every item in `problems` |
| 429 | `rate_limited` | Wait `Retry-After` seconds (Free: 30 requests a minute, paid plans: 120) |

## Example: plan next week

1. `GET /me` for the timezone, `now` and the approval rule. `GET /accounts` for what is connected.
2. Look at what worked: `GET /analytics/posts?days=30` and `GET /analytics/best-time`.
3. Check what is already planned: `GET /posts?from=<next Monday>&to=<next Sunday>`. Do not double-book.
4. Use the user's ideas: `GET /ideas`. Ask the user about anything you are unsure of before you write.
5. Write each post with `scheduled_at` in local time (or `"when": "queue"`), one `Idempotency-Key` per post, and
   `overrides` where a platform needs a shorter or different text.
6. Tell the user what you scheduled, when, and where, and that the posts wait for their approval if
   `waiting_for_approval` is true.

## Example: post a link to X and LinkedIn

```bash
curl -s -X POST "$PEP/posts" -H "Authorization: Bearer $PEP_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: blog-dial-in-guide" \
  -d '{"text": "How we dial in espresso every morning: https://example.com/dial-in",
       "platforms": ["x", "linkedin"], "when": "now",
       "overrides": {"linkedin": "Every bag behaves differently. Here is the 10-minute routine our baristas use to dial in espresso each morning: https://example.com/dial-in"}}'
```

`when: "now"` needs the `publish` permission. Without it, schedule a few minutes ahead (`schedule` permission) or save
a draft and tell the user.

## Example: answer comments

1. `GET /comments?filter=needs_reply` (only comments where replying is possible and nobody replied yet).
2. Draft replies that sound like the owner: short, warm, specific. Skip spam and anything heated; tell the user instead.
3. Show the user your replies before you send, unless they told you to go ahead.
4. `POST /comments/{id}/reply` with `{"text": "..."}`. Respect `reply_limit` (characters) on each comment.

## Good manners

- Ask before you post anything on someone's behalf that they have not seen, unless they told you to go ahead.
- Never invent numbers, quotes, reviews or facts in a post. Use what the user gave you or what the API returned.
- Report what happened in plain words: what is scheduled, what waits for approval, what failed and why.
