Framepine

Quickstart

Framepine turns a timecode into an image. You register a source once, then address any frame inside it by URL. This page takes about five minutes.

1. Register a source

Give Framepine a URL it can reach. It reads the container, indexes keyframes and returns an asset id together with a delivery token.

curl -X POST https://framepine.com/v1/assets \
  -H "Authorization: Bearer fp_live_9c2ad41f7b6e" \
  -H "Content-Type: application/json" \
  -d '{"source":"https://cdn.example.com/reel.mp4"}'
# 201 Created
{
  "id": "as_7f31c0",
  "status": "ready",
  "duration": 34.2,
  "frames": 821,
  "token": "k3d9xq2m",
  "delivery": "https://framepine.com/v1/f/k3d9xq2m/"
}

Sources over roughly a minute of video come back as "status": "indexing". Poll GET /v1/assets/{id} until it turns ready; indexing a ten-minute file takes a few seconds.

2. Request a frame

Delivery URLs are plain GET requests. No headers, no signing step — the token in the path is the credential.

https://framepine.com/v1/f/k3d9xq2m/t12.5s-w640.jpg

That reads as: from asset k3d9xq2m, the frame at 12.5 seconds, scaled to 640 pixels wide, encoded as JPEG. Drop it straight into an <img> tag.

Authentication

The control API uses a bearer token. Keys look like fp_live_9c2ad41f7b6e for production and fp_test_… for the sandbox; test keys accept any source but return a watermarked frame.

Authorization: Bearer fp_live_9c2ad41f7b6e

Delivery URLs carry no key. That is deliberate: the delivery token is scoped to one asset and can be rotated on its own, so you can put frame URLs in a public page without exposing anything else in your account. Rotate a token with POST /v1/assets/{id}/rotate and the old URLs stop resolving within a minute.

Delivery specs

The last path segment is the spec. Parameters are joined with - and may appear in any order. The extension sets the output format.

ParameterMeaningDefault
t{sec}sTimecode. Accepts decimals, e.g. t12.5s.t0s
w{px}Target width, 16–4096.source width
h{px}Target height, 16–4096.from aspect
q{n}Encoder quality, 40–95. Ignored for PNG.q82
fit-coverCrop to fill when both w and h are set.fit-contain
p{n}Page number, PDF sources only.p1

Extensions: .jpg, .png, .webp, .avif.

# 320px wide thumbnail, cheap encode
/v1/f/k3d9xq2m/t3s-w320-q70.jpg

# square crop for an avatar grid
/v1/f/k3d9xq2m/t3s-w400-h400-fit-cover.webp

# second page of a PDF
/v1/f/pq81ma47/p2-w900.png

Caching

The first request for a spec renders it and counts against your extraction quota. Every later request for the same spec is served from cache and counts only as delivery bandwidth.

Responses carry ETag and Cache-Control: public, max-age=86400, stale-while-revalidate=604800. Conditional requests with If-None-Match return 304 Not Modified and are not billed at all. Cached derivatives are kept for 30 days after their last hit.

Rate limits

Limits apply to the control API only. Delivery is not rate limited — it is metered by bandwidth, and the numbers are on the pricing page.

PlanControl APIConcurrent renders
Free10 req/s2
Studio50 req/s10
Scale200 req/s40

Every response carries X-RateLimit-Remaining and X-RateLimit-Reset. Over the limit you get 429 with a Retry-After header — wait that long rather than retrying immediately.

Errors

Errors are JSON with a stable code. Match on the code, not on the message: messages change, codes do not.

{
  "error": {
    "code": "unsupported_source",
    "message": "Container 'video/x-flv' cannot be indexed.",
    "asset": "as_7f31c0"
  }
}

The full table lives in the API reference.

Something not covered here?

Write to support@framepine.com with the asset id and the request time. We keep request logs for 14 days, which is usually enough to tell you exactly what happened.