15 KiB
Patriot API
The Alarm24 Patriot API is a REST API used to manage clients, zones, and users in the format required by Patriot.
🔐 Authentication
Every request must include a authorization header:
Authorization: Bearer <API_KEY>
If the key is missing, invalid, disabled, or expired → 401 Unauthorized.
📘 Common Status Codes
| Status | Meaning |
|---|---|
| 200 OK | Request successful |
| 201 Created | Resource created |
| 204 No Content | Resource deleted |
| 401 Unauthorized | Missing/invalid/disabled/expired API key |
| 404 Not Found | Client/zone/user not found |
| 409 Conflict | Duplicate record, or client temporarily locked |
| 422 Unprocessable Entity | Validation failed, check request format |
🧩 Required Headers
Some GET endpoints require IDs in HTTP headers:
| Header | Purpose |
|---|---|
| X-Client-Id | Selects the client |
| X-User-No | Selects a specific user |
All endpoints also require the Authorization: Bearer header.
🗄 Data Isolation & XML Generation
Per-key Data Isolation
Each API key has its own completely separate dataset:
- Clients, zones, and users are not shared between API keys.
- Two different keys can both have a client
1234, but those are separate clients internally.
XML Output Structure
For each API key, XML files for new clients are written to temporary storage before import:
out/clients/<key_name>/<client_id>.xml
Example:
out/clients/Alarm24Key/9998.xml
out/clients/InstallerXKey/1234.xml
<__id> with Port Suffix
Each key in keys.json has a port field, e.g.:
{
"key_name": "Alarm24",
"key": "secure-api-key",
"enabled": true,
"valid_to": "2030-01-01T00:00:00Z",
"port": "BASE11"
}
When generating XML, the <__id> element is:
<__id>9998BASE11</__id>
9998= client IDBASE11=portvalue fromkeys.json
The XML filename remains 9998.xml (client id only).
Extra XML Fields Injected Per Key
Some XML fields are not provided by the API, but are automatically injected from keys.json for each key:
Examples (exact names depend on your keys.json):
<Installer>Installer Name</Installer>
<UseGlobCallOuts>True</UseGlobCallOuts>
<ShowOnCallOuts>False</ShowOnCallOuts>
<GlobCallOuts>TLF01BASE01</GlobCallOuts>
<UseGlobCallOuts2>True</UseGlobCallOuts2>
<ShowOnCallOuts2>False</ShowOnCallOuts2>
<GlobCallOuts2>TLF02BASE01</GlobCallOuts2>
<AltLookup>True</AltLookup>
<AltAlarmNo>CID4BASE01</AltAlarmNo>
<ConvertType>None</ConvertType>
<SIGINTERPRET>SIADecimal</SIGINTERPRET>
<ClientGroupings>
<ClientGrouping>
<Description>Alarm24</Description>
<GroupingTypeDescription>Alarm24 Tilgang</GroupingTypeDescription>
<GroupingAllowMultiple>True</GroupingAllowMultiple>
</ClientGrouping>
<ClientGrouping>
<Description>Import</Description>
<GroupingTypeDescription>Østfold Sikkerhetsservice AS</GroupingTypeDescription>
<GroupingAllowMultiple>True</GroupingAllowMultiple>
</ClientGrouping>
</ClientGroupings>
These are configured per key and not exposed as API fields, and can therefore not be changed by the client.
Auto-added Installer User (UserNo 199)
For each client, the XML will also contain an additional user (not managed via API):
<User>
<User_Name>Installer Name</User_Name>
<Email>installer@email.com</Email>
<UserNo>199</UserNo>
<CallOrder>0</CallOrder>
<Type>N</Type>
</User>
The actual User_Name and Email come from the API key configuration (keys.json).
This is the clients installer company. Make sure the name and e-mail is identical to whats stored in Patriot, otherwise it will generate a new installer on first import.
📂 API Endpoints
Below are the available endpoints and request/response formats.
Note: All examples assume you are also sending the correct
Authorization: Bearer <API_KEY>header.
🔹 CLIENTS
GET /clients
Get full client info (info + zones + users).
Also regenerates XML for this client if the XML file is missing (Only when the client is not present in DB, and is pending import).
Headers:
X-Client-Id: 9998
Authorization: Bearer <API_KEY>
Response (example):
{
"client_id": 9998,
"info": { ... },
"zones": { ... },
"users": { ... }
}
PUT /clients
Create or update a client (UPSERT).
- Returns 201 Created on first creation.
- Returns 200 OK on subsequent updates.
Body (all supported fields):
{
"client_id": 9998,
"info": {
"Name": "Ola Nordmann",
"Alias": "",
"Location": "Produksjonsveien 13",
"area_code": "1604",
"area": "Fredrikstad",
"BusPhone": "69310000",
"Email": "post@ostsik.no",
"OKPassword": "franzjager",
"SpecRequest": "Dette skal gjøres ved alarm på denne kunden.",
"NoSigsMon": 1,
"SinceDays": 1,
"SinceHrs": 0,
"SinceMins": 30,
"ResetNosigsIgnored": true,
"ResetNosigsDays": 7,
"ResetNosigsHrs": 0,
"ResetNosigsMins": 0,
"InstallDateTime": "2023-02-20",
"PanelName": "Ajax",
"PanelSite": "Stue",
"KeypadLocation": "Inngang",
"SPPage": "Ekstra informasjon som kan være relevant."
}
}
Some fields may be auto-defaulted if omitted, depending on implementation.
PATCH /clients
Partial update of client info. Only the fields inside info that you send will be updated; the rest are left unchanged.
Example: update only the Location:
{
"client_id": 9998,
"info": {
"Location": "Ny gate 99"
}
}
You can also update any combination of supported fields, for example:
{
"client_id": 9998,
"info": {
"BusPhone": "12345678",
"SPPage": "Oppdatert tekst"
}
}
DELETE /clients
Delete an entire client and its XML file for the current API key.
This does not touch clients of other API keys, even if they use the same client_id.
Body:
{
"client_id": 9998
}
Response: 204 No Content on success.
🔹 ZONES
GET /clients/zones
List all zones for a client.
Headers:
X-Client-Id: 9998
Authorization: Bearer <API_KEY>
Response example:
{
"client_id": 9998,
"zones": [
{
"Zone_No": 1,
"Zone_area": "Inngang",
"ModuleNo": 0
},
{
"Zone_No": 2,
"Zone_area": "Stue",
"ModuleNo": 0
}
]
}
POST /clients/zones/list
Alternative way to list zones using JSON body.
Body:
{
"client_id": 9998
}
PUT /clients/zones
Create or update (UPSERT) a zone.
Body:
{
"client_id": 9998,
"zone_id": 1,
"Zone_area": "Inngang",
"ModuleNo": 0
}
zone_idmust be between 1 and 999.- Returns 201 Created for both create and update (by design).
DELETE /clients/zones
Delete a specific zone for a client.
Body:
{
"client_id": 9998,
"zone_id": 1
}
Response: 204 No Content on success.
🔹 USERS
GET /clients/users
List all users for a client.
Headers:
X-Client-Id: 9998
Authorization: Bearer <API_KEY>
Response example:
{
"client_id": 9998,
"users": [
{
"User_Name": "User Number 1",
"MobileNo": "+4712345678",
"MobileNoOrder": 1,
"Email": "user@email.com",
"Type": "U",
"UserNo": 1,
"Instructions": "Optional",
"CallOrder": 0
}
]
}
Note: The installer user (UserNo 199) is in XML only and not exposed via this API.
GET /clients/users/single
Get a single user by number.
Headers:
X-Client-Id: 9998
X-User-No: 1
Authorization: Bearer <API_KEY>
POST /clients/users/list
List all users via JSON body.
Body:
{
"client_id": 9998
}
POST /clients/users
Create a new user.
Body:
{
"client_id": 9998,
"user": {
"User_Name": "User Number 1",
"MobileNo": "+4712345678",
"MobileNoOrder": 1,
"Email": "user@email.com",
"Type": "U",
"UserNo": 1,
"Instructions": "Optional",
"CallOrder": 0
}
}
If UserNo already exists → 409 Conflict.
POST /clients/users/get
Get one specific user using JSON body.
Body:
{
"client_id": 9998,
"user_no": 1
}
PUT /clients/users
Update/replace a user completely.
Body:
{
"client_id": 9998,
"user": {
"User_Name": "Changed Name",
"MobileNo": "+4798765432",
"MobileNoOrder": 1,
"Email": "new@email.com",
"Type": "U",
"UserNo": 1,
"Instructions": "New instructions",
"CallOrder": 0
}
}
User is identified by client_id + UserNo.
DELETE /clients/users
Delete a specific user.
Body:
{
"client_id": 9998,
"user_no": 1
}
Response: 204 No Content on success.
You can run the API as a systemd service (recommended) or screen/tmux process.
Copy "main.py" and "run_server.py" to (/opt/patriot_api/)
Example systemd unit (/etc/systemd/system/patriot-api.service):
[Unit]
Description=Patriot Client Registry API
After=network.target
[Service]
# Run as this user
User=root
Group=root
WorkingDirectory=/opt/patriot_api
# Environment variables (optional overrides)
#Environment=DATA_FILE=/opt/patriot_api/data.json
Environment=KEY_FILE=/opt/patriot_api/keys.json
Environment=XML_DIR=/opt/patriot_api/out/clients
# Start uvicorn from the venv
ExecStart=/opt/patriot_api/.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8081 --proxy-headers
# Restart behavior
Restart=always
RestartSec=3
# Logging (stdout/stderr go to journalctl)
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Then:
sudo systemctl daemon-reload
sudo systemctl enable patriot-api.service
sudo systemctl start patriot-api.service
Logs will be available both in:
/opt/patriot_api/api_requests.logjournalctl -u patriot-api.service -f
🔁 Companion Service: XML Combiner & SMB Uploader
In addition to the API, there is a separate Python script that:
- Scans all per-client XML files from the API:
/opt/patriot_api/out/clients/**/<client_id>.xml - Combines them into a single
clients.xmlfile under:/opt/patriot_api/ready_for_import/clients.xml - Uploads this combined
clients.xmlto a Windows SMB share. - Tracks import state per client in
client_state.json. - Logs missing or failed imports based on a results file.
The script is designed to run as a daemon, sleeping until ~1 minute before the next full hour, then performing a combine/upload run.
Script Location & Paths
Example paths used in the script:
XML_ROOT_PATH = Path("/opt/patriot_api/out/clients") # per-client XMLs from API
READY_DIR = Path("/opt/patriot_api/ready_for_import") # combined XML output
COMBINED_FILENAME = "clients.xml"
LOG_FILE = "/opt/patriot_api/xml_combine.log"
ERROR_LOG_FILE = "/opt/patriot_api/import_errors.log"
CLIENT_STATE_FILE = Path("/opt/patriot_api/client_state.json")
Client State Tracking
The script maintains a JSON file:
/opt/patriot_api/client_state.json
After a successful combine+upload, every client <__id> in that batch is marked:
{
"9998BASE11": {
"status": "pending_import",
"last_batch": "2025-01-01T12:55:00"
},
"1234BASE05": {
"status": "pending_import",
"last_batch": "2025-01-01T12:55:00"
}
}
This is used for tracking what was included in which batch.
SMB / Windows Share Upload
The script uploads clients.xml to an SMB share using the pysmb library.
Key config:
SMB_ENABLED = True
SMB_SERVER_IP = "10.181.149.83"
SMB_SERVER_NAME = "PATRIOT"
SMB_SHARE_NAME = "api_import"
SMB_USERNAME = "administrator"
SMB_PASSWORD = "...."
SMB_DOMAIN = "WORKGROUP"
SMB_REMOTE_PATH = "clients.xml"
SMB_RESULTS_FILENAME = "clients_Import_Results.txt"
Upload behavior:
-
Optional pre-upload check:
- Attempts to download existing
clients.xmlandclients_Import_Results.txtfrom the share. - Parses all
<__id>values from the remoteclients.xml. - For each
__id, checks whetherclients_Import_Results.txtcontains a line with that id and the phrase"Completed processing client". - Any ids missing a success line are logged into
import_errors.logwith timestamp.
- Attempts to download existing
-
Rotation of existing clients.xml:
- If
clients.xmlalready exists on the share, it is renamed with a timestamp, e.g.:
clients_20250101_120000.xml
- If
-
Upload new clients.xml:
- The new combined XML is uploaded as
clients.xml.
- The new combined XML is uploaded as
-
Local cleanup:
- If upload succeeds, the per-client XMLs that were included in this batch are deleted from
out/clients/.... - If upload fails, per-client XMLs are not deleted, so the batch can be retried later.
- If upload succeeds, the per-client XMLs that were included in this batch are deleted from
Combining Logic
- The script looks for all
*.xmlfiles underXML_ROOT_PATH(recursively). - It parses each file and extracts
<Row>elements with a<__id>child. - Rows are keyed by
__id; if the same__idappears multiple times, the last one wins in the combined XML. - All rows are appended under a single root
<Clients>element. - A limit
MAX_CLIENTS_PER_RUNcan be set (default 300).
Scheduling
The script is written to run in an infinite loop:
if __name__ == "__main__":
main()
Where main() essentially:
- Calls
sleep_until_next_run()→ sleeps until ~hh:59 before the next hour. - Runs
combine_xml_once(). - Sleeps 1 second as a safety fallback.
- Repeats.
You can run it as a systemd service (recommended) or screen/tmux process.
Example systemd unit (/etc/systemd/system/patriot-xml-combine.service):
[Unit]
Description=Patriot XML Combiner & SMB Uploader
After=network-online.target
[Service]
Type=simple
User=patriot
WorkingDirectory=/opt/patriot_api
ExecStart=/usr/bin/python3 /opt/patriot_api/combine_xml.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Then:
sudo systemctl daemon-reload
sudo systemctl enable patriot-xml-combine.service
sudo systemctl start patriot-xml-combine.service
Logs will be available both in:
/opt/patriot_api/xml_combine.logjournalctl -u patriot-xml-combine.service -f
✅ Summary
- The REST API manages clients, zones, and users, per API key.
- Each API key has fully isolated data and a dedicated XML output folder.
<__id>in XML is generated asclient_id + portfromkeys.json.- Extra XML-only fields (installer info, callouts, client groupings, User 199) are injected per key, not via the API.
- A companion script periodically combines per-client XMLs into one
clients.xml, uploads it to a Windows SMB share, and tracks client import state.
This README describes all the endpoints and the companion XML pipeline required for integrating with Patriot.