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.
| Parameter | Meaning | Default |
|---|---|---|
t{sec}s | Timecode. 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-cover | Crop 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.
| Plan | Control API | Concurrent renders |
|---|---|---|
| Free | 10 req/s | 2 |
| Studio | 50 req/s | 10 |
| Scale | 200 req/s | 40 |
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.