# Pagecuts API

Pagecuts turns a business's web page into a 15-second video ad kit. Every ad is made for a person whose email address has been confirmed with a code: one call emails the code, a second starts the build with it, a third follows it. There is no API key. Origin: `https://pagecuts.com`.

A build takes about five minutes. The preview is free and carries Pagecuts' mark. The kit is US$19, once, paid by a person at a Stripe checkout page: the ad in three shapes, six still images and Meta ad copy at once, and a few minutes later the rest, for YouTube, Google, Pinterest, LinkedIn, TikTok and Snapchat.

## Confirm the person's email

`POST /api/codes`

```json
{ "email": "sam@example-bakery.com" }
```

`200`: `{ "sent": true }`. Pagecuts emails that address a code of six digits, which works once, for an hour. Ask the person for it. Never use an address the person did not give you: the ad is saved to that address's account, and that is where "your ad is ready" is sent.

One code a minute and five an hour per address; ten an hour per connection.

## Start a build

`POST /api/requests`

```json
{ "url": "https://example-bakery.com", "email": "sam@example-bakery.com", "code": "407182" }
```

`instructions` is optional: up to 2,000 characters on what this ad should push and who it is for. It can add facts the page does not state, such as a new offer.

`url` is the page the ad should send people to. A bare domain is fine. It has to be a public web address.

`201`:

```json
{ "id": "5c0f6f0a-…", "page": "https://pagecuts.com/r/5c0f6f0a-…", "status": "https://pagecuts.com/api/requests/5c0f6f0a-…" }
```

`page` is for a person: it shows the build live, plays the preview and sells the kit. `status` is the address of the call below.

## Follow a build

`GET /api/requests/{id}`

Add `?wait=` (seconds, 25 at most) to hold the call until there is a preview or the build has failed.

```json
{
  "id": "5c0f6f0a-…",
  "page": "https://pagecuts.com/r/5c0f6f0a-…",
  "host": "example-bakery.com",
  "status": "preview_ready",
  "paid": false,
  "progress": [ { "step": "Reading your page", "stage": 1, "at": "2026-10-06T05:28:01.000Z" } ],
  "created_at": "…", "started_at": "…", "finished_at": "…", "server_now": "…",
  "preview": "https://pagecuts.com/out/5c0f6f0a-…/preview.mp4",
  "cover": "https://pagecuts.com/out/5c0f6f0a-…/cover.jpg",
  "stills": [ "https://pagecuts.com/out/5c0f6f0a-…/still-preview-1.jpg", "https://pagecuts.com/out/5c0f6f0a-…/still-preview-2.jpg" ],
  "sample": { "primary": "…", "headline": "…" },
  "checkout": "https://pagecuts.com/api/requests/5c0f6f0a-…/checkout",
  "kit": null
}
```

| `status` | Meaning |
|---|---|
| `queued` | Waiting for a builder. |
| `running` | Being researched, designed and filmed. `progress` says which. |
| `preview_ready` | The preview can be watched. The full-size files are still rendering. |
| `done` | Everything is rendered. |
| `failed` | No ad could be made from this page. Nothing is charged. |

`progress[].stage` is 1 for research, 2 for design, 3 for rendering, 4 once the preview is ready. `finished_at` is when the preview was ready or the build failed.

`preview`, `cover`, `stills`, `sample` and `checkout` are null or empty until `preview_ready`. The preview is an MP4, 720×900, with Pagecuts' mark; `cover` is its opening frame and the stills are two later moments, all marked JPEGs.

## The kit

`checkout` is a link for a person. `GET` it in a browser and it redirects to Stripe Checkout; after payment the person lands back on `page`. An agent cannot pay.

Once `paid` is true and `status` is `done`, `kit` is:

```json
{
  "zip": "https://pagecuts.com/out/…/kit.zip",
  "copy_file": "https://pagecuts.com/out/…/copy.txt",
  "copy": { "primary_texts": ["…", "…", "…"], "headlines": ["…", "…", "…"], "descriptions": ["…", "…"], "button": "Book now" },
  "videos": [ { "label": "Video 4:5, feed", "url": "…/final-4x5.mp4" }, { "label": "Video 1:1, square", "url": "…/final-1x1.mp4" }, { "label": "Video 9:16, Stories, Reels and TikTok", "url": "…/final-9x16.mp4" } ],
  "stills": [ { "label": "Still image, 4:5, feed", "url": "…/still-product-4x5.png" } ]
}
```

The videos are 15 seconds, H.264 MP4, 1080 pixels wide (1080×1350, 1080×1080, 1080×1920), without the mark. There are six stills: two designs in each of the three shapes. `button` is one of Meta's call-to-action labels. Kit files answer `404` until the ad is paid for; the preview files are public to anyone with their address.

### The rest of the kit: `more`

Buying the kit also starts work on the other platforms: the ad is recomposed for landscape and for Pinterest, cut down to six seconds, and given copy to each platform's lengths. `more` is `null` until the ad is paid for, then `{ "status": "queued" }` or `"running"` for about five minutes, then:

```json
{
  "status": "done",
  "copy_file": "https://pagecuts.com/out/…/copy-platforms.txt",
  "videos": [ { "label": "Video 16:9, YouTube and LinkedIn", "url": "…/final-16x9.mp4" }, { "label": "Six-second cut 16:9, YouTube bumper", "url": "…/final-6s-16x9.mp4" } ],
  "stills": [ { "label": "Still image, landscape, Google and LinkedIn", "url": "…/still-product-191x100.png" }, { "label": "Still image, 2:3, Pinterest", "url": "…/still-product-2x3.png" } ],
  "copy": {
    "google": { "headlines": ["15 of up to 30 characters"], "long_headlines": ["5 of up to 90"], "descriptions": ["5 of up to 90"], "business_name": "…" },
    "pinterest": { "title": "…", "description": "…" },
    "linkedin": { "intro": "…", "headline": "…", "description": "…" },
    "tiktok": { "texts": ["…", "…", "…"] },
    "snapchat": { "headline": "…", "brand": "…" }
  }
}
```

The 16:9 video is 1920×1080; the landscape stills are 1200×628 and the Pinterest stills 1000×1500. `kit.zip` is rebuilt to hold these too. A `status` of `failed` means they could not be made; the kit itself is unaffected.

## Limits and errors

- Three new ads an hour per connection.
- Request bodies up to 64 KB.
- Only pages the requester has the right to advertise: the ad reuses the page's text and images.

Errors are `{ "error": "code" }`:

| Status | `error` | Meaning |
|---|---|---|
| 400 | `invalid_url` | Not a public web address. |
| 400 | `invalid_email` | Not an email address. |
| 401 | `bad_code` | The code is wrong, already used, or more than an hour old. Send a new one. |
| 401 | `sign_in` | No code, and no signed-in person's token, came with the request. |
| 402 | `unpaid_limit` | That person has three previews, made in the last seven days, waiting to be bought. |
| 401 | `private` | A private test deployment: starting a build needs its key (see below). |
| 404 | `not_found` | No ad has that id, or the file is not available. |
| 429 | `rate_limited` | Three ads in the last hour from this connection, or too many codes for this address or connection. |
| 429 | `wait` | A code was sent to that address less than a minute ago. |
| 503 | `no_email` | This deployment cannot send email, so it cannot start ads. |
| 503 | `busy` | The queue is full. Try again in a few minutes. |

## Accounts and photos

Confirming an address makes that person's account the first time, and every ad they make is kept in it. On the site they stay signed in (Supabase Auth), and the site's calls carry their access token, `Authorization: Bearer <token>`, in place of `email` and `code`. These calls need that token:

- `POST /api/uploads`: one photo as the request body, with its `Content-Type` (`image/jpeg`, `image/png` or `image/webp`, 10 MB at most) and optionally `X-File-Name`. `201`: `{ "id": "…jpg", "name": "…" }`. Photos not used within two days are deleted. `POST /api/requests` then takes `uploads`, up to 8 of these ids, for the ad to use.
- `GET /api/me/requests`: the caller's ads, newest first.

Errors here: `401 sign_in` (no valid token), `400 instructions_too_long`, `400 bad_uploads`, `415 not_an_image`, `413 too_large`, `429 too_many_uploads`.

These calls are not offered as MCP tools yet.

## MCP server

`https://pagecuts.com/mcp` is the same API as two tools, over streamable HTTP. There is nothing to sign in to; `make_ad` confirms the person's email itself.

| Tool | What it does |
|---|---|
| `make_ad` | `{ "url", "email" }` emails the person a code. Called again with `"code"` added (and optionally `"instructions"`), it starts the build and returns `id` and `page`. |
| `get_ad` | `{ "id", "wait" }`. The state of the ad, as above but with `step` (the current step) in place of `progress`. `wait` is up to 20 seconds. |

Claude Code:

```sh
claude mcp add --transport http pagecuts https://pagecuts.com/mcp
```

Any other client: add a remote (HTTP) MCP server with that address. Refusals come back as tool errors in plain words, never as a login challenge.

## Private test deployments

A test deployment may be locked so that only its owner can start builds. There, send the key as an `X-Staging-Key` header on `POST /api/requests`, or add `?key=…` to the MCP address. Reading an ad never needs the key.
