7.3 KiB
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:
[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 IDkey: the session options key for that peer
System-level endpoints require:
systemkey: the FreeDMR system API key
Endpoints
Health
curl http://127.0.0.1:8000/api/v1/health
Version
curl http://127.0.0.1:8000/api/v1/version
Get Options
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:
{"ok":true,"connected":true,"has_options":false,"options":""}
Set Options
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
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.
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}'
dmridis 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.slotis integer1or2: the slot being controlled, which may differ from the slot carrying the SMS.actionislink,unlink,status,resetorreset_tgs. Onlylinkacceptstg, an integer target using the existing private-call dial range and exclusions.unlinkdisconnects the dial link; an existing default returns through its normal timer.resetrestores that slot's configured default immediately, or disconnects if it has no default. It does not reset the HBP session.statusreads 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:
{"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.
{"systemkey":"<system-key>","dmrid":234269001,"slot":1,"action":"reset_tgs"}
Example response (expired counts timers whose expiry effects were applied):
{"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:
[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
curl -X POST http://127.0.0.1:8000/api/v1/system/resetall \
-H 'content-type: application/json' \
-d '{"systemkey":"system-secret"}'
Stop FreeDMR
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_credentialsinvalid_jsonmissing_optionsrequest_too_largenot_foundpeer_not_connectedambiguous_peer,shared_masterrf_busy,dial_disabledinvalid_dmrid,invalid_slot,invalid_action,invalid_tg,unexpected_tgdial_unavailable