Skip to main content

UniFi rich HTTP API service

Project description

unifi-api-server

REST + GraphQL HTTP API for UniFi controllers — a standalone HTTP service for desktop apps, web dashboards, Pi extensions, and any consumer that wants typed read access to UniFi Network, Protect, and Access without speaking MCP.

Quickstart

Two paths — pick the one that matches what you have. Both end with the admin URL and the bootstrap key printed in your terminal so you can paste and sign in. No UNIFI_API_DB_KEY to generate, no .env file, no docker exec incantations. The disk-encryption key and the bootstrap admin API key are both auto-generated on first boot and persisted inside the container's named volume.

A. Local clone (developing or testing changes)

Uses docker/docker-compose-api.yml, which builds from source and exposes the API on localhost:8089.

./scripts/start-api.sh

That's the whole thing. The script builds, starts, waits for first-boot bootstrap + HTTP readiness, then prints the URL and the key. Paste the key into http://localhost:8089/admin/login and you're in.

B. Public image (just want to use it)

docker run -d --name unifi-api-server -p 8080:8080 \
  -v unifi-api-state:/var/lib/unifi-api \
  ghcr.io/sirkirby/unifi-api-server:latest && \
  until docker exec unifi-api-server \
    test -f /var/lib/unifi-api/bootstrap-admin-key 2>/dev/null; \
    do sleep 1; done && \
  echo "" && \
  echo "Admin UI:  http://localhost:8080/admin/login" && \
  echo "Admin key: $(docker exec unifi-api-server cat /var/lib/unifi-api/bootstrap-admin-key)"

Paste that whole block into a terminal. It starts the container, waits for first-boot, and prints the URL and key. Open the URL, paste the key, sign in.

What's next

Once you're signed in:

  • Register a controller via the Controllers tab (or POST /v1/controllers).
  • Mint your own keys via the Keys tab — pick read, write, or admin scope per consumer. Once you have a personal key saved, revoke bootstrap-admin and delete /var/lib/unifi-api/bootstrap-admin-key.
  • Explore the APIs:
    • REST playground: /v1/docs
    • GraphQL playground: /v1/graphql
    • OpenAPI spec: /v1/openapi.json
    • Health: /v1/health

First GraphQL query:

curl -s http://localhost:8089/v1/graphql \
  -H "Authorization: Bearer $ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"{ network { clients(controller: \"<id>\") { items { mac hostname } } } }"}'

Production deployments

For shared / production deployments, set UNIFI_API_DB_KEY explicitly (e.g. from a secret manager) so the encryption key lives outside the container volume:

docker run -d \
  --name unifi-api-server \
  -p 8080:8080 \
  -e UNIFI_API_DB_KEY=$(openssl rand -hex 32) \
  -v unifi-api-state:/var/lib/unifi-api \
  ghcr.io/sirkirby/unifi-api-server:latest

When the env var is set, the file-backed fallback is skipped entirely.

Reset / lost-key recovery

State (controllers, audit log, admin keys, both encryption and bootstrap key files) lives in the unifi-api-state volume. docker compose down keeps it; docker compose down -v wipes for a fresh bootstrap.

If you lose the bootstrap admin key file and never saved a personal admin key, the simplest recovery is to wipe and re-bootstrap:

docker compose -f docker/docker-compose-api.yml down -v
docker compose -f docker/docker-compose-api.yml up --build -d

Note that wiping also drops registered controller credentials, since they were encrypted with the now-discarded DB key.

Architecture

unifi-api-server is a standalone HTTP service. It runs independently of the MCP servers — both projects share the same manager packages from unifi-core, but neither depends on the other being running. Choose based on the consumer:

  • Hobbyist with Claude Code: run only the MCP servers; skip unifi-api-server.
  • App developer building on the API: run only unifi-api-server via Docker.
  • Tool builder who wants both: run both as parallel containers (see docs/docker-compose.example.yml).

The HTTP layer is FastAPI; GraphQL is Strawberry on top of the same projection types REST uses, so consumer-facing field names are identical across the two surfaces. Pagination is cursor-based on REST, slice-based on GraphQL.

Distribution

unifi-api-server is published to:

  • PyPI: pip install unifi-api-server
  • GHCR: docker pull ghcr.io/sirkirby/unifi-api-server:latest
  • GitHub Releases: wheels attached to each api/v* tag

The distribution name unifi-api-server establishes unifi-api-* as the family namespace for the API and its future ecosystem (planned: unifi-api-python-sdk, unifi-api-ts-sdk, unifi-api-cli). The import name remains unifi_api.

Configuration

Two layers — environment variables override config.yaml. Defaults work for a standard local install; override only what you need.

Variable Default Purpose
UNIFI_API_DB_KEY auto-generated on first boot Encrypts controller credentials at rest. Auto-generated and persisted to <state_dir>/.db_encryption_key if unset. Not a login credential — set explicitly in production via secret manager so the key lives outside the volume.
UNIFI_API_STATE_DIR /var/lib/unifi-api Where the SQLite state DB lives
UNIFI_API_HTTP_HOST 127.0.0.1 Bind address (set to 0.0.0.0 for non-localhost access)
UNIFI_API_HTTP_PORT 8080 Listen port
UNIFI_API_LOG_LEVEL INFO DEBUG, INFO, WARNING, ERROR

Per-controller credentials are stored encrypted in the state DB after the first POST /v1/controllers call — no controller credentials in env vars.

MFA / 2FA support

unifi-api-server connects to UniFi controllers using local-account credentials (username + password + optional API token). It does not currently support controllers that require MFA / 2FA on local accounts.

If your controller has MFA enabled, you'll need either:

  • A separate local account with MFA disabled (recommended for service accounts)
  • A long-lived API token (UniFi Network and Protect both support this on recent firmware)

This is the same constraint the MCP servers (unifi-network-mcp, unifi-protect-mcp, unifi-access-mcp) inherit. See issue #150 for context.

Documentation

Development

The project uses an uv workspace covering apps/api/ plus the shared packages/unifi-core/ and three MCP apps. Common development commands:

# Install everything once
uv sync --all-packages

# Run the unifi-api-server test suite (~700 tests)
uv run --package unifi-api-server pytest apps/api/tests

# Run live smoke against real controllers (requires .env)
uv run --package unifi-api-server python scripts/live_api_smoke.py --output /tmp/smoke.json

# Re-export drift-gated artifacts after schema changes
uv run --package unifi-api-server python -m unifi_api.graphql.docgen

The full project quality gate runs make pre-commit (lint + format + sync-skills

  • tests) at the repo root.

Image-level smoke harness

scripts/live_api_smoke.py boots the API in-process via ASGITransport, which means it cannot detect dep-closure bugs that only manifest in the published Docker image (e.g. a missing runtime dependency that gets masked by a uv sync --all-packages workspace). For that, use scripts/smoke-api-image.sh:

# Optionally point at a real controller so authenticated paths get tested.
# Without these env vars the sweep still verifies first-boot, auth, and
# the capability_mismatch / api_key_required error paths.
export UNIFI_HOST=10.0.0.1
export UNIFI_USERNAME=svc
export UNIFI_PASSWORD=...
export UNIFI_API_TOKEN=...   # optional; required for DPI to return 200

./scripts/smoke-api-image.sh

The script wipes any existing state, rebuilds the image, brings it up, registers a controller (when env vars are set), runs scripts/api_image_smoke.py against every GET endpoint in the schema, and fails on any 5xx or network error. Tears down on exit.

This is the harness that should run on every release tag.

License

See the repository root LICENSE file. MIT License, © 2025 Chris Kirby.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

unifi_api_server-0.1.2-py3-none-any.whl (357.8 kB view details)

Uploaded Python 3

File details

Details for the file unifi_api_server-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for unifi_api_server-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 02e894c817f347929815a2bd596853c6c463d16bd5bf923af5ee9c0cfce98858
MD5 c58a4ed7b0e9afe79e96bc055f8628d5
BLAKE2b-256 71cc218f100ceba699f0724b25cea691dd51e3463dc9f7dd0779b4a633b5806e

See more details on using hashes here.

Provenance

The following attestation bundles were made for unifi_api_server-0.1.2-py3-none-any.whl:

Publisher: release-api.yml on sirkirby/unifi-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page