You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
FreeDMR/docs/api.md

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 ID
  • key: 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}'
  • 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:

{"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_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

Powered by TurnKey Linux.