Survey Micropub media-endpoint extensions and client behavior #182

Closed
opened 2026-08-12 04:13:19 +00:00 by rosa · 2 comments
Owner

Map: #181

Question

The Micropub media endpoint (POST /media, crates/web/src/handlers/micropub.rs:88) should grow q=source listing and delete. Both are community extensions with loose specs, and we do not yet know which clients to target — that is part of the question, not an input to it.

Survey:

  • The extension texts: the Micropub spec's media-endpoint section, plus the indieweb.org media-endpoint extension pages (q=source on the media endpoint, q=source&url= for a single item, delete of an uploaded file).
  • Real client behavior: Quill, Indigenous (Android/iOS), Micropublish, and any other maintained clients that speak to media endpoints. What do they send (params, limits, paging)? What response JSON shapes do they parse? Do any drive media delete, and with what wire shape (action=delete + url to the media endpoint? to the main endpoint?)? What scope do they expect to hold?
  • Server precedent: what do established implementations return for q=source on a media endpoint?

Resolve to: (a) a short list of clients worth targeting, (b) the exact request/response wire shapes to implement for listing and delete, (c) the scope enforcement expectation for media delete, with each fact traced to a primary source (spec text, client source code, or server implementation).

Map: #181 ## Question The Micropub media endpoint (`POST /media`, `crates/web/src/handlers/micropub.rs:88`) should grow `q=source` listing and delete. Both are community extensions with loose specs, and we do not yet know which clients to target — that is part of the question, not an input to it. Survey: - The extension texts: the Micropub spec's media-endpoint section, plus the indieweb.org media-endpoint extension pages (`q=source` on the media endpoint, `q=source&url=` for a single item, delete of an uploaded file). - Real client behavior: Quill, Indigenous (Android/iOS), Micropublish, and any other maintained clients that speak to media endpoints. What do they send (params, limits, paging)? What response JSON shapes do they parse? Do any drive media delete, and with what wire shape (`action=delete` + `url` to the media endpoint? to the main endpoint?)? What scope do they expect to hold? - Server precedent: what do established implementations return for `q=source` on a media endpoint? Resolve to: (a) a short list of clients worth targeting, (b) the exact request/response wire shapes to implement for listing and delete, (c) the scope enforcement expectation for media delete, with each fact traced to a primary source (spec text, client source code, or server implementation).
rosa self-assigned this 2026-08-12 04:16:22 +00:00
Author
Owner

Resolution

Full findings: docs/research/micropub-media-endpoint-extensions.md on branch research/micropub-media-endpoint, every fact cited to spec text, wiki/issue-thread record, or source at a pinned commit.

(a) Clients to target. Only two maintained open-source clients actually consume media-endpoint q=source:

  • Quill — sends ?q=source&limit=1; needs items[0].url, and if items[0].published exists it must parse and fall within the last 15 minutes (the code enforces 900 s despite its own "5 minutes" comment).
  • Sparkles — sends bare ?q=source; renders every items[].url and ignores all other fields (the wiki's claim that it paginates with limit/offset is contradicted by its source).

IndiePass/Indigenous Android (archived 2026-01), Indigenous iOS (archived 2023), and Micropublish are upload-only against the media endpoint. No open-source client anywhere issues a media delete or the url= single lookup — those exist purely as server precedent (media.aaronpk.com, micro.blog, WordPress plugin, Indiekit) and are implemented for parity, not for an observed client.

(b) Wire shapes.

  • Listing: GET <media-endpoint>?q=source[&limit=N][&offset=N]200 with {"items": [{"url": ..., "published": "<RFC3339>", "mime_type": ...}]}, newest first, default limit 10 (aaronpk/WordPress/benjifs precedent). url is the only consensus-required item property; published and mime_type are the interop-relevant extras (Indiekit's content-type field and after/before cursor are outliers). Missing or unknown q → 400.
  • Single lookup: GET <media-endpoint>?q=source&url=<file-url> → the bare item object, not wrapped in items; unknown URL → 400 invalid_request, not 404 (the only two implementations, WordPress and Indiekit, agree on both points).
  • Delete: form-encoded POST action=delete&url=<file-url> to the media endpoint itself (extensions issue #30 resolution; all four servers) → 200; response body is unstandardized (micro.blog returns {}, Indiekit a {"success":"delete",...} object). Missing url → 400.

(c) Scope for media delete. Require both delete and media, answering 403 insufficient_scope otherwise — the 2020 Micropub Pop-Up consensus, the wiki text, and aaronpk's production enforcement. Precedent is split (micro.blog enforces nothing; Indiekit only media; WordPress maps to capabilities), but since no client sends media deletes today the strict reading costs no interop. For uploads, mirror the common create-implies-media leniency: Sparkles and Indigenous iOS request only create, and Micropublish's login form cannot request media at all.

## Resolution Full findings: [`docs/research/micropub-media-endpoint-extensions.md`](https://code.cosmicrose.dev/rosa/vernier/src/branch/research/micropub-media-endpoint/docs/research/micropub-media-endpoint-extensions.md) on branch `research/micropub-media-endpoint`, every fact cited to spec text, wiki/issue-thread record, or source at a pinned commit. **(a) Clients to target.** Only two maintained open-source clients actually consume media-endpoint `q=source`: - **Quill** — sends `?q=source&limit=1`; needs `items[0].url`, and if `items[0].published` exists it must parse and fall within the last 15 minutes (the code enforces 900 s despite its own "5 minutes" comment). - **Sparkles** — sends bare `?q=source`; renders every `items[].url` and ignores all other fields (the wiki's claim that it paginates with limit/offset is contradicted by its source). IndiePass/Indigenous Android (archived 2026-01), Indigenous iOS (archived 2023), and Micropublish are upload-only against the media endpoint. No open-source client anywhere issues a media delete or the `url=` single lookup — those exist purely as server precedent (media.aaronpk.com, micro.blog, WordPress plugin, Indiekit) and are implemented for parity, not for an observed client. **(b) Wire shapes.** - *Listing:* `GET <media-endpoint>?q=source[&limit=N][&offset=N]` → `200` with `{"items": [{"url": ..., "published": "<RFC3339>", "mime_type": ...}]}`, newest first, default limit 10 (aaronpk/WordPress/benjifs precedent). `url` is the only consensus-required item property; `published` and `mime_type` are the interop-relevant extras (Indiekit's `content-type` field and `after`/`before` cursor are outliers). Missing or unknown `q` → 400. - *Single lookup:* `GET <media-endpoint>?q=source&url=<file-url>` → the **bare item object**, not wrapped in `items`; unknown URL → **400** `invalid_request`, not 404 (the only two implementations, WordPress and Indiekit, agree on both points). - *Delete:* form-encoded `POST action=delete&url=<file-url>` **to the media endpoint itself** (extensions issue #30 resolution; all four servers) → `200`; response body is unstandardized (micro.blog returns `{}`, Indiekit a `{"success":"delete",...}` object). Missing `url` → 400. **(c) Scope for media delete.** Require **both `delete` and `media`**, answering 403 `insufficient_scope` otherwise — the 2020 Micropub Pop-Up consensus, the wiki text, and aaronpk's production enforcement. Precedent is split (micro.blog enforces nothing; Indiekit only `media`; WordPress maps to capabilities), but since no client sends media deletes today the strict reading costs no interop. For *uploads*, mirror the common `create`-implies-`media` leniency: Sparkles and Indigenous iOS request only `create`, and Micropublish's login form cannot request `media` at all.
Author
Owner

Resolved: target Quill and Sparkles; q=source returns {"items":[{url, published, mime_type}]} newest-first (bare object for &url= lookup, 400 on unknown); delete is form-encoded POST action=delete&url= to the media endpoint requiring delete+media scopes. Details in the comment above and docs/research/micropub-media-endpoint-extensions.md on branch research/micropub-media-endpoint.

Resolved: target Quill and Sparkles; q=source returns {"items":[{url, published, mime_type}]} newest-first (bare object for &url= lookup, 400 on unknown); delete is form-encoded POST action=delete&url= to the media endpoint requiring delete+media scopes. Details in the comment above and docs/research/micropub-media-endpoint-extensions.md on branch research/micropub-media-endpoint.
rosa closed this issue 2026-08-12 04:40:45 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rosa/vernier#182
No description provided.