Guides .md
Unattended tools
Some integrations never have a person watching them run: a metadata manager reading a config file, a cron job, a scheduled task on a home server. There's no window to pop up a sign-in screen and nothing to run a 30-day refresh loop in the background. For these, run the device-code flow once with credential: "key", get back a permanent API key, and paste it into the config file for good.
Building something interactive instead, an app someone opens and leaves running? Use the session path in Authentication instead. Which credential should your app use? has the full breakdown.
1. Request a device code for a key, not a session#
Same call as every device-code flow, with one extra field: "credential": "key".
curl -X POST "https://flicklist.tv/api/auth/device/code" \
-H "Content-Type: application/json" \
-d '{"client_id": "your_client_id", "credential": "key"}'import requests
client_id = "your_client_id"
code = requests.post(
"https://flicklist.tv/api/auth/device/code",
json={"client_id": client_id, "credential": "key"},
).json()
print(f"Enter {code['user_code']} at {code['verification_uri']}")The response is the same shape as the session path:
{
"user_code": "A1B2C3D4",
"device_code": "b2f1c9e4-...",
"verification_uri": "https://flicklist.tv/link",
"expires_in": 900,
"interval": 5
}
2. Show the person the code#
Print user_code and verification_uri wherever the person setting this up will see them: stdout, a setup wizard, a log line. They open the link, sign in if needed, and enter the code. This is a one-time step. Nothing about later requests depends on that browser tab staying open.
3. Poll until the key arrives#
Same poll loop as the session path, waiting interval seconds between calls. Because this device code was requested with credential: "key", the moment the person approves, the response carries a permanent key instead of a session token:
DEVICE_CODE="the device_code from step 1"
INTERVAL=5 # the "interval" from step 1
while true; do
RES=$(curl -s -X POST "https://flicklist.tv/api/auth/device/token" \
-H "Content-Type: application/json" \
-d "{\"device_code\": \"$DEVICE_CODE\"}")
KEY=$(echo "$RES" | grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)
[ -n "$KEY" ] && break
sleep "$INTERVAL"
done
echo "Got a key starting with: ${KEY:0:12}"import time
key = None
while key is None:
time.sleep(code["interval"])
body = requests.post(
"https://flicklist.tv/api/auth/device/token",
json={"device_code": code["device_code"]},
).json()
if "access_token" in body:
key = body["access_token"]
elif body.get("error") != "authorization_pending":
raise RuntimeError(body.get("error"))
print(f"Got a key starting with: {key[:12]}")The successful response looks like this:
{
"access_token": "fs_live_...",
"token": "fs_live_...",
"user": { "...": "..." },
"expires_at": null,
"credential": "key"
}
expires_at is always null on this path. The key doesn't expire on its own.
4. Store it and use it forever#
Write access_token into the config file, environment variable, or wherever this tool keeps its credentials, and send it as a bearer token on every request from here on:
curl -H "Authorization: Bearer $KEY" "https://flicklist.tv/api/v3/sync/watched/shows"shows = requests.get(
"https://flicklist.tv/api/v3/sync/watched/shows",
headers={"Authorization": f"Bearer {key}"},
).json()Running this flow again for the same person and the same app, a reinstall, a new machine, a re-run setup wizard, rotates that same key in place rather than minting a second one. Their Developer page still shows one entry for your app, now with a different secret. If your tool re-runs setup on its own, overwrite the stored key with the new value and the old one stops working immediately.
Running on a schedule without wasting your rate budget#
A job that wakes up on a cron schedule and re-fetches full collections every run burns through the 1,000 requests/hour ceiling fast, mostly on data that hasn't changed since the last run. Poll GET /v3/sync/last_activities first and compare its per-domain timestamps to what you cached last time; only re-fetch the collections that actually moved. It costs a fraction of a full sync and is the intended way to run unattended. Server-side and unattended clients covers the rest of what running headless expects from a client, mainly a descriptive User-Agent.