Get started API reference What it can picture Plans and limits Using the picture When a feed fails Paying
API reference
Every route a customer can call. Everything the dashboard does, the API does.
The basics
- Base address:
https://stillframe.us. - Key: send it in the
X-API-Keyheader on every/api/call. The image address needs no key. - Bodies: JSON in, JSON out, except that a pushed picture is the raw file. Send a
Content-Lengthwith every body. A JSON body is at most 16 KB and must be an object. - Rate: 60 calls a minute for each account. Over that, the answer is 429
slow_downwith aRetry-Afterheader (seconds). - API answers are never cached (
Cache-Control: no-store). Pictures are; see the first route. - Errors are
{"error": "code", "detail": "words"}. The code is stable; the words may change. Every code is listed in Errors. - A lapsed subscription: when the account is
suspended, every call exceptGET /api/accountanswers 402suspended. Pictures already made are still served at their addresses. - The dashboard uses a cookie and an
X-CSRFheader instead of a key. Scripts do not need either.
The feed object
Every call that returns a feed returns this shape.
| field | meaning |
|---|---|
id | the number to use in /api/feeds/{id} |
name | your label, up to 100 characters |
kind | page, camera or push. Fixed when the feed is made. |
url | the page address, with any password before the @ shown as ***; for a camera, the address with the user and password shown as ***@ and every value after a ? shown as *** (names kept, such as ?user=***&channel=***; a part with no = is a value and is hidden whole); null for a push feed |
canvas | the size you chose: 1920x1080, 1080x1920, 3840x2160, 2160x3840, or native (camera and push feeds only) |
canvas_in_use | the size being drawn. The same as canvas, except that on a plan without 4K a 4K canvas is drawn at 1080p, same orientation. |
refresh_in_use_s | how often the feed is really refreshed. The same as refresh_s, except that if the plan's fastest refresh is slower than the feed's own, the plan's is used; refresh_s keeps what you chose. null for a push feed. |
background | #rrggbb, the colour around a picture smaller than the canvas |
format | png or jpg |
refresh_s | seconds between pictures; null for a push feed |
wait_s | extra seconds a page waits after it stops loading, 0 to 20 |
timezone | the time zone a page's clock shows, such as America/New_York |
allow_blank | true if an all-one-colour page is a good picture |
enabled | false stops page and camera refreshes. The last picture is still served. |
image_url | the permanent address of the picture |
status.state | pending (no result yet), ok, or failing |
status.last_ok_at, last_attempt_at | UTC times, or null |
status.error, detail | while failing: the reason (see When a feed fails), else null |
status.partial | true if the last good picture was taken before the page finished loading |
status.lag_s | how many seconds past due the next picture is; null for push feeds and disabled feeds |
status.render_ms, bytes | how long the last attempt took, and the disk this feed uses |
status.version | counts pictures stored; it is the picture's ETag |
Settings you can send
Used by POST /api/feeds and PATCH /api/feeds/{id}. A field not in this table is refused with unknown_field.
| field | allowed | if you leave it out |
|---|---|---|
kind | page, camera, push. Required when making a feed; ignored when changing one. | (required) |
name | text, up to 100 characters | empty |
url | page: http:// or https://. Camera: rtsp://, http:// or https://. At most 2048 characters. Not allowed on a push feed. | (required for page and camera) |
canvas | the four sizes above, the 4K pair only on Standard and Pro; native for camera and push | page 1920x1080; camera and push native |
background | #rrggbb or rrggbb | black, #000000 |
format | png or jpg | camera jpg; page and push png |
refresh_s | a whole number of seconds from your plan's fastest refresh to 86400 (a day). Page and camera only. | 900, or your plan's fastest if that is slower |
wait_s | a number from 0 to 20 | 1 |
timezone | a name such as America/New_York | UTC |
allow_blank | true or false | false |
enabled | true or false | true |
The address of a page or camera must pass the checks on What it can picture when you make or change the feed, and again every time a picture is taken.
Routes
GET /f/{token}.png and GET /f/{token}.jpg
The feed's newest picture. No key. HEAD works too. Use the extension of the feed's format. After a format change the other extension keeps working too (see PATCH below), so an address someone already embedded does not break.
- 200: the picture. Headers:
ETag("v<version>"),Cache-Control: public, max-age=N, stale-while-revalidate=N, stale-if-error=604800where N is the feed's refresh time (30 for a push feed), andAccess-Control-Allow-Origin: *. - 304: if you send the current ETag in
If-None-Match. - 404
not_found: no such address. Not cached. - 404
no_image_yet: the feed exists but has no picture yet. Comes withRetry-After: 5. - 405
method_not_allowed: any method other than GET and HEAD.
GET /api/account
Your plan, its limits and what you use. Works while the account is suspended.
{"email": "[email protected]", "plan": "standard", "status": "active", "feeds": 3, "feeds_allowed": 20,
"fastest_refresh_s": 300, "history_kept": 24, "storage_used": 412339, "storage_allowed": 2147483648,
"choices": {"canvases": [["1920x1080", "1080p, landscape"], ...], "four_k_plans": "Standard and Pro", "four_k_needs": "Standard or Pro", "refresh_s": [300, 600, ...],
"default_background": "#000000"}}
status is active, past_due, suspended or closed. A closed account has no key.
GET /api/feeds
All your feeds, oldest first: {"feeds": [feed, ...]}.
POST /api/feeds
Make a feed. Body: the settings above. Answers 201 with the feed.
- 400: a setting is wrong. The code names it (see Errors).
- 409
feed_limit: your plan holds no more feeds.
curl -s -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"kind":"page","url":"https://example.com/","canvas":"800x600"}' https://stillframe.us/api/feeds
{"error": "bad_canvas", "detail": "canvas is one of: 1080x1920, 1920x1080, 2160x3840, 3840x2160"}
GET /api/feeds/{id}
One feed. A feed that is not yours is answered exactly like one that does not exist: 404 not_found.
PATCH /api/feeds/{id}
Change any of the settings, sending only what changes. Answers 200 with the feed.
- A change to
url,canvas,background,format,wait_s,timezoneorallow_blankmakes a page or camera feed due at once and clears its failure count, if the value really changed (a field sent back as it was changes nothing). It shares the once-a-minute limit of the render route below: a second change inside the minute is saved and photographed on the feed's usual schedule. On a push feed it draws the last picture you sent again. - Changing
formatchanges the extension ofimage_url; the token stays the same. Use the newimage_urlfrom then on. Nothing breaks if you do not: until the next picture is taken (a change asks for one straight away), both extensions show the picture that was there before, in its old format and with its trueContent-Type; once the new picture exists, both extensions show it, in the new format. A program that decides what a file is from its extension may then show the old address wrongly, which is why the new one is the one to use. A push feed is redrawn from the last picture you sent at once. A format change made while a picture is being taken is redrawn at the next refresh, not straight away. - The
urla camera feed shows has its credentials hidden (see the feed object). If you send that hidden form back (for a camera or a page, with***where a user, password or query value was), it is refused withbad_url: send the full address, or leaveurlout.
DELETE /api/feeds/{id}
Remove the feed and its pictures. Answers 200 with {"deleted": id}. Its address stops working.
PUT /api/feeds/{id}/image
Send a picture to a push feed. The body is the file itself; the Content-Type you send is ignored, because the file is read for what it is. At most 10 MB and 50 million pixels, PNG or JPEG. Stillframe draws it again to the feed's canvas and format before serving it, so hidden details in the file are not passed on.
- 200:
{"stored": true, "width": W, "height": H, "bytes": N, "image_url": "...", "version": V} - 400
not_push: the feed is a page or camera feed. - 413
too_large: over the size limits. - 415
bad_image: not a PNG or JPEG. - 422
bad_image: it looked like a PNG or JPEG but could not be drawn. - 429
slow_down: your plan allows one picture every so many seconds for each push feed (Plans and limits). A push that is let in but then fails (a bad file, say) still uses up its turn. - 503
compose_failedorlaunch_failed: Stillframe could not draw the picture. Try again. - 507
quota: the plan's storage is full. The old picture stays up.
POST /api/feeds/{id}/render
Ask for a page or camera feed to be photographed now. Answers 202 {"queued": id}; the picture arrives when the render finishes. Also clears a failure's slow-down. Once a minute for each feed (429 slow_down otherwise). A push feed answers 400 not_rendered.
POST /api/feeds/{id}/retoken
Give the feed a new address. The old one stops working at once. Answers 200 with the feed. A copy someone already saved elsewhere (an email, a chat) is theirs to lose.
Errors
| status | code | meaning |
|---|---|---|
| 400 | bad_kind | kind is missing or is not page, camera or push |
| 400 | bad_name | the name is not text, or is over 100 characters |
| 400 | bad_url | the address is missing, too long, unreadable, the wrong kind of address for this feed, or given to a push feed |
| 400 | blocked_address | the address is private or local, or on a port Stillframe does not fetch from |
| 400 | address_not_found | the name does not resolve |
| 400 | bad_canvas | not one of the sizes (or native on a page) |
| 400 | canvas_needs_plan | a 4K canvas on a plan without 4K; the detail names the plans that have it. Sending the canvas a feed already has is not refused. |
| 400 | bad_background | not a colour like #1c1917 |
| 400 | bad_format | not png or jpg |
| 400 | bad_refresh | not a whole number in the range your plan allows. Sending back the value the feed already has is not refused, even if your plan's fastest rate has since risen above it. |
| 400 | bad_wait | not a number from 0 to 20 |
| 400 | bad_timezone | not a time zone name |
| 400 | bad_allow_blank, bad_enabled | not true or false |
| 400 | unknown_field | the detail lists the fields that are not settings |
| 400 | bad_json | the body is not a JSON object |
| 400 | not_push | only a push feed takes a picture |
| 400 | not_rendered | a push feed is updated by sending it a picture |
| 401 | unauthorized | no key, a wrong key, or a closed account |
| 402 | suspended | the subscription is not active |
| 403 | csrf | a dashboard change without its anti-forgery header; scripts using a key never see this |
| 404 | not_found | no such feed, address or route |
| 404 | no_image_yet | the feed has no picture yet |
| 405 | method_not_allowed | that method does not work on that route |
| 409 | feed_limit | the plan holds no more feeds |
| 411 | length_required | send a Content-Length |
| 413 | too_large | the body is over its limit |
| 415 | bad_image | not a PNG or JPEG |
| 422 | bad_image | could not be drawn |
| 429 | slow_down | too many calls; wait Retry-After seconds |
| 500 | internal | a fault on our side; try again |
| 503 | compose_failed, launch_failed | the picture could not be drawn; try again |
| 507 | quota | the plan's storage is full |