# Photographs and video: attaching them, and the inbox

> Attach photographs and video to a day, or stage them before the days exist.

## One door for every kind of bytes

```http
POST /api/v2/{user}/media
Content-Type: multipart/form-data
Authorization: Bearer fs_agent_…

file=@DSC_4471.HEIC
intent={"kind": "photo", "trip": "japan-2027", "day": "2026-08-26-lanterns-of-hoi-an", "caption": "…"}
```

One photograph (or clip, or document, or export) per call. `intent.kind` is one of `photo`, `bank_export`, `gps_history`, `document`, and it decides which questions the intent is asked — a bank statement has no caption, a photograph is asked for `day`. Each asked question is answered or declined, the same rule as a day's own sections: `declined: {"day": "no day written yet — stage it for later"}`.

**No trip named at all lands in the journal-wide inbox** (owner only) rather than any one trip's gallery — that is the door for a card of two hundred photographs with no days written yet.

```http
GET    /api/v2/{user}/inbox      — everything staged, by shelf (media / files)
DELETE /api/v2/{user}/inbox/{id} — discard one; no confirmation, since nothing here has ever been on the site
```

To attach a staged file to a trip and day later, send the same POST again with `{"intent": {...}, "inbox": "<id>"}` instead of bytes — it moves the file rather than copying it.

To attach a photograph already stored **day-less** on a trip (`intent.day` declined, or not yet named) to a day, `POST /api/v2/{user}/trips/{trip}/days/{slug}/media` with `{"items": [{"src": "…"}]}` — the same `src` `GET /api/v2/{user}/media` already answers with. This re-points the day document at the existing bytes; it never moves or copies the file. `GET /api/v2/{user}/media` still answers this trip's own `day` for it correctly afterwards — that answer comes from whichever day document names the `src`, not from which folder the bytes sit in.

Or hand over URLs instead of bytes: `{"intent": {...}, "url": "https://…"}` — https and public hosts only; anything resolving to a private, loopback or link-local address is refused, including after a redirect.

**This door reads a photograph's own EXIF, but only onto itself.** `GET /api/v2/{user}/media` answers a photograph carrying GPS and a timestamp with its own `takenAt`, `lat` and `lon`, each marked `measuredFrom: "exif"` so it reads as measured rather than told. None of that reaches the day: a day's own `coordinates` and `time` are separate fields, never filled in from a photograph's metadata — send those yourself through the day's `PUT`/`PATCH` if the day itself should carry them.

**`intent.caption` is what you say at this door, and only lasts until a day names the photograph.** Once a day's own `media[].caption` exists for it (set here at upload, or later through the day's `PUT`/`PATCH`), `GET /api/v2/{user}/media` answers with that — this door's caption is the upload's own answer, not a second copy of the current one. Edit a captioned photograph on its day, not by uploading it again.

## What is accepted

| | |
| --- | --- |
| images | jpeg, png, heic, heif, webp — at most 50 MB, 12000px on the longest edge, 64 MP total |
| video | mp4, mov, webm — at most 500 MB and 300s |
| per day | at most 40 items |
| per request | at most 512 MB of body |
| per JSON call | at most 4 MB of body on every other `/api/v2` JSON call, `/import` aside (`limits.jsonBodyMaxBytes`) |

**Send the largest file you have.** The site serves a resized copy; the original is kept whole as the print master, and there is no way to add resolution back later.

```http
GET    /api/v2/{user}/media?trip=japan-2027   — one trip's stored media, paged
DELETE /api/v2/{user}/media
{"src": "…"}                              — remove a stored item by the src an upload answered with
```

## A folder of photographs, all at once

For a whole memory card, on the machine the journal lives on, `npm run ingest` is the faster route — it reads each file's own timestamp and place, groups them into days, and writes drafts around the result. Over the network you have only the door above, which is fine for a handful of pictures: send them and they land, one call per file.
