The POST contract between Max's cloud-hosted Telegram bot and the Pi5 upload endpoint — everything Max needs to customise the bot and send a first test audio file. The bot forwards media here; the Pi5 builds a wiki page and answers with status the bot posts back to the Telegram group. Part of Upload Media Phase A.
## What Travels Out-of-Band
Two secrets never appear on this wiki, in a repo, or in the bot's public config: the **endpoint hostname** (a Tailscale Funnel URL on the Pi5) and the **bearer token** minted for the bot. David delivers both directly. In return Max sends David the Telegram **group chat id**, which the endpoint allowlists — posts about unknown chats are refused.
## The Request
One HTTP call per media message. `POST /drop` as `multipart/form-data`:
- `file` — the media itself, downloaded by the bot from Telegram (`getFile`); voice notes arrive as `.oga`/`.ogg` opus - `caption` — the message caption, verbatim; empty is fine. This is the intent: it can route (`public`), title, and instruct — see Uploading Audio - `kind` — the Telegram media type: `voice`, `audio`, `video_note`, `video` or `document` - `chat_id` — the group the message came from (allowlist check) - `sender` — a display name for attribution, e.g. the Telegram first name; no usernames or ids needed - `duration`, `mime_type` — optional, passed through from Telegram when present
Headers: `Authorization: Bearer <token>`. Size: anything the cloud Bot API lets the bot download (≤ 20 MB) is accepted; the endpoint answers `413` beyond its own cap.
## The Response
Instant JSON — the endpoint answers before any processing starts. The page slug is minted on receipt, so the URL is known ahead of time; transcription and encoding happen after the reply has gone:
{ "status": "accepted", "title": "A First Test from Max", "public": true, "page_url": "https://media.anarchive.earth/a-first-test-from-max.html", "message": "Recording received — page being created: https://media.anarchive.earth/a-first-test-from-max.html (give it a minute, then refresh)" }
The bot's only job with the answer: post `message` into the group, verbatim, as its instant reply — the page is *in progress* and the link is already good. When the page is **private** (the default: no `public` in the caption) there is no `page_url` and the message says the recording is being paged privately, with its title. Errors come back the same shape:
{ "status": "error", "message": "Not authorised for this chat." }
`401` bad or missing token · `403` chat not allowlisted · `413` too large · `422` no file. The bot can post the error `message` to the group too — they are written to be read aloud.
## Stub First
The endpoint's first act — before transcription, before any encoding — is to write a **stub page** at the minted URL: sender, kind, duration, and a line saying the work is in progress — *check back and refresh*. Processing then fills the page in behind the same URL.
The URL is permanent. The slug comes from the caption when one is given, otherwise from timestamp and sender. The title may improve once the transcript exists; the slug — and therefore the link already sitting in the Telegram chat — never changes.
## Speaking the Route
A recording that arrives **without a caption** now says where it belongs. The pipeline transcribes it and listens to the opening sentence or two for a spoken preamble:
- *"This is a private audio note…"* — stays private on the Pi - *"This is a public audio note for david geek fish…"* — routed to that wiki (spoken names match real sites; unknown names stay private) - *"Morning check-in…"* — a group can bind its own phrases to a destination, so a check-in heard in the check-in group lands on the check-in wiki and its log
The instant reply for a captionless recording becomes "Recording received — no caption, so I'll listen and route it. Link follows in a minute", and the link arrives as a follow-up reply once the page is live — the pipeline waits until the page and its audio actually serve before posting, so a spoken link never errors. Public pages can take a few minutes to cross the mirror. Captioned behaviour is unchanged. Anything the pipeline cannot confidently place stays private: that is the floor.
## Max's Test Sequence
1. **Exchange secrets** — receive endpoint + token from David; send back the group chat id 2. **Curl first** — prove the contract with no bot code at all:
curl -s -X POST "https://$ENDPOINT/drop" \ -H "Authorization: Bearer $TOKEN" \ -F file=@test-voice.ogg \ -F caption="public — a first test from Max" \ -F kind=voice -F chat_id="$CHAT_ID" -F sender=Max
3. **Wire the bot** — on a voice/audio message in the group: `getFile`, download, POST as above, instantly reply to the group with the returned `message` 4. **Send a voice note** — the *in-progress* link should come back at once, with no wait on transcription; open it, see the stub, refresh and watch the page fill in 5. **Try the private default** — send one *without* `public` in the caption and confirm the reply names the page but carries no link
## Notes for v1
- Completion post-back: in v1 the wiki side posts to the Telegram group when the page is ready — "I'm ready, task completed", with the link. Not in this test: for v0 the stub page and a refresh are enough - Video notes (`video_note`) already fit this contract — same fields, the Pi5 extracts the soundtrack; see Uploading Video - Captions beginning `Sound of Law` route to the constitution archive — the caption grammar grows on Uploading Audio
## See - Upload Media — the doors, ingress and authentication ladder - Uploading Audio and Uploading Video — what the Pi5 does with the file - Sound of Law and Spoken Deeds — the first uses waiting on this test