# discs.fyi — agent skill

discs.fyi is a personal physical-media (Blu-ray / 4K / DVD) collection tracker.
This document tells an AI agent how to authenticate as a discs.fyi user and read
their collection over a small JSON API.

If the user just said something like "list my action movies from discs.fyi",
follow the steps below: authenticate once (reuse the saved token afterwards),
then call the API.

## Reuse a saved token first (do this before anything else)

Tokens are long-lived, so you only ever need to run the device flow once per
user. Before starting it, look wherever you persist secrets for an existing
discs.fyi token:

- If you can write files, a good spot is `~/.config/discs-fyi/token`.
- If you're Claude Code, your persistent memory works too.
- If you can't use a filesystem (e.g. a mobile or otherwise sandboxed app), use
  whatever durable storage you do have — a secrets store, an environment
  variable, your own long-term memory, or somewhere else you can keep it.

If you find a saved token, skip the device flow entirely and go straight to the
API. Only fall into the device flow when you have no token, or when an API call
returns 401 (see below).

## Authenticating — OAuth 2.0 Device Flow (RFC 8628)

The user signs in with their browser; the agent never handles their password.

1. Request a code:

   POST https://discs.fyi/api/auth/device/code

   Response (200, application/json):
   {
     "device_code": "<secret handle for polling>",
     "user_code": "WDJB-MJHT",
     "verification_uri": "https://discs.fyi/device",
     "verification_uri_complete": "https://discs.fyi/device?user_code=WDJB-MJHT",
     "expires_in": 600,
     "interval": 5
   }

2. Ask the user to approve. Print the "verification_uri_complete" link (it
   pre-fills the code) and tell them to open it and click Approve. Also show the
   "user_code" in case they use the plain "verification_uri".

3. Poll for the token every "interval" seconds:

   POST https://discs.fyi/api/auth/device/token
   Content-Type: application/x-www-form-urlencoded

   device_code=<the device_code from step 1>

   - Not approved yet → 400 {"error": "authorization_pending"} — keep polling.
   - User denied      → 400 {"error": "access_denied"} — stop.
   - Code expired     → 400 {"error": "expired_token"} — stop; start over at step 1.
   - Approved         → 200 {"access_token": "<token>", "token_type": "Bearer"}

4. Use the token on every API call:

   Authorization: Bearer <access_token>

   Save it durably now (see "Reuse a saved token first" above) so future sessions
   skip straight to the API. Treat it like a password: it grants read access to
   the user's collection, so don't commit it to a repo or print it in logs. If
   any API call later returns 401, the token has been revoked — discard the saved
   copy and start over at step 1.

## API

All endpoints below require the bearer token and return JSON.

### GET https://discs.fyi/api/me

Confirms the token and identifies the user.

   { "id": 1, "email": "you@example.com", "name": "Your Name" }

### GET https://discs.fyi/api/movies

The user's owned movies. Optional query parameter:

- genre — case-insensitive genre name (e.g. Action, Comedy, "Science Fiction",
  Thriller, Horror, Drama). Filters to movies tagged with that genre.

Example:

   GET https://discs.fyi/api/movies?genre=Action
   Authorization: Bearer <token>

   {
     "movies": [
       {
         "tmdb_id": 603,
         "title": "The Matrix",
         "year": 1999,
         "genres": ["Action", "Science Fiction"],
         "formats": ["4K UHD"]
       }
     ],
     "count": 1
   }

### GET https://discs.fyi/api/items

The user's owned titles of any kind (movies, TV, box sets). Optional query params:

- type — one of: movie, tv, box-set. Omit for all kinds.
- genre — case-insensitive genre name (as above).

Each item has a "type" field. Example:

   GET https://discs.fyi/api/items?type=tv
   Authorization: Bearer <token>

   {
     "items": [
       { "tmdb_id": 1399, "title": "Game of Thrones", "year": 2011,
         "type": "tv", "genres": ["Sci-Fi & Fantasy", "Drama"], "formats": ["Blu-ray"] }
     ],
     "count": 1
   }

### GET https://discs.fyi/api/items/{tmdb_id}

Everything the user has on one title, looked up by its "tmdb_id" (take this from
an /api/items or /api/movies result). Returns the title's metadata, every release
(edition) of it the user owns, and their watch history aggregated across those
releases — so this answers "tell me about <title>": which discs they have, how
many times they've watched it, and when they last did.

Optional query param:

- type — movie, tv, or box-set. Only needed in the rare case a tmdb_id is
  ambiguous across kinds; omit otherwise.

404 if the user owns no release of that title. Example:

   GET https://discs.fyi/api/items/36647
   Authorization: Bearer <token>

   {
     "item": {
       "tmdb_id": 36647,
       "title": "Blade",
       "year": 1998,
       "type": "movie",
       "genres": ["Action", "Horror", "Thriller"],
       "formats": ["4K UHD", "Blu-ray"],
       "releases": [
         { "release_id": 88, "format": "4K UHD", "edition_type": "Standard", "region": "A",
           "status": "owned", "season_number": null, "collection_id": 12,
           "collection_name": "Movie Wall", "shelf": "Action" }
       ],
       "watches": {
         "count": 3,
         "last_watched_at": "2026-05-30",
         "history": [ { "id": 5, "watched_at": "2026-05-30", "release_id": 88, "format": "4K UHD" } ]
       }
     }
   }

### GET https://discs.fyi/api/collections

The collections the user can access.

   { "collections": [ { "id": 12, "name": "Movie Wall", "description": null, "is_main": true } ], "count": 1 }

### GET https://discs.fyi/api/collections/{id}

One collection: metadata, stats, and a paginated list of its titles. Optional
query params:

- status — owned or wishlist (omit for all).
- genre — case-insensitive genre name.
- limit — page size, max 200 (default 50). offset — number to skip (default 0).

"count" is the total before paging. Each item lists its releases (editions):

   GET https://discs.fyi/api/collections/12?status=owned&limit=50
   Authorization: Bearer <token>

   {
     "collection": { "id": 12, "name": "Movie Wall", "description": null, "is_main": true },
     "stats": { "totalTitles": 153, "ownedTitles": 150, "wishlistTitles": 3, "formats": 4 },
     "items": [
       { "tmdb_id": 603, "type": "movie", "title": "The Matrix", "year": 1999,
         "releases": [ { "release_id": 88, "format": "4K UHD", "edition_type": "Standard", "status": "owned", "shelf": "Sci-Fi" } ] }
     ],
     "count": 153, "limit": 50, "offset": 0
   }

### GET https://discs.fyi/api/collections/{id}/shelves

The shelves within a collection.

   { "shelves": [ { "id": 4, "name": "Sci-Fi", "color": "#3b82f6", "item_count": 22 } ], "count": 1 }

### GET https://discs.fyi/api/releases/{id}

Full detail of one release (edition fields, which collection/shelf it's on, and
the user's watch history for it).

   { "release": { "release_id": 88, "item_title": "The Matrix", "tmdb_id": 603, "format": "4K UHD",
                  "edition_type": "Standard", "status": "owned", "collection_name": "Movie Wall", "shelf_name": "Sci-Fi" },
     "watches": [ { "id": 5, "watched_at": "2026-05-30" } ] }

## Worked example — "list my action movies from discs.fyi"

1. Run the device flow once and obtain a bearer token (reuse a saved one if you
   have it).
2. GET https://discs.fyi/api/movies?genre=Action with the Authorization header.
3. Present each movies[].title (with its year) back to the user.

If they then ask about one of those titles ("tell me about Blade"), take its
"tmdb_id" from the list and GET https://discs.fyi/api/items/<tmdb_id> — the response has
the editions they own and how many times they've watched it.
