# FreeDMR API FreeDMR includes an experimental HTTP/JSON API for small live control-plane actions. It is intended for local administration and automation, not for public internet exposure. Enable it with: ```ini [GLOBAL] ENABLE_API: True ``` When enabled, the API listens on TCP port `8000` (`[GLOBAL] API_PORT`, if set). ## Safety Notes FreeDMR is a live voice routing process. API requests are deliberately limited to small in-memory operations so they do not delay DMR voice packet handling. Request bodies larger than 8192 bytes are rejected. Bind or firewall port `8000` appropriately. Do not expose it publicly without a trusted reverse proxy and access controls. ## Authentication User-level endpoints require: - `dmrid`: the connected HBP peer/repeater DMR ID - `key`: the session options key for that peer System-level endpoints require: - `systemkey`: the FreeDMR system API key ## Endpoints ### Health ```bash curl http://127.0.0.1:8000/api/v1/health ``` ### Version ```bash curl http://127.0.0.1:8000/api/v1/version ``` ### Get Options ```bash curl -X POST http://127.0.0.1:8000/api/v1/options/get \ -H 'content-type: application/json' \ -d '{"dmrid":1234567,"key":"secret"}' ``` If no live options are present, the response is: ```json {"ok":true,"connected":true,"has_options":false,"options":""} ``` ### Set Options ```bash curl -X POST http://127.0.0.1:8000/api/v1/options/set \ -H 'content-type: application/json' \ -d '{"dmrid":1234567,"key":"secret","options":"KEY=secret;TS1=91;DIAL=2350"}' ``` The `options` value must be the complete FreeDMR `OPTIONS` string. The API does not add or preserve `KEY=...` automatically. ### Reset Peer Session ```bash curl -X POST http://127.0.0.1:8000/api/v1/reset \ -H 'content-type: application/json' \ -d '{"dmrid":1234567,"key":"secret"}' ``` FreeDMR expects one HBP peer per master instance, so this resets the master instance that owns the authenticated peer session. ### Dial-a-TG Control `POST /api/v1/dial` is for a trusted SMS backend or local automation. It uses the **system API key**, not the peer's OPTIONS key. SMS decoding and command syntax are not implemented by FreeDMR. ```bash curl -X POST http://127.0.0.1:8000/api/v1/dial \ -H 'content-type: application/json' \ -d '{"systemkey":"system-secret","dmrid":1234567,"slot":1,"action":"link","tg":235}' ``` - `dmrid` is the connected ingress **hotspot/repeater ID**, not the SMS sender's subscriber ID or the SMS service destination. The trusted backend must derive it and the owning worker from observed SMS ingress metadata, never from SMS text. This API does not itself verify an SMS or its origin. - `slot` is integer `1` or `2`: the slot being controlled, which may differ from the slot carrying the SMS. - `action` is `link`, `unlink`, `status`, `reset` or `reset_tgs`. Only `link` accepts `tg`, an integer target using the existing private-call dial range and exclusions. - `unlink` disconnects the dial link; an existing default returns through its normal timer. `reset` restores that slot's configured default immediately, or disconnects if it has no default. It does **not** reset the HBP session. - `status` reads the current dial link without changing route timers. The four dial actions schedule a TG9 voice announcement on the controlled slot, using source ID 5000 and the configured language. Responses describe current state, not confirmation that the user heard the announcement: ```json {"ok":true,"system":"HOTSPOT-0","slot":1,"action":"link","tg":235,"default_tg":91,"announcement":"scheduled"} ``` `tg: 0` means no active dial link. OPTIONS, configured defaults, timer durations, conventional static TGs, other slots and other systems are left unchanged. Disabled dial-a-TG and AllStar mode reject these commands. Active RF voice on the controlled slot rejects the request with `409 rf_busy`; active network voice and hangtime do not block control. Missing RF terminators use the existing stream timeout. Voice prompts use the existing pacing/cancellation mechanism. `reset_tgs` is separate from dial reset. It immediately expires running **non-dial** bridge timers for the selected peer and slot: active `ON` entries are deactivated, and inactive `OFF` entries are reactivated (restoring temporarily disconnected static TGs). It leaves dial links, untimed entries, other peers and slots, OPTIONS, configured defaults and timer durations unchanged. It works with dial-a-TG disabled; AllStar mode and active RF voice still reject the request. Network voice and hangtime do not block it. Like ordinary non-dial timer expiry, it generates no dial-link voice announcement. ```json {"systemkey":"","dmrid":234269001,"slot":1,"action":"reset_tgs"} ``` Example response (`expired` counts timers whose expiry effects were applied): ```json {"ok":true,"system":"HOTSPOT-0","slot":1,"action":"reset_tgs","expired":2,"announcement":"none"} ``` Only a fully connected HBP peer in this process is eligible. An absent or logging-in peer returns `404 peer_not_connected`. Duplicate connected peer IDs return `409 ambiguous_peer`. A master with multiple connected peers returns `409 shared_master`, because dial state belongs to the master, not each peer. These conditions do not trigger forwarding or peer migration. ### HDStack Worker APIs Worker APIs remain disabled by default, including when the aggregator's `ENABLE_API` is true. To enable direct worker control, add to the main config: ```ini [GLOBAL] HDSTACK_ENABLE_API: True API_PORT: 8000 ``` Worker N listens on `API_PORT + N`: normally `8001` through `8005`. With `HDSTACK=1`, use the normal `ENABLE_API: True` setting and port `8000` instead; `HDSTACK_ENABLE_API` only affects generated multi-worker configs. The aggregator retains its independent `ENABLE_API` setting and base port; it does not relay requests to workers. Generated port conflicts are rejected at startup. Each worker uses its existing numbered key store, for example `[ALIASES] PATH` plus `keys-hdstack-1.json` when `KEYS_FILE` is `keys.json`. Use that worker's `SYSTEM_API_KEY`; existing key generation/persistence is unchanged. Configure the trusted backend with each worker URL and key and use the SMS ingress worker identity to select the endpoint. The aggregator key is not a replacement for worker keys. Listeners retain the existing `0.0.0.0` binding. No Docker ports are published automatically: a backend on the same private container network can reach the worker ports directly. Keep them off the public internet; any remote access requires a separately configured protected path. The full existing API is available on enabled worker listeners, so treat their system keys as privileged. ### Reset All Connections ```bash curl -X POST http://127.0.0.1:8000/api/v1/system/resetall \ -H 'content-type: application/json' \ -d '{"systemkey":"system-secret"}' ``` ### Stop FreeDMR ```bash curl -X POST http://127.0.0.1:8000/api/v1/system/kill \ -H 'content-type: application/json' \ -d '{"systemkey":"system-secret"}' ``` ## Responses Successful responses include `"ok": true`. Failed responses include `"ok": false` and an `error` string. Common errors: - `invalid_credentials` - `invalid_json` - `missing_options` - `request_too_large` - `not_found` - `peer_not_connected` - `ambiguous_peer`, `shared_master` - `rf_busy`, `dial_disabled` - `invalid_dmrid`, `invalid_slot`, `invalid_action`, `invalid_tg`, `unexpected_tg` - `dial_unavailable`