Design portal
Submit app artwork and blog copy; the requested filter is the backlog
Design portal
Five /api/v1/design-portal/* routes are mounted. They require operator type Design or Creative (case-insensitive). Anyone else — including a Content operator — gets 403 Design portal access only.
Live Design JWT (design@linkit.works) on 2026-09-10:
| Call | Result |
|---|---|
GET /design-portal/summary | 200 {pending_approval_total:0, rejected_total:0, accepted_total:348, requested_total:307} |
GET /design-portal/apps?filter=requested | 200 {items:[…]} — apps with needs_logo / needs_cover |
GET /design-portal/posts?filter=requested | 200 {items:[…]} |
| Content JWT on the same paths | 403 Design-only |
apps and posts are global catalog tables — no organization_id. The portal type is the whole boundary. A Design caller cannot open a content-portal route (probed: 403 the other way).
requested is a Rust addition. Go's four filters only describe a submission. On this tenant, pending / rejected / all are empty while 307 rows want artwork. The React portal defaults to requested. A client that never sends it cannot tell the difference.
Quick reference
| Endpoint | Purpose |
|---|---|
GET /api/v1/design-portal/summary | Four counts, plus requested_total |
GET /api/v1/design-portal/apps | ?filter= |
GET /api/v1/design-portal/posts | ?filter= |
POST /api/v1/design-portal/apps/{id}/submit | Multipart logo / cover |
POST /api/v1/design-portal/posts/{id}/submit | Multipart featured / attachment / rewrite |
A repository failure on summary is 500 here (failed to read the design summary: …). Go answered zeroes.
Filter
?filter= values:
| Value | Meaning |
|---|---|
requested | needs_* set and nothing staged — the backlog. React default |
pending | Waiting on a decision |
rejected | Sent back |
accepted | Settled, nothing pending or refused |
all or omit | pending ∪ rejected only — not accepted, not requested |
Unknown filter: 400 Invalid filter (pending|rejected|accepted|requested|all).
all is not everything. The React control is labelled "waiting or sent back".
Submit (multipart)
Not multipart is not an error (Go ignores http.ErrNotMultipart). A body that claims multipart and is malformed is 400 Expected multipart form.
Apps — 32 MiB parse cap
| Field | Effect |
|---|---|
proposed_logo | File. Sets pending_logo_approval |
proposed_cover_image | File. Sets pending_cover_approval |
pending_logo / pending_cover | Truthy (1/true/yes/on) can set the flag without a file. false leaves it alone |
Posts — 40 MiB parse cap
| Field | Effect |
|---|---|
proposed_featured_image | File |
proposed_attachment | File |
proposed_content | Rewrite. Flag only when non-blank after trim |
pending_cover / pending_attachments / pending_rewrite | Same truthy rule |
Success is {"id":"…"}. Missing id: 404 Missing id. Unknown row: 404 App not found. / Post not found. Persist failure: 400.