HTTP API
The routes the interface uses. They're stable within a major version, but they exist for the UI, not as a public integration API.
All mutating requests need a same-origin Origin header (browsers send it). Errors are JSON: { "error": "<code>", "details"?: { field: message } }.
| Code | Status | Meaning |
|---|---|---|
bad_request | 400 | Invalid input, see details |
unauthorized | 401 | Not signed in as admin, or wrong password |
invalid_code | 401 | Wrong event access code |
forbidden | 403 | Cross-site request, or the guest hasn't joined the event |
uploads_closed | 403 | Uploads are off or past their closing time |
quota_reached | 403 | The guest reached the per-guest limit |
not_found | 404 | Doesn't exist, or exists but isn't yours |
too_large | 413 | Body above MAX_UPLOAD_MB |
invalid_image | 415 | Not a supported image |
rate_limited | 429 | Slow down; see Retry-After |
Guest routes
Authenticated by the gr_guest cookie.
| Route | Body | Response |
|---|---|---|
POST /api/e/:slug/join | none | { ok }. Events without an access code only. |
POST /api/e/:slug/unlock | { code } | { ok } |
POST /api/e/:slug/guest | { name } | { name }. Stored in the cookie. |
POST /api/e/:slug/photos | multipart, field photo | 201 { photo: PhotoDto } |
GET /api/e/:slug/photos?before=<ms> | { photos: PhotoDto[], nextCursor }, the guest's own photos only | |
GET /api/e/:slug/logo?v=<n> | The event logo (PNG), public with the slug | |
GET /api/media/:id?v=thumb|display|original[&download=1] | The image, if the viewer may see it |
interface PhotoDto {
id: string;
width: number;
height: number;
guestName: string | null;
status: "visible" | "pending" | "hidden";
mine: boolean;
downloads?: number; // admins only
createdAt: number; // epoch ms
}Admin routes
Authenticated by the gr_admin cookie.
| Route | Body | Response |
|---|---|---|
POST /api/admin/login | { password } | { ok } |
POST /api/admin/logout | { ok } | |
POST /api/admin/password | { current, next } | { ok } |
GET /api/admin/events | { events: EventSummary[] } | |
POST /api/admin/events | event fields, name required | 201 { event } |
GET /api/admin/events/:id | { event } | |
PATCH /api/admin/events/:id | any event fields | { event } |
DELETE /api/admin/events/:id | { ok }, photos deleted from disk | |
POST /api/admin/events/:id/rotate | { event } with a new slug | |
POST /api/admin/events/:id/logo | multipart, field logo | { event } |
DELETE /api/admin/events/:id/logo | { event } | |
GET /api/admin/events/:id/qr?format=svg|png&size=512..4096&download=1 | The styled QR code | |
GET /api/admin/events/:id/photos?status=&before= | { photos, nextCursor } | |
POST /api/admin/events/:id/photos | { action: "approve"|"hide"|"delete", ids } | { changed } |
GET /api/admin/events/:id/archive[?all=1] | Streamed ZIP; all=1 includes pending and hidden | |
GET /api/admin/stats[?eventId=] | Totals, time series, top guests |
Event fields: name, description, eventDate (YYYY-MM-DD), accentColor (#rrggbb), accessCode (string to set, null to remove), uploadsOpen, uploadsCloseAt (epoch ms or null), moderation (post|pre), captureMode (camera|picker|both), askGuestName, maxPhotosPerGuest, defaultLocale (en|fr|it|es|null), qrStyle (object, see src/lib/qr/style.ts).
Health
GET /api/health returns 200 {"status":"ok"}, or 503 when the database can't be read.