# Monkeygun API — full endpoint reference Base URL: https://api.monkeygun.com/v1 Auth: `Authorization: Bearer sk_live_…` on everything marked (key). A key is the account — create one with POST /v1/keys. Machine-readable contract: https://monkeygun.com/openapi.yaml Concise map: /llms.txt · Human docs: /docs ## Discover What your data supports, and what it would cost, before you spend anything. - `POST /v1/stories` (key) — Every story your data supports — numbers to the relational engine, prose to Arc. Returns a ranked list with the evidence behind each. - `POST /v1/estimate` (key) — What a render costs at a given look + fidelity. - `GET /v1/templates` — Catalog, credit packs, top-up bounds and bonus tiers, payment rails. - `GET /v1/looks` — All 33 looks with real computed prices. No key needed. - `GET /v1/presets` — Beat-sheet preset catalog. ## Render Order a film and collect it. A quote is a ceiling; you are billed from the shot plan. - `POST /v1/render` (key) — Order a film. Bearer key + credits. - `GET /v1/jobs/:jobId` — Status, progress, queue position, result URL. - `GET /v1/jobs/:jobId/link` — A fresh download URL for a past render. Signed URLs last 7 days; the file is kept 90, so come back and re-sign rather than re-render. - `POST /v1/checkout` — Card checkout — {kind:"render"} for one film, {kind:"credits", usd} to top up. - `GET /v1/checkout/:orderId` — Poll a paid order for its jobIds. ## Clip library Your own footage, pulled into a per-key store so films can be cut from it. Every pull needs the literal attest: true — you are confirming, per item, that the footage is yours to use, and we will not download it without that. Retail keys hold 20 clips for 30 days; the GET envelope restates your limits so you never hardcode them. - `POST /v1/clips` (key) — Pull a span (default: the first 15s, max 120s) of a YouTube or Vimeo video into your store. Body: {youtubeUrl, attest: true, startSec?, endSec?, label?}. A label at ingest is free — you know what your footage shows. - `POST /v1/clips/batch` (key) — Up to 20 items in one call, each carrying its OWN attest: true — a batch-level flag is never accepted, because N downloads is N consents. Per-item results in the order sent; one refusal never takes its neighbours down. - `GET /v1/clips` (key) — Your clips — only ever yours — with the tier's limits beside the rows. - `PATCH /v1/clips/:clipId` (key) — Write the label yourself: one line, what it shows / when to use it. Free, and your words always overwrite a scan's. - `POST /v1/clips/scan` (key) — A model looks at your unlabeled clips and writes the labels. Quote-first: the first call prices it (2 credits per ≤10s clip) and charges nothing — resend with confirm: true to spend. The quote is a ceiling; any label that was not written is refunded. - `DELETE /v1/clips/:clipId` (key) — Row and bytes together. Frees the slot immediately. ## Images Find stills for image-hungry looks, then send the ones you picked into the render as a top-level assets object: {assets: [{url, source?, attest?}], rightsConfirmed: true}. rightsConfirmed must be the literal true, checked before any money moves. - `GET /v1/images/search?q=` (key) — One query across four pools — library (your stored clips), site (your own pages), pexels, web. Each candidate carries its license in plain words, an off-image attribution line when the license wants one, and requiresAttestation. The web pool is always requiresAttestation: true — a web image renders only if you send it with source:"web" and its own attest: true, and the render refuses otherwise. A pool that fails answers {pool, error} beside the ones that worked. ## Keys and credits A key is the account. There are no users, sessions or passwords — send an email at creation and you can get a lost key back. - `POST /v1/keys` — Create a key — returns the secret and the webhook signing secret, once. Send `email` to make it recoverable. - `GET /v1/keys/me` — Balance, tier, wholesale rate, usage. - `POST /v1/keys/recover` — Email yourself a recovery link. Always 202 — we will not confirm whether an address has a key. - `POST /v1/keys/recover/confirm` — Spend the link's token. ROTATES the secret on your existing key: credits, feeds, triggers and Buffer all survive, the old secret dies. - `POST /v1/promo/redeem` (key) — Turn a promo code into credits on your key. - `GET /v1/keys/me/providers` (key) — Which of your own provider keys we hold (fal, OpenRouter, ElevenLabs). - `PUT /v1/keys/me/providers` (key) — Store one of your own provider keys — your render, your quota. - `DELETE /v1/keys/me/providers` (key) — Remove one. ## Account keys The optional front door for teams: a Supabase login that holds several API keys. Auth here is the Supabase access token, not an sk_live key — in the Authorization header on the GET (a GET carries no body), in the body as `token` everywhere else, so it never lands in an access log. Once linked, you use your key like everyone else. - `GET /v1/auth/keys` — Your account's keys — labels, timestamps and a mask, never a secret. Send the Supabase token as `Authorization: Bearer `. - `POST /v1/auth/keys` — Mint an additional key. Body: {token, label?}. The plaintext crosses the wire exactly once, in this response — after that it is reveal or nothing. - `POST /v1/auth/keys/:keyId/reveal` — Show one key's secret again, deliberately — its own POST (body: {token}) so the secret only travels when you ask for it. - `DELETE /v1/auth/keys/:keyId` — Revoke one key. Body: {token}. Refused in plain words when it is your last. ## Publish without a human Register a data source, say what should trigger a film, and connect Buffer. Everything published passes a confidence gate first, and anything below it holds for review instead. - `POST /v1/feeds` (key) — Register a data source: url, poll interval, and what the columns MEAN. - `GET /v1/feeds` (key) — Your feeds, with last poll, last change and last error. - `PATCH /v1/feeds/:feedId` (key) — The kill switch. Stops a feed dead — reach for this first, diagnose second. - `POST /v1/feeds/:feedId/triggers` (key) — Fire a render when a condition on the feed is met. The template must be a story template. - `GET /v1/holds` (key) — Films the gate declined to publish on its own authority. - `POST /v1/holds/:jobId/approve` (key) — Release a held film. - `POST /v1/holds/:jobId/reject` (key) — Bin it. - `PUT /v1/keys/me/buffer` (key) — Store your Buffer token, encrypted and scoped to your key. - `GET /v1/keys/me/buffer` (key) — Whether it is configured, plus every channel it reaches with its service — that is how we know which socials you have. - `DELETE /v1/keys/me/buffer` (key) — Disconnect. ## Data adapters Snapshots we can read for you, when you would rather not fetch them yourself. - `GET /v1/adapters/markets?symbol=` — Stock / ticker snapshot. - `GET /v1/adapters/github?repo=` — GitHub repo snapshot. ## Quickstart curls `BASE=https://api.monkeygun.com/v1` and `KEY=sk_live_…` throughout. ### 1. Find the stories ```sh curl -X POST $BASE/stories \ -H "authorization: Bearer $KEY" -H "content-type: application/json" \ -d '{ "business": "Freightline", "fields": [ {"key":"deposits","role":"stock","unit":"usd","noun":"in deposits"}, {"key":"sellers","role":"population","agents":"sellers"} ], "metrics": {"deposits": 48000, "sellers": 120} }' ``` ### 2. Render the one you picked ```sh curl -X POST $BASE/render \ -H "authorization: Bearer $KEY" -H "content-type: application/json" \ -d '{ "template": "monkeygun-story", "storyId": "arc_9f2c1d40", "options": {"look":"noir","fidelity":"standard"}, "webhookUrl": "https://yours.example/hook" }' ``` ### 3. Collect ```sh curl $BASE/jobs/$JOB_ID \ -H "authorization: Bearer $KEY" # → {"status":"done","progress":1,"result":{"mediaUrl":"https://…"}} # URL expired? GET $BASE/jobs/$JOB_ID/link re-signs it ({"url":…}) — the file is kept 90 days ``` ### Bring your own images (assets ride at the top level of /render) ```sh # find candidates across the four pools curl "$BASE/images/search?q=warehouse+robots&pools=library,site,pexels,web" \ -H "authorization: Bearer $KEY" # → candidates tagged {pool, url, license, attribution?, requiresAttestation} # render with the images you picked — assets ride at the TOP LEVEL of the body curl -X POST $BASE/render \ -H "authorization: Bearer $KEY" -H "content-type: application/json" \ -d '{ "template": "monkeygun-story", "storyId": "arc_9f2c1d40", "assets": { "assets": [ {"url": "https://yours.example/product.jpg"}, {"url": "https://found.example/shot.jpg", "source": "web", "attest": true} ], "rightsConfirmed": true } }' # rightsConfirmed must be the literal true, and every source:"web" image needs # its own attest: true — the render refuses otherwise, before any money moves ``` ### Token films in one call (no discovery step) ```sh # 1) Catalog curl -s https://api.monkeygun.com/v1/templates | jq '.templates[].id' # 2) Card checkout (browser return URL) curl -s -X POST https://api.monkeygun.com/v1/checkout \ -H 'content-type: application/json' \ -d '{ "kind": "render", "template": "spotlight-clip", "chain": "solana", "tokenAddress": "", "successUrl": "https://monkeygun.com/studio?order={ORDER_ID}", "cancelUrl": "https://monkeygun.com/studio" }' # 3) Or with credits curl -s -X POST https://api.monkeygun.com/v1/render \ -H "authorization: Bearer sk_live_…" \ -H 'content-type: application/json' \ -d '{ "template": "spotlight-clip", "chain": "solana", "tokenAddress": "" }' ```