# Everpop — agent skill

Everpop turns uploads (and, when enabled, prompts) into published vertical shorts — on YouTube
Shorts, Instagram Reels, Facebook Reels and TikTok — with animated captions, then
measures real performance ("receipts") for eligible YouTube Shorts via the
YouTube Analytics API. This file teaches an AI agent to use the public API.

Base URL: https://everpop.app
Auth: `Authorization: Bearer epk_...` (create a key at Dashboard → Settings → API keys)

## Generate a short from a prompt (currently paused — returns 403 `{reason:"disabled"}` until re-launch)

(REST API only — this endpoint is intentionally **not** part of the MCP
connector. The MCP tool surface works exclusively on the creator's own
uploaded footage: upload, clip, publish, receipts.)

POST /api/v1/generate
```json
{
  "prompt": "3 psychology tricks that make people instantly like you",
  "clipCount": 1,
  "voice": "nova",            // alloy | echo | fable | onyx | nova | shimmer
  "tone": "educational",      // educational | storytime | hype | calm
  "targetSeconds": 45,        // 20-60
  "captionStyle": "pop"       // pop | boxed | minimal | karaoke | bold (see openapi.json for the current enum)
}
```
→ `{ "ok": true, "data": { "jobId": "...", "poll": "/api/v1/jobs/{id}" } }`

Generation takes ~1–3 minutes: script → TTS voiceover → visuals →
word-timed animated captions → 9:16 MP4.

## Poll a job

GET /api/v1/jobs/{jobId}
→ `{ "data": { "status": "PENDING|PROCESSING|COMPLETED|FAILED", "clips": [{ "clipUrl": ..., "thumbnailUrl": ..., "title": ..., "durationSeconds": ... }] } }`

Poll every 15–30s until status is COMPLETED or FAILED.


## List the user's videos (to pick what to clip)

```bash
curl -s https://everpop.app/api/v1/videos \
  -H "Authorization: Bearer $EVERPOP_API_KEY"
```

## Upload the video file (upload-first — the compliant ingestion path)

Everpop clips from the creator's FILE, never from a YouTube download
(compliance cutover, 2026-07-02). Agents can deliver the file end-to-end —
no dashboard detour:

```bash
# 1. Request a presigned upload slot (write-scope key)
curl -s -X POST https://everpop.app/api/v1/videos/upload   -H "Authorization: Bearer $EVERPOP_API_KEY"   -H "content-type: application/json"   -d '{"filename":"episode-42.mp4","sizeBytes":734003200}'
# → { "ok": true, "data": { "putUrl": "...", "key": "...", "requiredHeaders": { "Content-Type": "video/mp4", "If-None-Match": "*" }, "expectedBytes": 734003200, "expiresInSeconds": 900, "next": "PUT exactly expectedBytes ... then POST /api/v1/videos/complete ..." } }

# 2. PUT exactly expectedBytes straight to storage (valid 15 min, up to 4 GB).
# Send every requiredHeaders entry; curl supplies Content-Length automatically.
curl -s -X PUT "$PUT_URL" -H "content-type: video/mp4" -H "if-none-match: *" --data-binary @episode-42.mp4

# 3. Finalize — rightsConfirmed must be the USER's answer, never assumed
curl -s -X POST https://everpop.app/api/v1/videos/complete   -H "Authorization: Bearer $EVERPOP_API_KEY"   -H "content-type: application/json"   -d '{"key":"<key from step 1>","title":"Episode 42","rightsConfirmed":true}'
# → { "ok": true, "data": { "videoId": "...", "title": "Episode 42", "existing": false, "linked": false, "autoClipping": true } }
# autoClipping true = a render is ALREADY running; optional parked:"quota" = uploaded safely, but monthly videos are used up
```

If the PUT fails or is abandoned, release the slot with the same durable key:

```bash
curl -s -X DELETE https://everpop.app/api/v1/videos/upload \
  -H "Authorization: Bearer $EVERPOP_API_KEY" \
  -H "content-type: application/json" \
  -d '{"key":"<key from step 1>"}'
```

A 503 with `reason: "cleanup_pending"` means provider cleanup is durably queued
and will retry automatically. If finalization returns a network error, 429, 5xx,
or `upload_not_ready`, retry finalization with the same key. Do not upload the
bytes again, and never cancel after a successful or ambiguously acknowledged
finalization.

Rules of the road:

- /clip on a video whose source is a YouTube URL returns **422 `upload_required`**
  — don't retry; upload the file (above), or pass the parked video's id as
  `linkSourceVideoId` in /videos/complete to ATTACH the file to it (un-parks it
  and keeps its YouTube attribution — the best option for detected uploads).
- Uploads **auto-clip on completion** for active subscribers, so after an upload
  check /videos and /jobs — clips may already be rendering.
- New uploads to a CONNECTED channel are still detected instantly; Everpop parks
  them and emails the creator — or ingests them AUTOMATICALLY if the creator
  connected a Drive "Finals folder" (Settings → Finals folder; zero-step).
- `POST /api/v1/videos/import` still registers a YouTube video's METADATA
  (own-channel only, idempotent) — pair it with linkSourceVideoId to upload
  its file in the same session.
- Files over 4 GB are not accepted on any path — split or re-encode the source.

## Clip moments from one of their videos (ClipAnything)

```bash
curl -s -X POST https://everpop.app/api/v1/clip \
  -H "Authorization: Bearer $EVERPOP_API_KEY" \
  -H "content-type: application/json" \
  -d '{"sourceVideoId":"<id from /videos>","prompt":"the most surprising moment, ending on a cliffhanger","clipCount":2,"targetSeconds":45,"captionStyle":"pop"}'
```

- `clipCount` is 1–5 (default 1).
- `prompt` is optional — omit it and the AI picks the best moments itself.
- Returns `data.jobId`; poll the same as generate.

## Publish a clip to exact connected destinations

First list the destination ids and show the user the exact platform/account:

```bash
curl -s https://everpop.app/api/v1/publish \
  -H "Authorization: Bearer $EVERPOP_API_KEY"
```

Then show the user the final title, description, visibility, and every selected
destination. Only after their explicit confirmation:

```bash
curl -s -X POST https://everpop.app/api/v1/publish \
  -H "Authorization: Bearer $EVERPOP_API_KEY" \
  -H "content-type: application/json" \
  -d '{"clipId":"<clipId from a COMPLETED /jobs/{id}>","destinationIds":["<id from GET /api/v1/publish>"],"metadata":{"title":"<reviewed title>","description":"<reviewed description or empty string>","privacyStatus":"public"}}'
```

- `clipId` comes from a COMPLETED job's `clips[].clipId`.
- `destinationIds` must contain the exact ids returned by `GET /api/v1/publish`;
  omission never means "all connected accounts."
- `metadata` is the final reviewed title/caption, description, and visibility.
- Idempotent per (clip, destination): a clip already published returns its existing post.
- Retries: send an `Idempotency-Key` header on `POST /api/v1/clip` and `POST /api/v1/generate` (MCP: `operationId`) so a retried request never mints a second metered render; without it, only option-less requests inside a 20-second window are deduplicated.
- **Only publish after the user explicitly confirms all destinations, metadata,
  and visibility** — provider publication is not easily undone.

## Pull receipts (real measured performance)

GET /api/v1/receipts
→ per eligible YouTube Short published through Everpop: 48h and 7d aggregate
metrics (views, retention %, likes, comments, shares, subscribers gained), a public token-protected
`receiptUrl`, and a `campaignReportUrl` bundling all measured clips. A receipt
may also contain a separately signed pre-publish prediction; the later measured
metrics are fetched from YouTube and are not separately signed. These links are
a measured report a campaign owner can open; the marketplace's own view count and snapshot date govern payout. Measured numbers are not cryptographically signed — the link token controls access only.

## Notes for agents

- Rate limits per account: 10 generate/min, 10 clip/min, 6 publish/min,
  60 videos/min. 402 means the account needs an active subscription.
  401 means bad/revoked key. 429 means slow down (a `Retry-After` may be set).
- 503 with "engine is offline" → Everpop's clipping service is briefly
  unavailable. Tell the user nothing was charged and retry the same request in
  a few minutes (the failed attempt is recorded as a failed job).
- Clip URLs are publicly fetchable MP4s (Cloudflare R2) — download and
  publish them anywhere, or let Everpop auto-publish to the connected channel.
- Measured receipts exist only for eligible clips published through Everpop to
  a connected YouTube channel. The displayed metrics are pulled from the
  YouTube Analytics API; they are not themselves cryptographically signed.

## Use it from Claude (MCP connector)

OAuth is supported: add `https://everpop.app/api/mcp/mcp` with NO key and Claude walks the
user through consent (tokens appear in Settings → API keys, revocable). The keyed URL below
also still works.

Everpop also runs a Model Context Protocol server. In Claude → Settings →
Connectors → Add custom connector:

    URL:     https://everpop.app/api/mcp/mcp
    Header:  Authorization: Bearer epk_...

Prefer the header — it keeps your key out of the URL, and therefore out of browser
history, server logs, referrers, and shared screenshots. If your client genuinely
can't send a header, the key can ride in the URL as a fallback
(`https://everpop.app/api/mcp/mcp?key=epk_...`) — but treat that whole URL like a
password, and rotate the key from Settings → API keys if it ever leaks.

### Available tools

- `list_videos` — List all source videos known to Everpop (detected, API, Drive, or dashboard uploads), newest first.
- `request_upload` — Exact-size presigned slot for the user's video FILE (sizeBytes is required; PUT bytes yourself with returned requiredHeaders, 15-min validity, ≤4 GB).
- `cancel_upload` — Cancel a failed/abandoned upload slot. A `cleanup_pending` result is durably queued; never cancel after successful or ambiguous finalization.
- `finalize_upload` — Turn the uploaded file into a clippable library video; auto-clips for active plans; `linkSourceVideoId` attaches it to a parked detection; optional `parked: "quota"` means the file is safe but monthly videos are used up.
- `import_video` — Import a specific YouTube video by URL/id when it isn't in `list_videos` yet (e.g. an older upload). Own-channel only; returns a videoId for `clip_video` after the file is uploaded (`request_upload` → `finalize_upload` with `linkSourceVideoId`).
- `clip_video` — Create short clips from one of your source videos; an optional prompt directs which moments to clip; returns a jobId.
- `get_job` — Check a clip/generation job; returns status, exact connected destinations, and, when COMPLETED, clips with video URLs and clipIds (use a clipId with `publish_clip`).
- `publish_clip` — Publish a READY clip to explicitly named destination ids with final title, description, and visibility. Confirm every account and field first — provider publication is not easily undone.
- `list_receipts` — Eligible YouTube Shorts' aggregate measured performance (48h & 7d views, retention, likes, subscribers gained) with token-protected share links.
- `create_campaign` — Create a paid-clipping campaign to group measured clips into one shareable payout report; returns a campaignId.
- `list_campaigns` — List your campaigns with post counts, measured views, CPM, and each campaign's public payout-report link.
- `attach_to_campaign` — Attach your published posts to a campaign so they count toward its payout report.
- `campaign_proof` — A campaign's measured payout report: per-clip 48h/7d numbers, totals, the estimated payout, and the public report link.

(The `import_video` MCP tool is own-channel only. To import licensed/third-party footage you hold the rights to, use the REST endpoint `POST /api/v1/videos/import` with `rightsConfirmed: true` — that rights-attestation path is intentionally not exposed over MCP.)
