All documentation pages ↓

On this page

The API

LiteBlox has a JSON API under /api/v1. Scripts, monitoring and other tools use it to read nodes, zones, records and DHCP leases, and to change records and DHCP reservations. Every endpoint is listed, with its fields, on the API page (/api/docs), which any logged-in user can open. Calls go through the same checks as the pages, so a publish is still reviewed against named-checkzone and the nodes, and a DHCP change still goes to the standby first and rolls back if Kea refuses it.

Tokens

Every call needs an API token, sent as a header:

Authorization: Bearer lbx_…

Trying it

On the API page, Authorize with a token, then Try it out on any endpoint. From a shell ($LB is LiteBlox's address, $TOKEN the token):

curl -s -H "Authorization: Bearer $TOKEN" $LB/api/v1/me
curl -s -H "Authorization: Bearer $TOKEN" $LB/api/v1/zones
curl -s -H "Authorization: Bearer $TOKEN" $LB/api/v1/zones/example.com/records

Records

A record's id comes from its content (owner, type and data), so it stays the same while the record does, whatever else changes in the zone. Changing a record gives it a new id, which the answer includes.

A static zone changes in its draft, as in the grid. Look at the draft, then publish it:

# add a record (and its PTR, when the zone's auto-PTR is on)
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
     -X POST $LB/api/v1/zones/example.com/records \
     -d '{"name": "web", "type": "A", "data": "192.168.0.20"}'

# what publishing would change: added/removed/changed records, the diff, named-checkzone, linked PTR drafts
curl -s -H "Authorization: Bearer $TOKEN" $LB/api/v1/zones/example.com/draft

# publish it (with the PTR drafts linked to it); the answer is a job to poll
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
     -X POST $LB/api/v1/zones/example.com/publish -d '{"message": "Add web"}'
curl -s -H "Authorization: Bearer $TOKEN" $LB/api/v1/jobs/42

A dynamic zone changes at once, as one guarded update to its primary, as a grid edit does. It has no draft and nothing to publish.

Many changes at once

POST /zones/{zone}/records/batch takes a list of ops and makes them as one change: all of them, or none.

curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
     -X POST $LB/api/v1/zones/example.com/records/batch -d '{
  "ops": [
    {"op": "add", "name": "web1", "type": "A", "data": "192.168.0.21", "comment": "rack 2"},
    {"op": "add", "name": "web2", "type": "A", "data": "192.168.0.22", "ttl": "10m"},
    {"op": "change", "id": "<an id from GET …/records>", "ttl": 600},
    {"op": "delete", "id": "<another id>"}
  ]}'

Changes made meanwhile

Jobs

Publishing and DHCP changes run as jobs, as they do from the pages. The call answers 202 with {"job": N} (and a Location header). Poll GET /api/v1/jobs/N until status is ok or failed; its log is what the job page shows. GET /api/v1/jobs lists recent ones.

DHCP

curl -s -H "Authorization: Bearer $TOKEN" $LB/api/v1/dhcp            # servers, the failover pair, settings
curl -s -H "Authorization: Bearer $TOKEN" $LB/api/v1/dhcp/subnets    # with their reservations
curl -s -H "Authorization: Bearer $TOKEN" $LB/api/v1/dhcp/leases     # live from Kea (?subnet=ID)

# reserve an address (a job: every DHCP node, the standby first)
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
     -X POST $LB/api/v1/dhcp/subnets/2/reservations \
     -d '{"hw_address": "aa:bb:cc:dd:ee:ff", "ip_address": "192.168.0.60", "hostname": "printer"}'

# delete a lease now (its DNS names go too; the client asks again at its next renewal)
curl -s -H "Authorization: Bearer $TOKEN" -X DELETE $LB/api/v1/dhcp/leases/192.168.0.123

GET /api/v1/search?q=… is the Search page as JSON: records, reservations, leases, zones, subnets and devices, each with its count and up to limit (default 200) items. A query for one IPv4 address also gets address: its names, PTR, reservation, lease, subnet, and the checks that didn't add up. A dynamic zone searched from its history because its primary didn't answer gives its records as_of; what a short query didn't read live is in skipped. A read-only token is enough.

curl -s -H "Authorization: Bearer $TOKEN" "$LB/api/v1/search?q=192.168.0.50" | jq .address

IPAM

Everything on the IPAM pages, under /api/v1/ipam: networks (GET, POST, PATCH, DELETE) and their ranges, a network's addresses (one /24 per page, used_only to leave the free ones out), what the nodes seen (and DELETE /ipam/seen/{ip} to forget it), flapping addresses and their ack, and devices (PUT a label and comment).

Next free finds addresses nothing uses and holds them for a few minutes, then assign puts names on them:

# the next 3 free addresses in a row, held for 10 minutes
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
     -X POST $LB/api/v1/ipam/next-free -d '{"network": "192.168.10.0/24", "count": 3}'
# A records (and PTRs) for them, published; a group to remove them by later
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -X POST $LB/api/v1/ipam/assign \
     -d '{"ips": ["192.168.10.81", "192.168.10.82", "192.168.10.83"], "name": "k8s-{n}", "zone": "lab.example.com", "group": "k8s"}'
# and gone again: only what assign made
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
     -X POST $LB/api/v1/ipam/unassign -d '{"group": "k8s"}'

These need a full token; the reads work with a read-only one.

The command line (lbcli)

lbcli.py does the same from a shell: one file, Python 3.9 or later, nothing to install. It reads the server from LITEBLOX_URL (or --url) and the token from LITEBLOX_TOKEN (or --token-file).

export LITEBLOX_URL=https://liteblox.example.com LITEBLOX_TOKEN=lbx_…
lbcli.py next --subnet 192.168.1.0/24 --blocksize 3        # 192.168.1.81, 192.168.1.82, 192.168.1.83
lbcli.py reserve 'k8s-{n}' --network 192.168.10.0/24 --blocksize 3 --zone lab.example.com --group k8s
lbcli.py reserve printer --subnet 192.168.1.0/24 --mac aa:bb:cc:dd:ee:ff   # a DHCP reservation
lbcli.py reserve nas --network 192.168.1.0/24 --ipam                        # a name kept in IPAM only
lbcli.py unassign --group k8s
lbcli.py release --hold 3f9c0a1b2c4d
lbcli.py show 192.168.1.20

reserve takes the next free addresses (or --ip) and assigns them in one go; --json prints the API's answer for scripts. Errors start with ERROR: and exit 1.

Time

GET /api/v1/ntp returns the sources every node uses and, for each node with time sync, whether it serves time, to which networks, and chrony's last state (synced, the source it follows, stratum, offset). Each node in GET /api/v1/nodes carries the same state as time.

Status

GET /api/v1/status is the Dashboard as JSON, for a monitoring system: level (ok, warn or error), attention (how many problems need an admin), checked_at (when the health check last heard from a node, UTC), the problems (each with severity, area, what, the page that fixes it as link, and the node), and the tiles. A read-only token is enough.

curl -s -H "Authorization: Bearer $TOKEN" $LB/api/v1/status | jq -r .level

Errors

Status Meaning
401 no token, or it's unknown, revoked or expired
403 the token is read-only, or its user isn't an admin
404 no such zone, record, subnet, reservation, lease, network or job
409 not possible as things are: nothing free (left) for next free, an address already assigned, a forwarding zone has no records, a dynamic zone has no draft, a publish or DHCP change is already running, or a dynamic zone changed on its primary during the update
412 If-Match didn't match: read it again
422 not valid. errors says which field and why ({"detail": …, "errors": {"value": …}})
502 a node or a zone's primary didn't answer

Every other answer is JSON, with detail saying what went wrong.

What it doesn't do (yet)

These are on the pages: - creating and deleting zones, and zone settings - nodes (setup, take over, server settings, promote) - TSIG keys and ACLs - DHCP subnets, settings, interfaces and the failover pair

Every write through the API is in the audit log under the token's user, with the token's name.