π API Reference
- transport:
- same-origin JSON, multipart upload, and server-sent events
- every
/api/*route still requires trusted proxy identity and rejects a missingRemote-User
- production identity since D-169 (2026-09-26):
- anonymous requests receive trusted
Remote-User: publicwith empty groups - optional Authelia SSO supplies a signed-in user and groups; client-supplied identity headers are overwritten
- admin routes require
adminsorpwiz-admins; anonymous and signed-in non-admin requests return 403 - signed-in library queries use that identity; anonymous saved/history/owned-wizard rows share
public
- anonymous requests receive trusted
- operator-local diagnostics:
- loopback service may be called with explicit trusted headers as shown in π Private runbook (login)
- error contract:
- handlers return
{ error: string }with a real non-2xx status on failure - successful routes do not fabricate success data when the underlying operation fails
- handlers return
Identity
GET /api/me
- source:
- production: nginx-injected optional SSO identity, falling back to
publicfor anonymous requests - local test mode: loopback-only test header
- production: nginx-injected optional SSO identity, falling back to
- response:
{ id, username, is_admin }- anonymous value:
{"id":"public","username":"public","is_admin":false}; a signed-in response reflects that user and its group-derived admin status
- test-only policy:
X-Pwiz-Test-Useris accepted only whenPWIZ_ALLOW_TEST_IDENTITY=1and the request originates from loopback- production requires a trusted
Remote-User; nginx supplies optional SSO identity or the anonymouspublicfallback
Collections
GET /api/collections/:name?filter=<json>
- names:
wizardsblueprintsprompt_historysaved_promptslikesadmin_promptsllm_logs
- collection policy:
- collection routes require trusted identity: signed-in SSO user or the shared anonymous
publicuser wizards: seeded globals (user_id IS NULL) are admin-writable; ordinary users may mutate only owned rowsadmin_prompts: authenticatedGET, admin-only write methodsllm_logs: fully admin-only for every methodlikes: authenticated reads only; generic collection writes are forbidden and/api/likes/toggleis the sole mutation route
- collection routes require trusted identity: signed-in SSO user or the shared anonymous
- response:
- newest-first array
- filter:
- URL-encoded JSON object
- equality only
- allowed indexed keys:
is_publicuser_idblueprint_idkey
- unknown key: 400
POST /api/collections/:name
- request:
- JSON row payload
- server-owned fields:
idcreated_at- applicable
user_id
- response:
- created row
- collection scope:
admin_prompts: admin-onlyllm_logs: admin-onlylikes: disabled; use/api/likes/toggle
- event:
- broadcasts affected collection
PATCH /api/collections/:name/:id
- request:
- partial JSON patch
- response:
- updated row or mutation result
- collection scope:
wizards: seeded globals (user_id IS NULL) require admin membership; owned custom wizards require owner identityadmin_prompts: admin-onlyllm_logs: admin-onlylikes: disabled; use/api/likes/toggle
- event:
- broadcasts affected collection
DELETE /api/collections/:name/:id
- response:
- 204 no-content
- collection scope:
wizards: seeded globals (user_id IS NULL) require admin membership; owned custom wizards require owner identityadmin_prompts: admin-onlyllm_logs: admin-onlylikes: disabled; use/api/likes/toggle
- event:
- broadcasts affected collection
Likes
POST /api/likes/toggle
- request:
{ blueprint_id }
- transaction:
- if not liked, insert like row and increment blueprint count
- if liked, delete like row and decrement blueprint count
- response:
{ liked: boolean, likes_count: number }
- write boundary:
- this route is the only supported likes mutation path
- generic
POST,PATCH, andDELETEthrough/api/collections/likesare forbidden
- events:
- broadcasts
likes - broadcasts
blueprints
- broadcasts
Uploads
POST /api/uploads
- request:
- multipart form-data
- field:
file
- accepted types:
image/pngimage/jpegimage/webp
- maximum size:
- 8 MB
- write target:
data/uploads/<uuid>.<ext>
- response:
{ url: "/uploads/<uuid>.<ext>" }
AI text
POST /api/ai/text
- request:
{ system, messages, json, kind, templateKey, requestId?, thoughtsTab? }- only Live generation sends its random per-page
thoughtsTab
- response:
{ content, timing }; timing is null for successful non-JSON text calls- JSON timing fields:
requestedModel,resolvedModel, queue/attempt/repair/total; D-479g defaults requestedModel tohost default, with no forced model flag unless the operator explicitly overrides it - admin timing rows expose
requested_modelandresolved_model; summaries identify the latest requested route, not a resolved provider for every call
- behavior:
- calls
ompText - successful text AI calls are stored in
llm_logs(admin-only) - JSON mode first tries local repair, then the existing single rerun on invalid output
kind: "ideas"enforces an object{ideas: [...]}containing 5 or 6 title/description entries; parsing and local repair validate the complete batch, not a singleton extracted from an array
- calls
- errors:
- 400 for invalid request
- 502 for generation failure or invalid JSON after retry
- 504 for timeout
See omp-integration for command details.
AI image
POST /api/ai/image
- request:
{ subject, aspectRatio, kind, system }systemis optional; when omitted, the server uses the built-in safe image system prompt
- response:
{ url, provider, model }
- behavior:
- calls
ompImage - copies generated WebP into uploads
- successful image AI calls are stored in
llm_logs(admin-only); this route does not insert a timing row
- calls
- accepted aspect ratios:
1:13:44:39:1616:9
- errors:
- 400 for invalid subject or aspect ratio
- 502 for tool failure or missing image event
- 504 for timeout
Client errors
POST /api/errors
- request:
{ type, message, stack }
- server action:
- log with client prefix
- response:
- 204
Logging, retention and public storage
Successful text/image AI calls are stored in llm_logs (admin-only) in the serverβs data/pwiz.db. Text rows retain kind/template key, submitted system text, the JSON-encoded message list, final response content/timing and timestamp. Image rows retain supplied context and the result URL/provider/model/aspect ratio; bytes are under data/uploads.
JSON text calls with timing data also insert queue/attempt/repair/outcome data into llm_timings. Timed OMP failures can create failed timing rows without a successful log row; this is not logging of every failed HTTP request. Successful plain-text/diagram calls and image calls do not insert timing rows.
Since D-479g, the requested route is stored separately from the final OMP-reported provider/model. host default means no app-level model flag was supplied; unknown means no final model metadata was reported. Historical requested values are preserved and old resolution stays unknown. This is reported provenance, not independent upstream verification.
There is no automatic log expiry/TTL or logging-disable setting in the app. Rows remain until an admin deletes them. Developer tools Off and thought display Off do not disable prompt/result logging. Stored prompt/result content is not blanket-redacted; do not submit credentials.
Live thought relay chunks are bounded, in-memory data, not a persisted raw reasoning transcript. Caught React errors and server/OMP diagnostics go to the service journal. Inherited nginx/journal retention, OMP-core telemetry and upstream provider logging/retention have not been established. No app-owned external analytics sender was found in this code path; this does not establish zero external telemetry.
Anonymous project/history/likes/custom wizard rows share the public identity. Upload URLs have no per-record user access check. Phase metadata is identity-scoped, so public tabs can receive one anotherβs phases; live thoughts additionally require the matching tab UUID.
Events
GET /api/events
- protocol:
- server-sent events
- headers:
Content-Type: text/event-streamCache-Control: no-cacheX-Accel-Buffering: no
- event payload:
data: {"collection":"<name>"}
- keepalive:
- comment every 25 seconds
See runtime for the client useQuery invalidation model.