# Recipes

Every example uses `Authorization: Bearer ap_live_…`. Base URL `https://apublished.com`.

## Connect a channel

You cannot complete OAuth yourself. Start a session, hand the URL to a human, poll.

```bash
curl -X POST https://apublished.com/v1/channels:connect \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"provider":"simulator"}'
```

```jsonc
{ "id": "…", "state": "pending", "url": "https://…", "pollUrl": "https://apublished.com/v1/connect-sessions/…" }
```

Poll `pollUrl` until `state` is not `pending`:

- `completed` — done, `channels` lists what was connected.
- `awaiting_subaccount` — the human must choose which Page or organization.
  Post the ids from `subAccountChoices` to `/v1/connect-sessions/{id}:select`.
- `abandoned` — they walked away. Sessions expire after 15 minutes.
- `failed` — they declined, or the platform refused. `error` says which.

## Schedule a post

Validate first. It costs nothing and it is the difference between an agent that
iterates and one that guesses.

```bash
curl -X POST https://apublished.com/v1/posts:validate \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{
    "body": { "items": [{ "text": "Hello" }] },
    "targets": [{ "channelId": "…", "settings": { } }]
  }'
```

Then create, with an idempotency key you can reuse on retry:

```bash
curl -X POST https://apublished.com/v1/posts \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "body": { "items": [{ "text": "Hello" }] },
    "targets": [{ "channelId": "…", "settings": { } }],
    "scheduleMode": "AT",
    "scheduledAt": "2026-09-01T09:00:00Z"
  }'
```

## Post a video

Upload first, so the file is validated and probed before it matters. Videos are
never transcoded — a file the platform will not take is refused with a reason
rather than silently re-encoded.

```bash
# 1. get a presigned URL
curl -X POST https://apublished.com/v1/media -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" -d '{"contentType":"video/mp4"}'

# 2. PUT the bytes straight to the returned uploadUrl (not through this API)
curl -X PUT "$UPLOAD_URL" --data-binary @video.mp4 -H "Content-Type: video/mp4"

# 3. tell us it is there
curl -X POST https://apublished.com/v1/media/$ASSET_ID:complete -H "Authorization: Bearer $KEY"

# 4. poll until status is READY, then reference assetId in a post
curl https://apublished.com/v1/media/$ASSET_ID -H "Authorization: Bearer $KEY"
```

Or hand us the URL and skip all of it:
`POST /v1/media:from-url` with `{"url":"https://…"}`.

## Handle a channel that needs reconnecting

This is routine, not exceptional. Do not treat it as an error state to retry.

```bash
curl https://apublished.com/v1/channels?status=NEEDS_REAUTH -H "Authorization: Bearer $KEY"
curl -X POST https://apublished.com/v1/channels/$ID:reauth -H "Authorization: Bearer $KEY"
```

Give the returned `url` to a human. The channel keeps its id, its schedule and its
queue when they finish.

## Recover after downtime

Webhooks get missed and agents restart. Do not walk every post — read the change
feed from the last cursor you saw:

```bash
curl "https://apublished.com/v1/changes?since=$CURSOR" -H "Authorization: Bearer $KEY"
```

Cursors are monotonic and retained for 30 days.

## Batch a week

Ask for free slots rather than inventing times: they respect the channel's own
schedule and skip slots that are already taken.

```bash
curl "https://apublished.com/v1/channels/$ID/slots?n=7" -H "Authorization: Bearer $KEY"
```

Or let create do it in one call with `"scheduleMode": "NEXT_SLOT"`. Add
`"randomizeQueueTime": true` for a ±10 minute jitter so the cadence does not look
automated.

## Draft for a human to approve

Use a key with `posts:write` but NOT `posts:publish`. Then `scheduleMode: "NOW"`
is refused, and everything the agent creates waits for a person. That is the whole
approval workflow — no extra API.
