| title | Quickstart |
|---|---|
| description | Run the server, build a two checkpoint graph, and produce a violation. |
This walks a full enforcement path on one machine: a server, two checkpoints, one edge between them, and a vehicle that crosses it too fast.
- Node.js 20 or newer
- The server repository
- A terminal that can send HTTP requests, such as
curl
The server listens on port `3000` unless `PORT` says otherwise, and creates
`database.db` next to `server.js` on first run.
By default it binds loopback only, so nothing else on the network can reach
it. That is fine for this walkthrough, where `curl` runs on the same
machine. To let real cameras and drivers connect, set `HOST=0.0.0.0`. See
[Server environment](/reference/rest-api#server-environment).
`npm run build` builds the [operator console](/reference/console) and
installs it as the server's static root. Skip it and the API still works;
only the browser pages are missing.
```bash
curl -X POST http://localhost:3000/create-project \
-H "Content-Type: application/json" \
-d '{"project-name":"Demo"}'
```
```json
{ "project_id": 1, "api_key": "Xk3pQ9..." }
```
Keep the key. This is the project's **operator key**: full read and write on
the project, and what every request below sends in the `x-api-key` header. A
camera in the field should hold a narrower [reporter
key](/reference/rest-api#reporter-keys) instead, but the operator key works
everywhere, which keeps this walkthrough to one credential.
```bash
curl -X POST http://localhost:3000/create-node \
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{"latitude":30.0444,"longitude":31.2357}'
curl -X POST http://localhost:3000/create-node \
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{"latitude":30.0459,"longitude":31.2243}'
```
```bash
curl -X POST http://localhost:3000/create-connection \
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{"from-node-id":1,"to-node-id":2,"distance":1767.5,"speed-limit":60}'
```
Note that `from-node-id` and `to-node-id` are `node_id` values, not
`id_in_project`. A `distance` of `0` is rejected: it must be a positive
number of metres.
Omit `distance` and the server asks the connected distance driver for it. If
no driver is connected, the edge is stored unresolved: its `distance` is
`null` and its `distance_status` is `unknown`, and an unresolved edge
enforces nothing until a distance is filled in. See [Distance
drivers](/concepts/distance-drivers).
A checkpoint reports a plate and the moment it passed. Enforcement begins on the second sighting, when the same plate reaches the other end of an edge.
curl -X POST http://localhost:3000/report-checkpoint \
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{"car-plate":"ABC-123","id-in-project":0,"timestamp":"2026-01-01T10:00:00Z"}'
curl -X POST http://localhost:3000/report-checkpoint \
-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \
-d '{"car-plate":"ABC-123","id-in-project":1,"timestamp":"2026-01-01T10:01:00Z"}'id-in-project here, not node_id. Omit timestamp and the server uses its own
clock.
1767.5 metres at a 60 km/h limit cannot legally be crossed in less than 106 seconds. The run above took 60, so the second call answers:
{
"status": true,
"carSpeed": 106.05,
"legalLimit": 60,
"timestamp": "2026-01-01T10:01:00.000Z",
"nodeId": 2,
"carPlate": "ABC-123"
}curl http://localhost:3000/project/1/violations -H "x-api-key: $API_KEY"