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.

Base URL: 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

PatternAuth
ptt/v3/{tenant}/audio/{client_id}tenant must match username
ptt/v3/{tenant}/presencetenant 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:

CodeMeaning
forbiddenMissing or wrong admin key
user_not_foundUsername doesn't exist
user_already_existsDuplicate on create
invalid_credentialsWrong password (or unknown user)
user_disabledActive flag is false
cross_tenantACL: trying to use another tenant's topic
forbidden_namespaceACL: topic doesn't start with ptt/v3/
room_not_foundACL: room doesn't exist
not_a_memberACL: user not in room
publish_forbiddenACL: member can_publish is false

MQTT Plugin Endpoints

POST /auth MQTT

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"}
POST /acl MQTT

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
  }'
accOperation
1read / subscribe
2write / publish
POST /superuser MQTT

Always returns 403. No superusers are configured — every operation must pass /acl.

Users

GET /admin/users admin

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
}
POST /admin/users admin

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
  }'
FieldTypeRequired
tenant_idstringyes
extensionstringyes
passwordstring (≥4 chars)yes
display_namestringno
is_adminbooleanno
activebooleanno (default true)
GET /admin/users/{username} admin

Get a single user.

curl -H "X-Admin-Key: $K" \
  http://127.0.0.1:1006/admin/users/acme:1001
PATCH /admin/users/{username} admin

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 /admin/users/{username} admin

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

GET /admin/rooms admin

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"
POST /admin/rooms admin

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
  }'
PATCH /admin/rooms/{tenant_id}/{name} admin

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 /admin/rooms/{tenant_id}/{name} admin

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

GET /admin/rooms/{tenant_id}/{name}/members admin
curl -H "X-Admin-Key: $K" \
  http://127.0.0.1:1006/admin/rooms/acme/engineering/members
POST /admin/rooms/{tenant_id}/{name}/members admin

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
  }'
FieldTypeNotes
usernamestringrequired, must exist
role"member" | "admin"default member
can_publishbooleanif false, user can sub but not pub
DELETE /admin/rooms/{t}/{n}/members/{username} admin
curl -X DELETE -H "X-Admin-Key: $K" \
  http://127.0.0.1:1006/admin/rooms/acme/engineering/members/acme:1001

Audit Log

GET /admin/audit admin

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

POST /admin/clear-cache admin

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

GET /health public

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}
}