API reference
Base URL https://framepine.com/v1. All control endpoints take
Authorization: Bearer <key> and speak JSON. Delivery endpoints take
no headers and return an image.
POST/v1/assets
Registers a source and starts indexing.
| Field | Type | Notes |
|---|---|---|
source | string | Required. HTTPS URL we can fetch. Max 2 GB. |
label | string | Optional. Your own name for the asset, echoed back. |
ttl_days | integer | Optional, 1–365. Deletes the asset automatically. Default: no expiry. |
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","label":"launch-reel"}'
# 201 Created
{
"id": "as_7f31c0",
"label": "launch-reel",
"status": "ready",
"kind": "video",
"duration": 34.2,
"frames": 821,
"width": 1920,
"height": 1080,
"token": "k3d9xq2m",
"delivery": "https://framepine.com/v1/f/k3d9xq2m/",
"created_at": "2026-07-14T09:21:04Z"
}
status is indexing, ready or
failed. For PDF sources kind is document,
duration is absent and pages takes the place of
frames.
GET/v1/assets/{id}
Returns the same object. Poll this while status is
indexing.
curl https://framepine.com/v1/assets/as_7f31c0 \
-H "Authorization: Bearer fp_live_9c2ad41f7b6e"
A failed asset carries the reason:
{
"id": "as_7f31c0",
"status": "failed",
"error": { "code": "source_unreachable", "message": "Origin returned 403." }
}
POST/v1/assets/{id}/rotate
Issues a new delivery token and retires the old one. Existing URLs keep working
for a 60 second grace period, then return 404. Cached derivatives
are preserved — rotation changes the address, not the images.
curl -X POST https://framepine.com/v1/assets/as_7f31c0/rotate \
-H "Authorization: Bearer fp_live_9c2ad41f7b6e"
# 200 OK
{
"id": "as_7f31c0",
"token": "w82hc6nv",
"delivery": "https://framepine.com/v1/f/w82hc6nv/",
"previous_token_expires_at": "2026-07-14T09:22:04Z"
}
DELETE/v1/assets/{id}
Removes the asset and every cached derivative. Returns 204 No Content.
Deleting is immediate and cannot be undone.
curl -X DELETE https://framepine.com/v1/assets/as_7f31c0 \
-H "Authorization: Bearer fp_live_9c2ad41f7b6e"
GET/v1/f/{token}/{spec}.{ext}
Returns the rendered frame. No authentication header — the token is the credential. Spec grammar is documented in the guide.
GET /v1/f/k3d9xq2m/t12.5s-w640.jpg
# 200 OK
Content-Type: image/jpeg
Content-Length: 48213
ETag: "9f2c11a4"
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
X-Framepine-Cache: MISS
X-Framepine-Render-Ms: 214
X-Framepine-Cache is HIT or MISS. Only a
MISS counts against your extraction quota; a HIT counts as
delivery bandwidth only. Requests with a matching If-None-Match return
304 and are not billed.
GET/v1/usage
Current billing period, as the dashboard sees it.
{
"period_start": "2026-07-01T00:00:00Z",
"period_end": "2026-08-01T00:00:00Z",
"plan": "studio",
"extractions": { "used": 6412, "included": 25000 },
"delivery_bytes": { "used": 41028374528, "included": 268435456000 }
}
Error codes
Every error body has the same shape: an error object with
code and message. Match on code — messages are
written for humans and change without notice.
| HTTP | Code | What happened |
|---|---|---|
| 400 | invalid_spec | The delivery spec did not parse, or a value was out of range. |
| 400 | timecode_out_of_range | The timecode is past the end of the source. |
| 401 | invalid_key | Missing, malformed or revoked API key. |
| 402 | quota_exceeded | Plan quota spent. Delivery of cached frames continues; new renders stop. |
| 404 | asset_not_found | Unknown id, retired token, or the asset was deleted. |
| 413 | source_too_large | Source exceeds 2 GB. |
| 415 | unsupported_source | Container or codec cannot be indexed. |
| 429 | rate_limited | Control API rate limit. Honour Retry-After. |
| 502 | source_unreachable | Your origin refused the fetch or returned an error status. |
| 504 | source_timeout | Your origin did not respond within 30 seconds. |
502 and 504 are worth retrying with backoff. The rest are
not: they will fail the same way until you change the request.