API Reference
GEN PTT Auth Server — version 3.0
Overview
This server exposes three groups of endpoints:
MQTT Plugin — /auth, /acl, /superuser. Called by Mosquitto over loopback. No admin key needed (Mosquitto plugin doesn't send one).
Admin — Full CRUD on users, rooms, members, audit log. Requires X-Admin-Key header.
Public — /health, the admin UI, and these docs.
http://127.0.0.1:1006 — bound to localhost only.
Reach it via SSH tunnel or a reverse proxy with auth.
Authentication
Send the admin key in the X-Admin-Key header on every admin request:
curl -H "X-Admin-Key: $PTT_ADMIN_KEY" http://127.0.0.1:1006/admin/users
Username format
MQTT usernames follow the pattern tenant_id:extension, e.g. acme:1001. The colon separator is mandatory and used everywhere users are referenced.
Topic conventions
| Pattern | Auth |
|---|---|
| ptt/v3/{tenant}/audio/{client_id} | tenant must match username |
| ptt/v3/{tenant}/presence | tenant must match username |
| ptt/v3/{tenant}/room/{room}/... | tenant match + room exists + user is member |
Error responses
Errors return HTTP 4xx with a JSON body: {"detail": "error_code"}. Common codes:
| Code | Meaning |
|---|---|
| forbidden | Missing or wrong admin key |
| user_not_found | Username doesn't exist |
| user_already_exists | Duplicate on create |
| invalid_credentials | Wrong password (or unknown user) |
| user_disabled | Active flag is false |
| cross_tenant | ACL: trying to use another tenant's topic |
| forbidden_namespace | ACL: topic doesn't start with ptt/v3/ |
| room_not_found | ACL: room doesn't exist |
| not_a_member | ACL: user not in room |
| publish_forbidden | ACL: member can_publish is false |
MQTT Plugin Endpoints
Verify MQTT CONNECT credentials.
Request
curl -X POST http://127.0.0.1:1006/auth \
-H "Content-Type: application/json" \
-d '{"username":"acme:1001","password":"secret","clientid":"phone-1"}'
Success — 200
{"result":"allow"}
Failure — 403
{"detail":"invalid_credentials"}
Authorize a subscribe (acc=1) or publish (acc=2) on a topic.
curl -X POST http://127.0.0.1:1006/acl \
-H "Content-Type: application/json" \
-d '{
"username": "acme:1001",
"topic": "ptt/v3/acme/room/engineering/audio",
"acc": 2
}'
| acc | Operation |
|---|---|
| 1 | read / subscribe |
| 2 | write / publish |
Always returns 403. No superusers are configured — every operation must pass /acl.
Users
List users. Optional query parameters: tenant_id, limit (default 100), offset.
curl -H "X-Admin-Key: $K" \
"http://127.0.0.1:1006/admin/users?tenant_id=acme&limit=50"
{
"users": [
{"id":1,"username":"acme:1001","tenant_id":"acme","extension":"1001",
"display_name":"Alice","active":1,"is_admin":0,"created_at":"2026-04-28 …"}
],
"count": 1
}
Create a user. username is derived as tenant_id:extension automatically.
curl -X POST -H "X-Admin-Key: $K" -H "Content-Type: application/json" \
http://127.0.0.1:1006/admin/users \
-d '{
"tenant_id": "acme",
"extension": "1001",
"password": "secretpw",
"display_name": "Alice",
"is_admin": false,
"active": true
}'
| Field | Type | Required |
|---|---|---|
| tenant_id | string | yes |
| extension | string | yes |
| password | string (≥4 chars) | yes |
| display_name | string | no |
| is_admin | boolean | no |
| active | boolean | no (default true) |
Get a single user.
curl -H "X-Admin-Key: $K" \
http://127.0.0.1:1006/admin/users/acme:1001
Update one or more fields. Caches are invalidated automatically.
curl -X PATCH -H "X-Admin-Key: $K" -H "Content-Type: application/json" \
http://127.0.0.1:1006/admin/users/acme:1001 \
-d '{"password":"newpw","active":true,"display_name":"Alice Smith"}'
Any of password, display_name, active, is_admin may be sent.
Delete a user. All room memberships for that user are removed.
curl -X DELETE -H "X-Admin-Key: $K" \
http://127.0.0.1:1006/admin/users/acme:1001
Rooms
List rooms. Optional tenant_id, limit, offset query params.
curl -H "X-Admin-Key: $K" \
"http://127.0.0.1:1006/admin/rooms?tenant_id=acme"
Create a room. The room id is built as tenant_id/name.
curl -X POST -H "X-Admin-Key: $K" -H "Content-Type: application/json" \
http://127.0.0.1:1006/admin/rooms \
-d '{
"tenant_id": "acme",
"name": "engineering",
"description": "Eng team channel",
"active": true
}'
Update description or active flag.
curl -X PATCH -H "X-Admin-Key: $K" -H "Content-Type: application/json" \
http://127.0.0.1:1006/admin/rooms/acme/engineering \
-d '{"active":false}'
Delete a room. All members are removed automatically.
curl -X DELETE -H "X-Admin-Key: $K" \
http://127.0.0.1:1006/admin/rooms/acme/engineering
Room Members
curl -H "X-Admin-Key: $K" \
http://127.0.0.1:1006/admin/rooms/acme/engineering/members
Add a user to a room. The user's tenant must match the room's tenant.
curl -X POST -H "X-Admin-Key: $K" -H "Content-Type: application/json" \
http://127.0.0.1:1006/admin/rooms/acme/engineering/members \
-d '{
"username": "acme:1001",
"role": "admin",
"can_publish": true
}'
| Field | Type | Notes |
|---|---|---|
| username | string | required, must exist |
| role | "member" | "admin" | default member |
| can_publish | boolean | if false, user can sub but not pub |
curl -X DELETE -H "X-Admin-Key: $K" \
http://127.0.0.1:1006/admin/rooms/acme/engineering/members/acme:1001
Audit Log
Returns recent events. Filters: event, username, limit (default 100), offset.
curl -H "X-Admin-Key: $K" \
"http://127.0.0.1:1006/admin/audit?event=auth&limit=50"
Event types
auth, acl, admin_create_user, admin_update_user, admin_delete_user, admin_create_room, admin_update_room, admin_delete_room, admin_add_member, admin_remove_member
Cache Control
Drop all auth, ACL and fail caches. Use after bulk DB changes via the seed.py CLI.
curl -X POST -H "X-Admin-Key: $K" \
http://127.0.0.1:1006/admin/clear-cache
Single-user / single-room edits via the admin API auto-invalidate their caches; you only need this after the CLI tool changes data.
Health
Lightweight DB ping plus cache sizes. No auth required.
curl http://127.0.0.1:1006/health
{
"status": "ok",
"service": "gen-ptt-auth-server",
"version": "3.0",
"db": {"ok": true, "users": 12, "rooms": 3},
"cache_size": {"auth": 4, "acl": 9, "fail": 0}
}