Skip to main content

Events

Every event the realtime connection sends, with its payload. Connect first; see Connecting.

Before you read the payloads​

  • Payloads are JSON strings. Parse them first. The shapes below are the parsed objects.
  • Keys are snake_case, dates are ISO-8601 strings, and many flags are 0 or 1.
  • A key whose value is missing may be left out rather than sent as null. The interfaces mark those keys with ?.
  • Who receives what: every event below except authorized goes to the lot it concerns and to the account that owns the lot. With an API token you get the events for your whole account; with a user token, those for the lots the user is assigned to. Something that isn't in a lot (a product with no lot, for example) sends nothing.
EventSent when
authorizedYour token is accepted
lot_changeA command succeeded, or a Gateway reported a product's state
gateway_errorA command failed
gateway_statusA Gateway went online or offline
plate_reading_createAn LPR camera read a plate
plate_reading_updateAn access decision was made for a reading
session_createA parking session started
session_updateA parking session changed or ended
lpr_camera_statusAn LPR camera reported its status
lot_detection_changeA lot's occupied count changed
zone_changeA zone's occupied count changed
parking_place_changeA parking place became free or occupied
parking_gateway_state_changeA parking-detection gateway went online or offline
lpr_eventLegacy: a lot entry was recorded from a plate reading

Connection​

authorized​

Sent once, only to your connection, when the server accepts your token. The payload is an empty string. See Authentication.

Products and Gateways​

lot_change​

The state of one product. Despite the name, it's about a product, not a lot. It's sent in two cases:

The product carries one block that matches its type: barrier, gate, chain, bollard or brain.

lot_change
interface LotChange {
lot_id: number | null;
event_id?: string; // 8 hex characters; only for command results
data: {
id: number;
device_id: string;
name: string;
lot_id: number | null;
product_type_id: number;
latitude: number | null;
longitude: number | null;
status: 'online' | 'offline';
battery_status: number | null;
rssi: number | null;
product_error_id?: number; // only when the product reports an error
product_error?: ProductError; // only when the product reports an error
deleted_at: null;
created_at: string;
updated_at: string;
barrier?: BarrierBlock;
gate?: BrainBlock;
chain?: BrainBlock;
bollard?: BrainBlock;
brain?: BrainBlock;
lot: {
id: number;
lot_cluster_id: number; // same as id
account_id: number;
name: string;
address: string;
latitude: number | null;
longitude: number | null;
timezone: string; // IANA name, e.g. "Europe/Zagreb"
created_at: string;
updated_at: string;
} | null;
};
}

interface BarrierBlock {
id: number;
product_id: number;
state: 'opened' | 'closed';
is_auto_close: 0 | 1; // REST calls this is_autoclose
is_car_on_top: 0 | 1;
auto_close_time: number; // seconds; REST calls this autoclose_time
create_at: string; // sic: REST calls this created_at
updated_at: string;
}

interface BrainBlock {
id: number;
product_id: number;
state: 'opened' | 'closed' | 'error' | 'unknown' | 'reserved' | 'disabled';
permanent_position: 'disabled' | 'stay_open' | 'stay_close';
deleted_at: string | null;
created_at: string;
updated_at: string;
}

interface ProductError {
id: number;
error_code: string;
jammed: 0 | 1;
sensor_dirty: 0 | 1;
pin_broken: 0 | 1;
rtcc_invalid: 0 | 1;
battery_empty: 0 | 1;
master_key_not_set: 0 | 1;
created_at: string;
updated_at: string;
}

For a barrier, opened means the arm is down (cars can pass over it) and closed means the arm is up. See Products and operations.

:::warning The barrier block's keys differ from the REST API In lot_change, the barrier block uses is_auto_close, auto_close_time and create_at. The REST API's product responses use is_autoclose, autoclose_time and created_at. Read the realtime keys as they are shown here. :::

lot_change: a command result for a barrier
{
"lot_id": 7,
"event_id": "a1b2c3d4",
"data": {
"id": 123,
"device_id": "B1234567",
"name": "Entrance barrier",
"lot_id": 7,
"product_type_id": 2,
"latitude": 45.815,
"longitude": 15.982,
"status": "online",
"battery_status": 80,
"rssi": -70,
"deleted_at": null,
"created_at": "2026-01-15T09:30:00.000Z",
"updated_at": "2026-03-02T08:12:44.000Z",
"barrier": {
"id": 41,
"product_id": 123,
"state": "opened",
"is_auto_close": 1,
"is_car_on_top": 0,
"auto_close_time": 45,
"create_at": "2026-01-15T09:30:00.000Z",
"updated_at": "2026-03-02T08:12:44.000Z"
},
"lot": {
"id": 7,
"lot_cluster_id": 7,
"account_id": 3,
"name": "Main garage",
"address": "Example street 1",
"latitude": 45.815,
"longitude": 15.982,
"timezone": "Europe/Zagreb",
"created_at": "2026-01-10T10:00:00.000Z",
"updated_at": "2026-01-10T10:00:00.000Z"
}
}
}

gateway_error​

A command failed. event_id matches the one POST /v2/products/{id}/state or POST /v2/products/{id}/settings returned. See Asynchronous operations.

gateway_error
interface GatewayError {
type: number | 'Timeout'; // see the table below
event_id: string; // 8 hex characters
data: {
id: number; // product id
name: string; // product name
};
message?: string; // absent when type is "Timeout"
}
gateway_error
{
"type": 6,
"event_id": "a1b2c3d4",
"data": { "id": 123, "name": "Entrance barrier" },
"message": "Car on top"
}

When no Gateway got an answer from the product in time, type is the string "Timeout" and there's no message. Otherwise type is one of these numbers. They are not the HTTP error codes, even where the numbers overlap.

typemessage
0Auth error
1Invalid guest key
2Device disconnected
3Bluetooth error
4Device not found
5Device jammed
6Car on top
7Sensor dirty
8Device not initialized
9Rtcc not set
10Device busy
11Unknown error

gateway_status​

A Gateway connected to or disconnected from Parklio. Only Gateways assigned to a lot send it. The payload identifies the Gateway by its uuid, not its numeric id.

gateway_status
interface GatewayStatus {
uuid: string;
status: 'online' | 'offline';
}
gateway_status
{ "uuid": "xgobgwEG7rmZ", "status": "online" }

License plates and sessions​

plate_reading_create​

An LPR camera read a license plate. When it's sent, the access decision hasn't been made yet: access_decision, access_decision_reason and session_id are null. Plate readings created by POST /v2/sessions don't send it.

plate_reading_create and plate_reading_update
interface PlateReading {
id: number;
plate: string;
confidence: number | null;
image_url: string | null; // temporary link to the full image
cropped_image_url: string | null; // temporary link to the plate crop
direction: 'in' | 'out' | 'undefined';
type: 'bus' | 'car' | 'motorcycle' | 'truck' | 'van' | null;
make: string | null;
color: string | null;
model: string | null;
country_code: string | null;
session_id: number | null;
access_decision: 'denied' | 'triggered' | null;
access_decision_reason:
| 'debounce'
| 'direction_mismatch'
| 'max_capacity'
| 'no_active_session'
| 'no_tariff'
| 'session_exit'
| 'tariff'
| 'unknown_direction'
| 'whitelist'
| null;
lpr_camera_id: number;
zone_id: number;
plate_side: 'front' | 'rear';
lpr_camera_direction: 'in' | 'out' | 'in/out'; // the camera's position at read time
created_at: string;
updated_at: string;
}
plate_reading_create
{
"id": 9001,
"plate": "ZG1234AB",
"confidence": 0.92,
"image_url": null,
"cropped_image_url": null,
"direction": "in",
"type": "car",
"make": null,
"color": null,
"model": null,
"country_code": "HR",
"session_id": null,
"access_decision": null,
"access_decision_reason": null,
"lpr_camera_id": 15,
"zone_id": 9,
"plate_side": "front",
"lpr_camera_direction": "in",
"created_at": "2026-03-02T08:12:40.000Z",
"updated_at": "2026-03-02T08:12:40.000Z"
}

direction is the string 'undefined' when the camera couldn't tell; such readings get no access decision.

plate_reading_update​

Same shape as plate_reading_create. Sent when:

  • an access decision is recorded: triggered (reason whitelist, tariff or session_exit) or denied (any other reason, including debounce for a repeat read of the same plate);
  • the camera refines a reading after the fact (new plate, confidence or direction).

session_id is usually still null here: the decision is recorded before the reading is linked to a session. To follow the session, listen for session_create and session_update. Editing a reading through PATCH /v2/plate-readings/{id} doesn't send this event.

session_create​

A parking session started: either you or a user created it through POST /v2/sessions, or an LPR camera let a vehicle in (by whitelist or tariff).

session_create and session_update
interface Session {
id: number;
public_id: string; // 10 characters, A-Z and 0-9
ended_at?: string | null; // left out while the session is new
duration: number; // seconds
discount_time?: number | null; // minutes; -1 means unlimited
amount_paid: number;
possibly_exited?: boolean | null;
exit_allowed: boolean; // true while the session hasn't ended
entry_license_plate: {
id: number;
plate: string;
image_url: string | null;
cropped_image_url: string | null;
} | null;
created_at: string;
entry: { id: number; type: SessionSourceType };
exit?: { id: number; type: SessionSourceType } | null; // left out or null while open
zone_id: number;
}

// entry.id / exit.id is the id of that source: the camera, the account or the user
type SessionSourceType = 'lpr_camera' | 'account' | 'attendant' | 'terminal' | 'product' | 'payment';

entry.type is account when an API token created the session, attendant when a user did, and lpr_camera when a camera let the vehicle in.

session_create
{
"id": 501,
"public_id": "K7Q2M9X4TB",
"duration": 0,
"amount_paid": 0,
"exit_allowed": true,
"entry_license_plate": {
"id": 9001,
"plate": "ZG1234AB",
"image_url": null,
"cropped_image_url": null
},
"created_at": "2026-03-02T08:12:41.000Z",
"entry": { "id": 15, "type": "lpr_camera" },
"zone_id": 9
}

session_update​

Same shape as session_create. Sent when:

  • a session is changed through PATCH /v2/sessions/{id} (discount time or entry plate);
  • a session is closed through POST /v2/sessions/{id}/close: ended_at and exit are set, with exit.type account or attendant;
  • an LPR camera matches a vehicle leaving to an open session: ended_at is set, possibly_exited is true and exit.type is lpr_camera.

lpr_camera_status​

An LPR camera reported its status. It's sent for every camera in each report, even when the status didn't change, so keep the last value per camera. Not every camera model sends it.

lpr_camera_status
interface LprCameraStatus {
id: number;
device_id: string;
status: 'online' | 'offline';
}
lpr_camera_status
{ "id": 15, "device_id": "L123456789", "status": "online" }

Occupancy​

A parking-detection sensor that changes state sends three events, in this order: lot_detection_change, zone_change, then parking_place_change. Occupied counts also change when LPR sessions start and end, and those changes don't send any event; re-read the lot or zone over REST if you need exact counts.

lot_detection_change​

lot_detection_change
interface LotDetectionChange {
lots: [
{
id: number; // lot id
detection_status: 'online';
occupied_count: number;
},
]; // always one element
}
lot_detection_change
{ "lots": [{ "id": 7, "detection_status": "online", "occupied_count": 42 }] }

zone_change​

zone_change
interface ZoneChange {
zones: [
{
id: number;
account_id: number;
lot_id: number;
name: string;
description: string | null;
spot_count: number;
occupied_count: number;
deleted_at: string | null;
created_at: string;
updated_at: string;
},
]; // always one element
}
zone_change
{
"zones": [
{
"id": 9,
"account_id": 3,
"lot_id": 7,
"name": "Level -1",
"description": null,
"spot_count": 120,
"occupied_count": 42,
"deleted_at": null,
"created_at": "2026-01-10T10:00:00.000Z",
"updated_at": "2026-03-02T08:12:40.000Z"
}
]
}

parking_place_change​

parking_place_change
interface ParkingPlaceChange {
places: [
{
id: number;
lot_id: number;
zone_id: number;
name: string | null;
occupancy_status: 'free' | 'occupied';
},
]; // always one element
}
parking_place_change
{
"places": [
{ "id": 301, "lot_id": 7, "zone_id": 9, "name": "Spot 12", "occupancy_status": "occupied" }
]
}

parking_gateway_state_change​

A parking-detection gateway (not a product Gateway) went online or offline. Not every parking-detection gateway model sends it.

parking_gateway_state_change
interface ParkingGatewayStateChange {
gateways: [{ id: number; status: 'online' | 'offline' }]; // always one element
}
parking_gateway_state_change
{ "gateways": [{ "id": 4, "status": "offline" }] }

Legacy​

lpr_event​

:::warning Legacy Use plate_reading_create and plate_reading_update instead. lpr_event is still sent, but lot entries are deprecated. :::

Sent when a plate reading with a known direction, not debounced, is recorded as a lot entry. An out entry that matches an earlier in has related_lot_entry and duration set.

lpr_event
interface LprEvent {
lot_id: number;
data: LotEntry & {
lpr_camera: {
id: number;
device_id: string;
device_serial: string;
name: string;
account_id: number;
zone_id: number;
status: 'online' | 'offline' | 'auth' | 'sleep' | 'reset';
lpr_position: 'in' | 'out' | 'in/out';
connected_device_type: 'gate' | 'chain' | 'bollard' | 'other' | 'standalone';
mac: string;
deleted_at: string | null;
created_at: string | null;
updated_at: string | null;
};
related_lot_entry:
| (LotEntry & { lpr_camera: { id: number; device_id: string; name: string } })
| null;
};
}

interface LotEntry {
id: number;
scan_time: string;
lpr_camera_id: number;
lot_id: number;
related_lot_entry_id: number | null;
duration: number | null; // seconds; out entries only
is_allowed: 0 | 1;
direction: 'in' | 'out';
license_plate: string;
license_plate_image_url: string | null; // a storage key, not a usable URL
full_image_url: string | null; // a storage key, not a usable URL
make: string; // "" when unknown
color: string; // "" when unknown
model: string; // "" when unknown
color_depth: number | null;
created_at: string;
updated_at: string;
}
lpr_event
{
"lot_id": 7,
"data": {
"id": 320,
"scan_time": "2026-03-02T08:12:40.000Z",
"lpr_camera_id": 15,
"lot_id": 7,
"related_lot_entry_id": null,
"duration": null,
"is_allowed": 1,
"direction": "in",
"license_plate": "ZG1234AB",
"license_plate_image_url": null,
"full_image_url": null,
"make": "",
"color": "",
"model": "",
"color_depth": 1,
"created_at": "2026-03-02T08:12:40.000Z",
"updated_at": "2026-03-02T08:12:40.000Z",
"lpr_camera": {
"id": 15,
"device_id": "L123456789",
"device_serial": "123456789",
"name": "Entrance camera",
"account_id": 3,
"zone_id": 9,
"status": "online",
"lpr_position": "in",
"connected_device_type": "standalone",
"mac": "AA:BB:CC:DD:EE:FF",
"deleted_at": null,
"created_at": "2026-01-10T10:00:00.000Z",
"updated_at": "2026-01-10T10:00:00.000Z"
},
"related_lot_entry": null
}
}

Next​