lumnik. / scenarios All scenarios · Français

NetBox: the whole network inventory read with a read-only token

Goal Read every device of a NetBox, the network team's source of truth, each night, with a read-only token.

NetBox is built on Django REST Framework: count, next and results in every answer, and a token presented by the word Token, not Bearer. lumnik names both:

manifests/lab-docker-compose.yml
# The network team's source of truth: a real NetBox, its devices read through the REST API.name: lumnik-netboxservices:  netbox:    image: netboxcommunity/netbox:v4.1    depends_on: [postgres, redis, redis-cache]    environment:      DB_HOST: postgres      DB_NAME: netbox      DB_USER: netbox      DB_PASSWORD: netbox-lab                 # the lab's own, on this machine only      REDIS_HOST: redis      REDIS_CACHE_HOST: redis-cache      SECRET_KEY: lab-only-secret-key-0123456789abcdefghijklmnopqrstuvwxyz-0123456789      SUPERUSER_NAME: netbox-admin      SUPERUSER_EMAIL: admin@netbox.test      SUPERUSER_PASSWORD: netbox-admin-pw-1      SKIP_SUPERUSER: "false"      ALLOWED_HOSTS: "*"    ports: ["127.0.0.1:${NETBOX_PORT:-9270}:8080"]   # this machine only: the admin password is the lab's  postgres:    image: postgres:16-alpine    environment:      POSTGRES_DB: netbox      POSTGRES_USER: netbox      POSTGRES_PASSWORD: netbox-lab    tmpfs: ["/var/lib/postgresql/data"]  redis:    image: valkey/valkey:8.0-alpine  redis-cache:    image: valkey/valkey:8.0-alpine
manifests/10-devices.yaml
apiVersion: connectors.lumnik.io/v1kind: Connectormetadata:  name: scenario-netbox-a  connector_type: rest-generic  scopes: [network]spec:  base_url: http://host.docker.internal:9270/api  auth: { kind: bearer, scheme: Token, token_env: SCENARIO_NETBOX_TOKEN }   # Django REST Framework's token word  headers:    - { name: Accept, value: application/json }  endpoints:    - id: devices      path: /dcim/devices/      method: GET      pagination: { kind: drf-limit-offset, limit: 50 }      # NetBox is Django REST Framework      target:        kind: hybrid        table: ext.scenario_netbox_a        unique_key: $.id        promote:          - { name: name, path: $.name, type: text }          - { name: site, path: $.site.slug, type: text }          - { name: status, path: $.status.value, type: text }          - { name: model, path: $.device_type.model, type: text }          - { name: last_updated, path: $.last_updated, type: timestamptz }
$ lm connector run scenario-netbox-a
run scheduled: 5ae5a95b-1125-43b5-8921-f15a6ef53d0e

$ lm run get 5ae5a95b-1125-43b5-8921-f15a6ef53d0e
ID        CONNECTOR          ENDPOINT  STATUS     IN   OUT  SKIPPED  DEAD-LETTERS  STARTED           ENDED             EVALUATED
5ae5a95b  scenario-netbox-a  devices   Completed  120  120  0        0             2026-10-30 22:51  2026-10-30 22:51  yes
lille
8 offline · 40 devices
lyon
8 offline · 40 devices
nantes
14 offline · 40 devices

evidence: replay.log, page-1.json, requests-connect.txt, offline-before.txt, offline-after.txt, offline-by-site.txt · transform v1

Limits

  • Each night rereads the whole inventory. NetBox's last_updated filter would reread only the changes; this replay does not show it, its inventory being created within the minute.
  • A device deleted from NetBox stays in the hub with its last state.
  • Demo NetBox, synthetic devices.

Network · inventory

The network inventory lives in NetBox.

It must be read every night, with no write access.

Demo NetBox

The problem

NetBox refuses the token as usually sent.

  • It wants "Token <key>", not "Bearer <key>".
  • That is Django REST Framework's rule, which it is built on.

What is not changed

NetBox stays as it is.

  • A read-only token.
  • Its standard REST API.

What had to be written

auth: { kind: bearer, scheme: Token }
pagination: { kind: drf-limit-offset }

Name the framework and its word.

One 28-line connector.

Red

The replay found a gap.

  • The hub only wrote "Bearer".
  • "Token" had to be stored inside the secret: fixed (icreated/lumnik#1640).

Green

120 devices, 3 requests.

  • The next night, 6 switches taken offline.
  • The hub counts them per site.

The limit

Each night rereads the whole inventory.

  • NetBox's last_updated filter would reread only the changes.
  • A deleted device stays in the hub with its last state.

Try it

The scenario replays locally.

A real NetBox, every step published with its output.

This scenario runs on lumnik Open, free and open source.

Try lumnik Open →