From ddf09677f068d61eb8a119c110f6eef16caf1337 Mon Sep 17 00:00:00 2001 From: ignacionelson Date: Wed, 2 Sep 2026 18:56:55 -0300 Subject: [PATCH] Document the branding endpoints where their callers are Branding moved into the application on 2026-08-28 and its two read-only endpoints came with it unchanged -- same paths under /api/v1/modules/branding, same capability, same ability. Their documentation did not: the guide still sent readers to packages/cloud-modules/docs/api.md, so endpoints that every installation now carries were described in a private repository almost none of their callers can open. The OpenAPI document is still not the place for them. OpenApiContractTest skips api/v1/modules/* on purpose: that document is served unauthenticated and has to be identical on every installation, while a module's paths exist only where the module does. So this is a plain markdown file beside the guide, and published like it -- ignored docs are maintainer notes, and this one is for integrators. It is the cloud-modules file moved across, minus the attribution switch: that half stayed Cloud-only and has no API surface at all. The gate is described as every edition holding branding.customize, with a hosted plan able to subtract it, because that is what the enum now says. --- .gitignore | 1 + docs/api-guide.md | 6 ++-- docs/api-modules.md | 82 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 docs/api-modules.md diff --git a/.gitignore b/.gitignore index 6256e443..8387c91b 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ yarn-error.log /database/seeders/DevDataSeeder.php /docs/*.md !/docs/api-guide.md +!/docs/api-modules.md !/docs/email-oauth.md !/docs/api-zapier.md diff --git a/docs/api-guide.md b/docs/api-guide.md index 003c1ea9..89927cb8 100644 --- a/docs/api-guide.md +++ b/docs/api-guide.md @@ -449,8 +449,10 @@ the core vocabulary. A slug must be unique and lowercase; a clash throws at boot silently shadowing. Module endpoints are deliberately absent from the document above — `OpenApiContractTest` skips -`api/v1/modules/*` — so each package documents its own surface in its own repository. The -`branding` module's endpoints are in `packages/cloud-modules/docs/api.md`. +`api/v1/modules/*` — because that document is served unauthenticated and has to be identical on +every installation. The modules that ship with the application document their endpoints in +[`api-modules.md`](api-modules.md); a module living in its own package documents its surface in its +own repository. --- diff --git a/docs/api-modules.md b/docs/api-modules.md new file mode 100644 index 00000000..3b83bf20 --- /dev/null +++ b/docs/api-modules.md @@ -0,0 +1,82 @@ +# API — module endpoints + +Optional modules add endpoints under `/api/v1/modules/{module}/…`. They are **not** in the committed +[`api/openapi.json`](api/openapi.json): `OpenApiContractTest` skips `api/v1/modules/*`, because that +document is served unauthenticated and has to be identical on every installation, while a module's +paths exist only where the module does. This file is the documentation for the modules that ship +with the application; a module living in its own package documents its surface in its own repository. + +Everything [`api-guide.md`](api-guide.md) describes — bearer-token authentication, ability checks, +RFC 7807 errors, rate limits — applies unchanged. The core supplies all of it; none of it is +restated per module. + +`GET /api/v1/me` lists the modules an installation actually carries, so an integration can check for +`branding` before calling anything below rather than guessing from a 404. + +--- + +## Branding + +Mounted at `/api/v1/modules/branding`, behind `capability:branding.customize`. Every edition has +that capability; a hosted plan can have it subtracted from its environment, in which case these +paths answer 403 like any other gated route. + +Both endpoints are read-only. Uploading either image is a multipart flow whose content-sniffing +rules only make sense behind a file picker, and settings writes follow the rule that there is never +a generic `PATCH /settings`. + +Hiding the attribution line is not here. That switch is Cloud-only, has no API surface, and its +column is written by the `cloud-modules` package. + +### `GET /logo` — ability: `edit_settings` + +The logo shown in place of the default sidebar icon. + +```json +{ + "data": { + "logo_url": "https://example.test/storage/branding/9f3c….png", + "updated_at": "2026-08-07T16:02:30+00:00" + } +} +``` + +`logo_url` is `null` when no logo has been uploaded, which is the normal state rather than an error. + +### `GET /watermark` — ability: `edit_settings` + +The mark stamped onto the thumbnails and previews clients and anonymous public visitors see. What +this installation's own staff see goes unmarked, and the stored files — including every download — +are never altered. + +```json +{ + "data": { + "enabled": true, + "image_url": "https://example.test/storage/branding/a68a….png", + "position": "bottom-right", + "size": 35, + "opacity": 55 + } +} +``` + +| Field | Meaning | +|---|---| +| `enabled` | Whether client- and public-facing images are *actually* being watermarked. False whenever nothing is drawn — including when the toggle is on but its image has since been removed. It answers "is this installation watermarking?", not "which way is the switch pointing?" What staff see is never marked regardless. | +| `image_url` | The artwork, or `null` if none was ever chosen. | +| `position` | One of `top-left`, `top-center`, `top-right`, `middle-left`, `center`, `middle-right`, `bottom-left`, `bottom-center`, `bottom-right`. | +| `size` | Percentage of the image the mark is scaled to fit inside, keeping its proportions — so a thumbnail and a preview carry the same design at different scales. 5–100. | +| `opacity` | Percentage. 1–100. | + +An installation that has never opened the branding screen answers with the defaults it would start +from (`enabled: false`, `bottom-right`, `30`, `60`) rather than a payload of nulls. + +--- + +## Deferred, on purpose + +- **Writes for either image.** See above. +- **Rendered thumbnails themselves.** Already deferred by the host ([`api-todo.md`](api-todo.md)); + the watermark endpoint exists so an integration generating its own derivative images can reproduce + the installation's mark, not as a step toward serving thumbnails over the API.