Go to file
anders 7832be5fac Change in comments. 2025-12-04 20:58:19 +01:00
Patriot API.postman_collection.json Updated postman example data 2025-12-03 16:14:58 +01:00
README.md v.0.9 Beta 2025-12-03 09:51:31 +01:00
keys.json v.0.9 Beta 2025-12-03 09:12:19 +01:00
main.py Change in comments. 2025-12-04 20:58:19 +01:00
run_server.py v.0.9 Beta 2025-12-03 09:12:19 +01:00
test_change.py v.0.9 Beta 2025-12-03 09:12:19 +01:00
xml_combine_for_import.py v.0.9 Beta 2025-12-03 09:51:31 +01:00

README.md

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 ID
  • BASE11 = port value from keys.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_id must 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.log
  • journalctl -u patriot-api.service -f

🔁 Companion Service: XML Combiner & SMB Uploader

In addition to the API, there is a separate Python script that:

  1. Scans all per-client XML files from the API:
    /opt/patriot_api/out/clients/**/<client_id>.xml
    
  2. Combines them into a single clients.xml file under:
    /opt/patriot_api/ready_for_import/clients.xml
    
  3. Uploads this combined clients.xml to a Windows SMB share.
  4. Tracks import state per client in client_state.json.
  5. 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:

  1. Optional pre-upload check:

    • Attempts to download existing clients.xml and clients_Import_Results.txt from the share.
    • Parses all <__id> values from the remote clients.xml.
    • For each __id, checks whether clients_Import_Results.txt contains a line with that id and the phrase "Completed processing client".
    • Any ids missing a success line are logged into import_errors.log with timestamp.
  2. Rotation of existing clients.xml:

    • If clients.xml already exists on the share, it is renamed with a timestamp, e.g.:
      clients_20250101_120000.xml
  3. Upload new clients.xml:

    • The new combined XML is uploaded as clients.xml.
  4. 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.

Combining Logic

  • The script looks for all *.xml files under XML_ROOT_PATH (recursively).
  • It parses each file and extracts <Row> elements with a <__id> child.
  • Rows are keyed by __id; if the same __id appears 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_RUN can be set (default 300).

Scheduling

The script is written to run in an infinite loop:

if __name__ == "__main__":
    main()

Where main() essentially:

  1. Calls sleep_until_next_run() → sleeps until ~hh:59 before the next hour.
  2. Runs combine_xml_once().
  3. Sleeps 1 second as a safety fallback.
  4. 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.log
  • journalctl -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 as client_id + port from keys.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.