{"openapi":"3.1.0","info":{"title":"meetagain public API","version":"1.1.0","description":"Public REST API. The set of endpoints documented here depends on which plugins are active for the host you are querying; an endpoint absent from `paths` is also absent from the live router on this host. Bearer auth is required for everything tagged `Token required` (90-day expiry; issue with `POST \/api\/auth\/token`).","contact":{"name":"meetagain.org","url":"https:\/\/meetagain.org"},"license":{"name":"EUPL-1.2","url":"https:\/\/eupl.eu\/1.2\/en\/"}},"servers":[{"url":"\/"}],"tags":[{"name":"public","description":"Service health and discovery endpoints"},{"name":"data","description":"Public read-only data"},{"name":"auth","description":"Authentication \/ token issuance"},{"name":"cms","description":"CMS page management. Admin-only.","x-badge":"Token required"},{"name":"cms-blocks","description":"CMS block management. Admin-only.","x-badge":"Token required"},{"name":"logs","description":"System \/ activity \/ not-found \/ cron log streams. Admin-only.","x-badge":"Token required"}],"paths":{"\/api\/status":{"get":{"operationId":"getStatus","tags":["public"],"summary":"Service health check","x-example":"curl HOST\/api\/status","responses":{"200":{"description":"OK","content":{"application\/json":{"schema":{"type":"string"},"example":"OK"}}}}}},"\/api\/auth\/token":{"post":{"operationId":"issueAuthToken","tags":["auth"],"summary":"Issue a new Bearer token (email + password)","description":"Returns a 90-day Bearer token for use in `Authorization: Bearer ...` headers. Rate-limited per IP.","x-example":"TOKEN=$(curl -s -X POST HOST\/api\/auth\/token \\\n  -H \u0027Content-Type: application\/json\u0027 \\\n  -d \u0027{\u0022email\u0022:\u0022admin@example.com\u0022,\u0022password\u0022:\u0022...\u0022}\u0027 | jq -r .token)","requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string"}}},"example":{"email":"admin@example.com","password":"correct-horse-battery-staple"}}}},"responses":{"200":{"description":"Token issued","content":{"application\/json":{"schema":{"type":"object","properties":{"token":{"type":"string"}}},"example":{"token":"ag_4f7d9e2a1b8c6f3e5d2c1b9a8e7f6d5c4b3a2e1d"}}}},"400":{"description":"Malformed request","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}},"401":{"description":"Invalid credentials","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}},"429":{"description":"Too many attempts","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}}}},"delete":{"operationId":"revokeAuthToken","tags":["auth"],"summary":"Revoke the current Bearer token","x-example":"curl -s -X DELETE HOST\/api\/auth\/token \\\n  -H \u0022Authorization: Bearer $TOKEN\u0022","security":[{"bearerAuth":[]}],"responses":{"204":{"description":"Revoked"},"401":{"description":"Missing or invalid token","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}}}}},"\/api\/events":{"get":{"operationId":"listEvents","tags":["data"],"summary":"List upcoming public events (paginated)","x-example":"curl \u0027HOST\/api\/events?limit=10\u0026from=2026-05-01\u0027","parameters":[{"$ref":"#\/components\/parameters\/Locale"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}},{"name":"from","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"OK","content":{"application\/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/EventSummary"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}},"example":{"items":[{"id":42,"title":"Sunday Dinner: Sichuan Hot Pot","start":"2026-05-10T18:00:00+08:00","end":"2026-05-10T21:00:00+08:00","location":"Beijing Code Loft","url":"https:\/\/meetagain.org\/en\/event\/42","previewImageUrl":"https:\/\/meetagain.org\/images\/thumbnails\/abc123_600x400.webp"}],"total":1,"limit":20,"offset":0}}}}}}},"\/api\/events\/{id}":{"get":{"operationId":"getEvent","tags":["data"],"summary":"Single event detail","x-example":"curl HOST\/api\/events\/42","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"$ref":"#\/components\/parameters\/Locale"}],"responses":{"200":{"description":"OK","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/EventDetail"}}}},"404":{"description":"Not found","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}}}}},"\/api\/cms\/":{"get":{"operationId":"listCmsPages","tags":["cms"],"summary":"List all CMS pages","x-example":"curl HOST\/api\/cms\/ \\\n  -H \u0022Authorization: Bearer $TOKEN\u0022","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK"}}},"post":{"operationId":"createCmsPage","tags":["cms"],"summary":"Create page","x-example":"curl -X POST HOST\/api\/cms\/ \\\n  -H \u0022Authorization: Bearer $TOKEN\u0022 \\\n  -H \u0027Content-Type: application\/json\u0027 \\\n  -d \u0027{\u0022slug\u0022:\u0022my-page\u0022,\u0022titles\u0022:{\u0022en\u0022:\u0022My Page\u0022},\u0022linkNames\u0022:{\u0022en\u0022:\u0022My Page\u0022}}\u0027","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Created"}}}},"\/api\/cms\/{id}":{"get":{"operationId":"getCmsPage","tags":["cms"],"summary":"Get full page with blocks","x-example":"curl HOST\/api\/cms\/1 \\\n  -H \u0022Authorization: Bearer $TOKEN\u0022","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"OK"}}},"put":{"operationId":"updateCmsPage","tags":["cms"],"summary":"Update metadata (slug, published, titles, linkNames)","x-example":"curl -X PUT HOST\/api\/cms\/1 \\\n  -H \u0022Authorization: Bearer $TOKEN\u0022 \\\n  -H \u0027Content-Type: application\/json\u0027 \\\n  -d \u0027{\u0022published\u0022:true,\u0022titles\u0022:{\u0022en\u0022:\u0022Updated\u0022}}\u0027","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"OK"}}},"delete":{"operationId":"deleteCmsPage","tags":["cms"],"summary":"Delete page (not allowed if locked)","x-example":"curl -X DELETE HOST\/api\/cms\/1 \\\n  -H \u0022Authorization: Bearer $TOKEN\u0022","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"204":{"description":"Deleted"}}}},"\/api\/cms\/{id}\/blocks":{"post":{"operationId":"createCmsBlock","tags":["cms-blocks"],"summary":"Add block","x-example":"curl -X POST HOST\/api\/cms\/1\/blocks \\\n  -H \u0022Authorization: Bearer $TOKEN\u0022 \\\n  -H \u0027Content-Type: application\/json\u0027 \\\n  -d \u0027{\u0022language\u0022:\u0022en\u0022,\u0022type\u0022:1,\u0022priority\u0022:1,\u0022json\u0022:{\u0022text\u0022:\u0022Hello\u0022}}\u0027","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"201":{"description":"Created"}}}},"\/api\/cms\/{id}\/blocks\/{blockId}":{"put":{"operationId":"updateCmsBlock","tags":["cms-blocks"],"summary":"Update block json and\/or priority","x-example":"curl -X PUT HOST\/api\/cms\/1\/blocks\/5 \\\n  -H \u0022Authorization: Bearer $TOKEN\u0022 \\\n  -H \u0027Content-Type: application\/json\u0027 \\\n  -d \u0027{\u0022priority\u0022:2,\u0022json\u0022:{\u0022text\u0022:\u0022Updated\u0022}}\u0027","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"blockId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"OK"}}},"delete":{"operationId":"deleteCmsBlock","tags":["cms-blocks"],"summary":"Delete block","x-example":"curl -X DELETE HOST\/api\/cms\/1\/blocks\/5 \\\n  -H \u0022Authorization: Bearer $TOKEN\u0022","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"blockId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"204":{"description":"Deleted"}}}},"\/api\/logs":{"get":{"operationId":"getLogsSummary","tags":["logs"],"summary":"Summary of all log streams (counts + latest timestamps)","x-example":"curl \u0027HOST\/api\/logs\u0027 \\\n  -H \u0022Authorization: Bearer $TOKEN\u0022","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK"}}}},"\/api\/logs\/system":{"get":{"operationId":"getLogsSystem","tags":["logs"],"summary":"Recent file-based system log entries (Monolog rotating file)","x-example":"curl \u0027HOST\/api\/logs\/system?limit=50\u0026level=WARNING\u0027 \\\n  -H \u0022Authorization: Bearer $TOKEN\u0022","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad request - limit out of range or non-numeric"}}}},"\/api\/logs\/activity":{"get":{"operationId":"getLogsActivity","tags":["logs"],"summary":"Recent user activity stream entries","x-example":"curl \u0027HOST\/api\/logs\/activity?limit=50\u0027 \\\n  -H \u0022Authorization: Bearer $TOKEN\u0022","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad request - limit out of range or non-numeric"}}}},"\/api\/logs\/not-found":{"get":{"operationId":"getLogsNotFound","tags":["logs"],"summary":"Recent 404 not-found URL log entries","x-example":"curl \u0027HOST\/api\/logs\/not-found?limit=50\u0027 \\\n  -H \u0022Authorization: Bearer $TOKEN\u0022","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad request - limit out of range or non-numeric"}}}},"\/api\/logs\/cron":{"get":{"operationId":"getLogsCron","tags":["logs"],"summary":"Recent cron run log entries","x-example":"curl \u0027HOST\/api\/logs\/cron?limit=50\u0027 \\\n  -H \u0022Authorization: Bearer $TOKEN\u0022","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad request - limit out of range or non-numeric"}}}},"\/api\/groups":{"get":{"tags":["data"],"summary":"List public groups (multisite plugin only)","x-example":"curl HOST\/api\/groups","responses":{"200":{"description":"OK","content":{"application\/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/Group"}},"total":{"type":"integer"}}}}}},"404":{"description":"Plugin not active"}}}},"\/api\/groups\/{slug}":{"get":{"tags":["data"],"summary":"Single group detail","x-example":"curl HOST\/api\/groups\/my-group","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Group"}}}},"404":{"description":"Not found"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API token issued by `POST \/api\/auth\/token`. Token is hashed at rest, scoped to the issuing user, and expires 90 days after issue."}},"parameters":{"Locale":{"name":"locale","in":"query","description":"Locale code (e.g. `en`, `de`, `zh`). Falls back to the request\u0027s resolved locale and then the configured default.","schema":{"type":"string"}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Short human-readable failure reason"},"details":{"type":"object","additionalProperties":true}},"example":{"error":"Not found"}},"EventSummary":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"start":{"type":"string","format":"date-time"},"end":{"type":["string","null"],"format":"date-time"},"location":{"type":"string"},"url":{"type":"string","format":"uri"},"previewImageUrl":{"type":["string","null"],"format":"uri"}}},"EventDetail":{"allOf":[{"$ref":"#\/components\/schemas\/EventSummary"},{"type":"object","properties":{"description":{"type":"string"}}}]},"Group":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":["string","null"]},"domain":{"type":["string","null"]},"url":{"type":"string","format":"uri"}}}}}}