# Conventions

> Ids, dates, statuses and the rules every response follows.

## Requests and responses

- Base address: `https://getrefract.app/v1`. Every response is JSON, in UTF-8.
- Field names are `snake_case`. A missing value is `null`, never an absent field.
- **Ignore what you do not know.** New fields and new enum values can appear in any release: your client must not
  break on them. Removing or renaming a field is a breaking change, and breaking changes only ship under a new
  version (`/v2`), announced in the [changelog](/developers/changelog) with `Deprecation` and `Sunset` headers on the
  old one.

## Ids

Every show or movie carries an `ids` object:

```json
"ids": { "refract": "8b0e5c1a-…", "tmdb": 95396, "imdb": "tt11280740", "tvdb": null }
```

- `refract` is Refract's own id; use it to ask for one library title (`/v1/library/{id}`).
- `tmdb`, `imdb` and `tvdb` are the ids we hold; a missing one is `null`.
- **Always pair `tmdb` or `tvdb` with `type`.** A TMDB id names a film and a show, and so does a TheTVDB id.
- An episode is its show, its `season` and its `number`, in TMDB numbering. `season` 0 holds specials. Some anime are
  numbered differently on TheTVDB; the API always uses TMDB's numbering.

## Shows, movies and anime

`type` is `show` or `movie`. Anime are shows or movies with `is_anime: true`.

## Library statuses

| Status | Meaning |
|---|---|
| `in_progress` | Started, with aired episodes left to watch. |
| `up_to_date` | Every aired episode watched; more are coming. |
| `completed` | Finished. For a show: it has ended and every episode is watched. |
| `planned` | On the watchlist. |
| `on_hold` | Paused by the member. |
| `dropped` | Abandoned by the member. |

A rewatch in progress (Revisiting, in the app) is not a status. The title keeps its own status, and carries a
`revisit` object with the rewatch's number, its start, its range and its own progress.

## Dates

Two kinds of dates, never mixed:

- **Instants** end in `_at` and are RFC 3339 strings in UTC: `"2026-10-27T20:41:55.120Z"`. They say when
  something happened on Refract's side: a title added, a rating changed.
- **Days the member lived**, such as when they watched an episode, are calendar facts:

  ```json
  "watched_at": { "local": "2026-10-27T21:40:00.000000+01:00", "tz": "Europe/Paris" }
  ```

  `local` is either a day (`"2026-10-26"`) or a local time with its UTC offset, and a time appears only when the
  member's clock is known. `tz` is the IANA zone it was lived in, or `null` for a zone-free day. The whole value is
  `null` when the date is unknown. Refract never invents a time it does not know.

These are the dates of Refract's account export (format 3), so an export and the API always agree.

## Caching

Every answer carries an `ETag`. Send it back as `If-None-Match`: when nothing changed you get `304 Not Modified`
and an empty body, so there is nothing to download again. It still counts against your
[rate limit](/developers/rate-limits). Answers are
`Cache-Control: private, no-cache`: keep them on the device, revalidate before reuse, never in a shared cache.

## Hidden titles

The API shows what the app shows. Titles TMDB no longer publishes are left out of every list, and so are media
other than shows and movies.
