Anviz Local Web API User Guide

This document is for deployment and integration users. It covers Docker installation and configuration, container deployment, HTTPS/JWT configuration, API usage, and troubleshooting. The current service version enables HTTPS and JWT authentication by default.

1. Project Overview

Anviz Local Web API is a Python wrapper around libtc-b_new_sdk.so that exposes HTTPS REST APIs. Clients send command and payload through the unified /api/commands/execute endpoint. The server calls the SDK to communicate with attendance devices and returns JSON.

Core Files

  • app.py: HTTP/HTTPS service, JWT authentication, and command dispatching.
  • sdk_wrapper.py: SDK shared library wrapper, structure parsing, and device command implementation.
  • run_service.sh: Startup script for local or container execution.
  • crosschex-webapi.env: Runtime configuration file.
  • test_api.sh: API test script with automatic login and JWT retrieval.

Default Behavior

  • Listens on 0.0.0.0:5011 by default.
  • Enables HTTPS by default. Self-signed certificates are stored in certs/.
  • Enables JWT by default. command APIs require a Bearer Token.
  • Requests for the same device_id are queued. Different device_id values can be processed in parallel.

2. Docker Environment Setup

If Docker is already installed on the system, skip this chapter. The following steps use Ubuntu 22.04/24.04 as examples. In production, install Docker Engine and the Compose plugin from the official Docker APT repository. Official documentation:

2.1 Install Docker Engine

# 1) Install base dependencies
sudo apt update
sudo apt install -y ca-certificates curl

# 2) Remove old packages that may conflict. Ignore errors if Docker was never installed
sudo apt remove -y docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc

# 3) Add the official Docker GPG key
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# 4) Add the Docker APT repository
sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

# 5) Install Docker Engine and the Compose plugin
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 6) Start Docker and enable it at boot
sudo systemctl enable --now docker
sudo systemctl status docker

2.2 Verify Docker

sudo docker run hello-world

2.3 Allow the Current User to Run Docker

sudo usermod -aG docker $USER
newgrp docker

# Verify that docker commands work without sudo
docker ps
If docker ps still reports insufficient permissions, log out of the current shell or restart the system and try again.

3. Configuration File

crosschex-webapi.env is the service runtime configuration file. run_service.sh loads it automatically.

In production, you must change JWT_SECRET and AUTH_PASSWORD, and you should replace the HTTPS certificate with a production certificate.
SettingDefault / ExampleDescription
HOST0.0.0.0Web API listen address.
PORT5011Web API listen port.
SDK_LIBRARY/app/libtc-b_new_sdk.soCrossChex SDK shared library path. In containers, this path must match the image or volume mount.
HTTPS_ENABLED1Whether HTTPS is enabled. Keep it enabled in production.
SSL_CERT_FILE/app/certs/server.crtHTTPS certificate path.
SSL_KEY_FILE/app/certs/server.keyHTTPS private key path.
AUTH_ENABLED1Whether JWT authentication is enabled. Keep it enabled in production.
JWT_SECRETopenssl rand -hex 32JWT signing secret. Use a strong random string.
JWT_EXPIRES_SECONDS3600Token lifetime in seconds.
AUTH_PASSWORDCustom strong passwordAuthentication password. Change it in production.
WEBHOOK_ENABLED0Set to 1 to enable realtime attendance record forwarding.
WEBHOOK_URLEmptyHTTP/HTTPS endpoint that receives command 100 JSON. Leave empty to disable forwarding.
WEBHOOK_TOKENEmptyOptional Bearer Token. Leave empty to omit the Authorization header.

Generate JWT Secret

openssl rand -hex 32

Generate a Self-Signed Certificate for Testing Only

mkdir -p certs
openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout certs/server.key \
  -out certs/server.crt \
  -days 3650 \
  -subj "/CN=127.0.0.1"
For production deployment, use a certificate issued by a trusted CA. With a self-signed certificate, calls require curl -k or importing the certificate into the client trust store.

4. Docker Deployment

4.1 Recommended Directory Layout

/webapi/
├── sdk/
│   ├── certs/
│   │   ├── server.crt
│   │   └── server.key
│   ├── demo/
│   │   └── index.html
│   ├── app.py
│   ├── crosschex-webapi.env
│   ├── libtc-b_new_sdk.so
│   ├── run_http.sh
│   ├── run_service.sh
│   ├── sdk_wrapper.py
│   └── test_api.sh
├── crosschex-webapi-usage.html
└── crosschex-webapi.tar

4.2 Load Image

docker load -i /webapi/crosschex-webapi.tar

4.3 Start with docker run


# Ubuntu
docker run -it --name crosschex-webapi --network host -v /webapi/sdk:/app crosschex-webapi:latest

# Mac/Windows
docker run -it --name crosschex-webapi -p 5010:5010 -p 5011:5011 -p 8080:8080 -p 5060:5060/udp -v d:/webapi/sdk:/app crosschex-webapi:latest
        

4.4 Check Container Runtime

docker ps --filter name=crosschex-webapi
docker logs -f crosschex-webapi
curl -k https://127.0.0.1:5011/healthz

5. JWT Authentication

5.1 Get Token

curl -k -X POST https://127.0.0.1:5011/api/auth/token \
  -H 'Content-Type: application/json' \
  -d '{"password":"change-this-password"}'

Successful response:

{
  "code": 200,
  "token_type": "Bearer",
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 3600
}

5.2 Call the command API

TOKEN=$(curl -k -sS -X POST https://127.0.0.1:5011/api/auth/token \
  -H 'Content-Type: application/json' \
  -d '{"password":"change-this-password"}' \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])')

curl -k -X POST https://127.0.0.1:5011/api/commands/execute \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"command":23,"payload":{}}'

5.3 Authentication Errors

HTTP StatusScenarioResponse Example
401Missing token, invalid token format, invalid signature, or expired token.{"code":401,"message":"Token expired."}

6. API Usage Specification

6.1 Health Check

The health check does not require JWT.

GET /healthz
curl -k https://127.0.0.1:5011/healthz

6.2 Unified Command Endpoint

POST /api/commands/execute
Content-Type: application/json
Authorization: Bearer <token>

{
  "command": 23,
  "payload": {}
}

All business commands use command to identify the operation and payload to carry command parameters.

6.3 Common Response Structure

{
  "code": 200,
  "command": 23,
  "data": { ... },
  "records": [ ... ],
  "count": 1
}

Not every API returns records and count. When there are no records, some APIs only return data.totalcnt=0.

6.4 Common Business Error Codes

codeHTTP StatusDescription
400Bad RequestInvalid request JSON, missing parameters, or invalid parameter range.
401UnauthorizedAuthentication failed.
404Not FoundDevice not connected, API not found, or no event was received while waiting.
502Bad GatewaySDK call failed immediately.
504Gateway TimeoutTimed out waiting for the device response.

7. Command List

commandNameMain payload parametersDescription
1Connect Devicedevice_id, device_ip, device_portCall the SDK to connect to a device and return device ID, type, version, IP, and dev_idx.
2Read New Recordsdevice_idRead record status first. If NewRecNum=0, return totalcnt=0 immediately.
3Disconnect Devicedevice_idDisconnect a connected device.
4Read All Recordsdevice_idRead record status first. If TotalRecNum=0, return totalcnt=0 immediately.
5Read Record Statusdevice_idReturn employee, fingerprint, password, card, total record, and new record counts.
6Clear Recordsdevice_idDelete or clear device records.
7Read Person Infodevice_idRead record status first. If EmployeeNum=0, return totalcnt=0 immediately.
8Add/Modify Persondevice_id, userid, password, card_id, username, etc.Write basic person information.
9Delete Persondevice_id, userid, operationDelete a person or person-related data.
10Read Device Timedevice_idReturn the current device time.
11Set Device Timedevice_id, year, month, day, hour, minute, secondSet the device time.
12Initialize User Areadevice_idClear user-related areas.
13Initialize Systemdevice_idInitialize the device system. This is a high-risk operation.
14Force Unlockdevice_idTrigger device force unlock.
15Read SNdevice_idRead the device serial number.
16Download Fingerprint Templatedevice_id, userid, templateidRead a user fingerprint template.
17Upload Fingerprint Templatedevice_id, userid, templateid, template, template_lenWrite a user fingerprint template.
18Download Face Image Templatedevice_id, useridRead a face image template.
19Upload Face Image Templatedevice_id, userid, templateWrite a face image template.
20Enroll Fingerprint Onlinedevice_id, userid, templateidTrigger online fingerprint enrollment on the device.
21Enroll Face Onlinedevice_id, useridTrigger online face image enrollment on the device.
22Read Records by User and Timedevice_id, userid, start_date, end_dateRead the matching record count first and return immediately if it is 0.
23List Connected DevicesNoneReturn the list of connected devices in the current service. records is always an array.
24Read Period Settingsdevice_id, timeidtimeid range is 1..32. Returns start1/end1 through start7/end7.
25Set Period Settingsdevice_id, timeid, start1..end7Time format is HH:mm.
26Read Group Settingsdevice_id, teamidteamid must be in the range 2..16. Returns timeid1..timeid4.
27Set Group Settingsdevice_id, teamid, timeid1..timeid4teamid must be in the range 2..16.
28Read Attendance Statusdevice_idReturn fp_len and status1..status8.
29Set Attendance Statusdevice_id, status1..status8The status field length depends on the device type.
30Search DevicesNoneSearch LAN devices over UDP and return IP, MAC, port, version, and related information.

Request and Response JSON Examples

The following examples show successful responses. Actual values may vary by device model, firmware version, and data content. Field structure follows the current API implementation.

command=1 Connect Device

Request JSON

{
  "command": 1,
  "payload": {
    "device_id": 1,
    "device_ip": "192.168.0.144",
    "device_port": 5010
  }
}

Response JSON

{
  "code": 200,
  "command": 1,
  "data": {
    "device_id": 1,
    "device_type": "FDEEP3M",
    "device_typeflag": 45154820,
    "device_ip": "192.168.0.144:5010",
    "device_version": "03.74.D6",
    "device_idx": 1
  }
}
command=2 Read New Records

Request JSON

{
  "command": 2,
  "payload": {
    "device_id": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 2,
  "data": {
    "device_id": 1,
    "totalcnt": 1
  },
  "records": [
    {
      "userid": 1,
      "checktime": "2026-07-27 09:30:00",
      "backid": 16,
      "rectype": 128, //highest bit 1 means door opened, for example 128: opened+IN, 129: opened+OUT, 0: IN, 1: OUT
      "curidx": 1
    }
  ],
  "count": 1
}
command=3 Disconnect Device

Request JSON

{
  "command": 3,
  "payload": {
    "device_id": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 3,
  "data": {
    "device_id": 1,
    "device_type": "FDEEP3M",
    "device_typeflag": 45154820,
    "device_ip": "192.168.0.144:5010",
    "device_version": "03.74.D6",
    "device_idx": 1,
    "live": 0
  }
}
command=4 Read All Records

Request JSON

{
  "command": 4,
  "payload": {
    "device_id": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 4,
  "data": {
    "device_id": 1,
    "totalcnt": 1
  },
  "records": [
    {
      "userid": 1,
      "checktime": "2026-07-27 09:30:00",
      "backid": 16,
      "rectype": 128,
      "curidx": 1
    }
  ],
  "count": 1
}
command=5 Read Record Status

Request JSON

{
  "command": 5,
  "payload": {
    "device_id": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 5,
  "data": {
    "device_id": 1,
    "employee_count": 10,
    "template_count": 8,
    "password_count": 6,
    "card_count": 9,
    "total_record_count": 100,
    "new_record_count": 3
  }
}
command=6 Clear Records

Request JSON

{
  "command": 6,
  "payload": {
    "device_id": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 6,
  "data": {
    "device_id": 1,
    "result": 0, //0: success, non-zero: failure
    "deleted_count": 100
  }
}
command=7 Read Person Info

Request JSON

{
  "command": 7,
  "payload": {
    "device_id": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 7,
  "data": {
    "device_id": 1,
    "totalcnt": 1
  },
  "records": [
    {
      "curidx": 1,
      "userid": 1,
      "password": "1",
      "card_id": 12732605,
      "username": "Jack",
      "deptid": 0,
      "groupid": 1,
      "mode": 0,
      "fp_status": 1024, //template flags, bit0: fingerprint 1 ... bit9: fingerprint 10, bit10: face, for example 1024: face, 3: fingerprint 1 + fingerprint 2
      "special": 64, //192/208: administrator
      "start_date": "2026-07-01",
      "end_date": "2031-07-06",
      "scheduling_id": 0,
      "start_scheduling_time": "00:00",
      "end_scheduling_time": "00:00"
    }
  ],
  "count": 1
}
command=8 Add/Modify Person

Request JSON

{
  "command": 8,
  "payload": {
    "device_id": 1,
    "userid": 1,
    "password": "1",
    "card_id": 12732605,
    "username": "Jack",
    "deptid": 0,
    "groupid": 1,
    "mode": 0,
    "special": 64,
    "start_date": "2026-07-01",
    "end_date": "2031-07-06",
    "scheduling_id": 0,
    "start_scheduling_time": "00:00",
    "end_scheduling_time": "00:00"
  }
}

Response JSON

{
  "code": 200,
  "command": 8,
  "data": {
    "device_id": 1,
    "result": 0, //0: success, non-zero: failure
    "userid": 1
  }
}
command=9 Delete Person

Request JSON

{
  "command": 9,
  "payload": {
    "device_id": 1,
    "userid": 1,
    "operation": 255 //255: delete person data and templates, 3: delete template, 4: delete password, 8: delete card
  }
}

Response JSON

{
  "code": 200,
  "command": 9,
  "data": {
    "device_id": 1,
    "result": 0, //0: success, non-zero: failure
    "userid": 1
  }
}
command=10 Read Device Time

Request JSON

{
  "command": 10,
  "payload": {
    "device_id": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 10,
  "data": {
    "device_id": 1,
    "result": 0, //0: success, non-zero: failure
    "year": 2026,
    "month": 7,
    "day": 27,
    "hour": 10,
    "minute": 30,
    "second": 20,
    "datetime": "2026-07-27 10:30:20"
  }
}
command=11 Set Device Time

Request JSON

{
  "command": 11,
  "payload": {
    "device_id": 1,
    "year": 2026,
    "month": 7,
    "day": 27,
    "hour": 10,
    "minute": 30,
    "second": 20
  }
}

Response JSON

{
  "code": 200,
  "command": 11,
  "data": {
    "device_id": 1,
    "result": 0 //0: success, non-zero: failure
  }
}
command=12 Initialize User Area

Request JSON

{
  "command": 12,
  "payload": {
    "device_id": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 12,
  "data": {
    "device_id": 1,
    "result": 0 //0: success, non-zero: failure
  }
}
command=13 Initialize System

Request JSON

{
  "command": 13,
  "payload": {
    "device_id": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 13,
  "data": {
    "device_id": 1,
    "result": 0 //0: success, non-zero: failure
  }
}
command=14 Force Unlock

Request JSON

{
  "command": 14,
  "payload": {
    "device_id": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 14,
  "data": {
    "device_id": 1,
    "result": 0 //0: success, non-zero: failure
  }
}
command=15 Read SN

Request JSON

{
  "command": 15,
  "payload": {
    "device_id": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 15,
  "data": {
    "device_id": 1,
    "result": 0, //0: success, non-zero: failure
    "sn": "1750100022370065"
  }
}
command=16 Download Fingerprint Template

Request JSON

{
  "command": 16,
  "payload": {
    "device_id": 1,
    "userid": 1,
    "templateid": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 16,
  "data": {
    "device_id": 1,
    "result": 0, //0: success, non-zero: failure
    "userid": 1,
    "templateid": 1,
    "template": "AQIDBA==" //base64
  }
}
command=17 Upload Fingerprint Template

Request JSON

{
  "command": 17,
  "payload": {
    "device_id": 1,
    "userid": 1,
    "templateid": 1,
    "template": "AQIDBA==" //base64
  }
}

Response JSON

{
  "code": 200,
  "command": 17,
  "data": {
    "device_id": 1,
    "result": 0, //0: success, non-zero: failure
    "userid": 1,
    "templateid": 1
  }
}
command=18 Download Face Image Template

Request JSON

{
  "command": 18,
  "payload": {
    "device_id": 1,
    "userid": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 18,
  "data": {
    "device_id": 1,
    "result": 0, //0: success, non-zero: failure
    "userid": 1,
    "template": "AQIDBA==" //base64
  }
}
command=19 Upload Face Image Template

Request JSON

{
  "command": 19,
  "payload": {
    "device_id": 1,
    "userid": 1,
    "template": "AQIDBA==" //base64
  }
}

Response JSON

{
  "code": 200,
  "command": 19,
  "data": {
    "device_id": 1,
    "result": 0, //0: success, non-zero: failure
    "userid": 1
  }
}
command=20 Enroll Fingerprint Online

Request JSON

{
  "command": 20,
  "payload": {
    "device_id": 1,
    "userid": 123,
    "templateid": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 20,
  "data": {
    "device_id": 1,
    "result": 0, //0: success, non-zero: failure
    "userid": 123,
    "templateid": 1,
    "template": "AQIDBA==" //base64
  }
}
command=21 Enroll Face Online

Request JSON

{
  "command": 21,
  "payload": {
    "device_id": 1,
    "userid": 123
  }
}

Response JSON

{
  "code": 200,
  "command": 21,
  "data": {
    "device_id": 1,
    "result": 0, //0: success, non-zero: failure
    "userid": 123,
    "template": "AQIDBA==" //base64
  }
}
command=22 Read Records by User and Time

Request JSON

{
  "command": 22,
  "payload": {
    "device_id": 1,
    "userid": 1,
    "start_date": "2026-07-01",
    "end_date": "2026-07-27"
  }
}

Response JSON

{
  "code": 200,
  "command": 22,
  "data": {
    "device_id": 1,
    "totalcnt": 1
  },
  "records": [
    {
      "userid": 1,
      "checktime": "2026-07-27 09:30:00",
      "backid": 16,
      "rectype": 128,
      "curidx": 1
    }
  ],
  "count": 1
}
command=23 List Connected Devices

Request JSON

{
  "command": 23,
  "payload": {}
}

Response JSON

{
  "code": 200,
  "command": 23,
  "data": {
    "totalcnt": 1
  },
  "records": [
    {
      "device_id": 1,
      "device_type": "FDEEP3M",
      "device_typeflag": 45154820,
      "device_ip": "192.168.0.144:5010",
      "device_version": "03.74.D6",
      "device_idx": 1
    }
  ],
  "count": 1
}
command=24 Read Period Settings

Request JSON

{
  "command": 24,
  "payload": {
    "device_id": 1,
    "timeid": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 24,
  "data": {
    "device_id": 1,
    "result": 0,
    "timeid": 1, //1-24: normal periods, 25-28: normally closed periods, 29-32: normally open periods
    "start1": "07:00", //Monday start time
    "end1": "19:00", //Monday end time
    "start2": "08:00",
    "end2": "20:00",
    "start3": "00:00",
    "end3": "00:00",
    "start4": "00:00",
    "end4": "00:00",
    "start5": "00:00",
    "end5": "00:00",
    "start6": "00:00",
    "end6": "00:00",
    "start7": "00:00", //Sunday start time
    "end7": "00:00" //Sunday end time
  }
}
command=25 Set Period Settings

Request JSON

{
  "command": 25,
  "payload": {
    "device_id": 1,
    "timeid": 1, //1-24: normal periods, 25-28: normally closed periods, 29-32: normally open periods
    "start1": "07:00", //Monday start time
    "end1": "19:00", //Monday end time
    "start2": "08:00",
    "end2": "20:00",
    "start3": "00:00",
    "end3": "00:00",
    "start4": "00:00",
    "end4": "00:00",
    "start5": "00:00",
    "end5": "00:00",
    "start6": "00:00",
    "end6": "00:00",
    "start7": "00:00", //Sunday start time
    "end7": "00:00" //Sunday end time
  }
}

Response JSON

{
  "code": 200,
  "command": 25,
  "data": {
    "device_id": 1,
    "result": 0,
    "timeid": 1
  }
}
command=26 Read Group Settings

Request JSON

{
  "command": 26,
  "payload": {
    "device_id": 1,
    "teamid": 2 //2-16
  }
}

Response JSON

{
  "code": 200,
  "command": 26,
  "data": {
    "device_id": 1,
    "result": 0,
    "teamid": 2,
    "timeid1": 1,
    "timeid2": 2,
    "timeid3": 0,
    "timeid4": 0
  }
}
command=27 Set Group Settings

Request JSON

{
  "command": 27,
  "payload": {
    "device_id": 1,
    "teamid": 2, //2-16
    "timeid1": 1, //0-24
    "timeid2": 2, //0-24
    "timeid3": 0, //0-24
    "timeid4": 0 //0-24
  }
}

Response JSON

{
  "code": 200,
  "command": 27,
  "data": {
    "device_id": 1,
    "result": 0,
    "teamid": 2
  }
}
command=28 Read Attendance Status

Request JSON

{
  "command": 28,
  "payload": {
    "device_id": 1
  }
}

Response JSON

{
  "code": 200,
  "command": 28,
  "data": {
    "device_id": 1,
    "result": 0,
    "fp_len": 160,
    "status1": "IN",
    "status2": "OUT",
    "status3": "BREAK",
    "status4": "RETURN",
    "status5": "",
    "status6": "",
    "status7": "",
    "status8": ""
  }
}
command=29 Set Attendance Status

Request JSON

{
  "command": 29,
  "payload": {
    "device_id": 1,
    "status1": "IN",
    "status2": "OUT",
    "status3": "BREAK",
    "status4": "RETURN",
    "status5": "",
    "status6": "",
    "status7": "",
    "status8": ""
  }
}

Response JSON

{
  "code": 200,
  "command": 29,
  "data": {
    "device_id": 1,
    "result": 0
  }
}
command=30 Search Devices

Request JSON

{
  "command": 30,
  "payload": {}
}

Response JSON

{
  "code": 200,
  "command": 30,
  "data": {
    "totalcnt": 1
  },
  "records": [
    {
      "devtype": "FDEEP3M",
      "devsn": "1750100022370065",
      "devid": 1,
      "ipaddr": "192.168.0.144",
      "ipmask": "255.255.255.0",
      "gwaddr": "192.168.0.1",
      "macaddr": "00:22:ca:8a:fd:1f",
      "servaddr": "192.168.0.159",
      "port": 5010,
      "netmode": 1,
      "version": "03.74.D6"
    }
  ],
  "count": 1
}

8. Using test_api.sh

test_api.sh accesses https://127.0.0.1:5011 by default and uses -k to skip self-signed certificate verification. Except for health, business commands automatically log in and include JWT.

# Health check, no token required
./test_api.sh health

# Test the login endpoint only
./test_api.sh login

# List connected devices; the script logs in automatically
./test_api.sh connected

# Connect device
./test_api.sh connect 192.168.0.144 5010 1

# Search devices
./test_api.sh searchdev

Common Environment Variables

BASE_URL=https://192.168.0.159:5011 \
AUTH_PASSWORD='your-password' \
DEVICE_ID=1 \
DEVICE_IP=192.168.0.144 \
./test_api.sh connect

Reuse an Existing Token

API_TOKEN='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' ./test_api.sh connected

9. Using the Web API Demo

9.1 Start the demo HTTP server

docker exec -it crosschex-webapi /app/run_http.sh

9.2 Open the Web API Demo page

When opening the demo page for the first time, click Open healthz first to remove browser network blocking. Then enter the correct API Base URL and password. The default password is CrossChex, but use the actual configured AUTH_PASSWORD.

10. Troubleshooting

SymptomCauseResolution
AUTH_ENABLED=1 but JWT_SECRET is emptycrosschex-webapi.env was not loaded or JWT_SECRET is not configured.Confirm that run_service.sh loads the env file, or set ENV_FILE. Configure a strong random JWT_SECRET.
Missing Authorization Bearer tokenThe command API was called without JWT.Call /api/auth/token first, then include Authorization: Bearer <token>.
Token expiredJWT exceeded JWT_EXPIRES_SECONDS.Log in again to get a new token.
SSL cert file not foundCertificate path is wrong or certs is not mounted in the container.Check SSL_CERT_FILE, SSL_KEY_FILE, and volume mounts.
Timed out while waiting for device responseDevice does not respond, network is unreachable, port is unreachable, or the event does not match.Check device IP, port 5010, container host network, device connection state, and SDK logs.
UDP search cannot find devicesContainer network isolation restricts UDP broadcast.Use --network host or network_mode: host.
HTTPS curl certificate errorA self-signed certificate is used.Use -k during testing. Use a trusted certificate in production.

View Logs

# Docker run
docker logs -f crosschex-webapi

# With systemd deployment
journalctl -u crosschex-webapi -f