bnna Platform API

REST API served by bnpd. All responses are JSON {"result":…,"timings":[…]} unless noted. Errors return {"error":"…","timings":[…]}.

Base URL: https://op.bnna.net

Authentication

Three credential types are accepted:

csvauth Bearer token — platform operator token from users.tsv.

Authorization: Bearer <token>

PVE API token — Proxmox root@pam token, validated against known clusters.

Authorization: PVEAPIToken=root@pam!<name>=<secret>

bnna tenant token — scoped to one account, issued via SSO exchange or created directly. Only valid on /api/accounts/{slug}/… routes where slug matches the token's account.

Authorization: Bearer bn_<token>

Health and Version

GET /health          → "ok" (plain text, no auth)
GET /api/version     → {"server":"bnna","version":"…"}
GET /api/platform/token/inspect   → token metadata

Accounts

Tenant accounts map to Proxmox resource pools.

GET    /api/platform/accounts                         list all accounts
POST   /api/platform/accounts                         create account
GET    /api/platform/accounts/{slug}                  get account
POST   /api/platform/accounts/{slug}/adopt            adopt existing Proxmox pool
POST   /api/platform/accounts/{slug}/suspend          suspend (blocks writes)
POST   /api/platform/accounts/{slug}/enable           re-enable
POST   /api/platform/accounts/{slug}/disable          disable (blocks all access)
DELETE /api/platform/accounts/{slug}                  GC account and resources
GET    /api/platform/accounts/{slug}/health           resource health check
GET    /api/platform/accounts/{slug}/inventory        full inventory
GET    /api/platform/accounts/{slug}/gc               dry-run GC report
GET    /api/platform/accounts/{slug}/settings         account settings
PUT    /api/platform/accounts/{slug}/settings/{key}   upsert setting
DELETE /api/platform/accounts/{slug}/settings/{key}   delete setting
PUT    /api/platform/accounts/{slug}/acls             sync Proxmox ACLs
POST   /api/platform/accounts/{slug}/repair           repair Proxmox pool/ACLs
POST   /api/platform/accounts/{slug}/impersonate      get scoped token for account

Request body for create:

{
  "slug": "acme",
  "display_name": "Acme Corp",
  "email": "ops@acme.com"
}

Networks

GET  /api/networks                           list networks (operator view)
GET  /api/networks/pending                   networks awaiting setup
GET  /api/platform/accounts/{slug}/networks  list account networks
POST /api/platform/accounts/{slug}/networks  assign network to account
DELETE /api/platform/accounts/{slug}/networks/{network_id}

PUT  /api/platform/accounts/{slug}/network   update network assignment
POST /api/platform/networks/allocate         allocate next free subnet
POST /api/platform/networks/prepare          prepare network on cluster
POST /api/platform/networks/apply            apply network config
POST /api/platform/networks/{id}/subnet      add subnet
PUT  /api/platform/networks/{id}/alias       set alias

Instances (VMs and LXC)

GET  /api/instances                   list all instances (across clusters)
GET  /api/instances/{vmid}/domains    list DNS domains for instance
POST /api/instances/{vmid}/domains    add DNS domain
DELETE /api/instances/{vmid}/domains/{domain}
POST /api/instances/{vmid}/start
POST /api/instances/{vmid}/stop
POST /api/instances/{vmid}/shutdown
POST /api/instances/{vmid}/reboot
DELETE /api/instances/{vmid}          destroy instance
POST /api/instances/{vmid}/adopt      adopt unmanaged instance
GET  /api/tasks                       poll Proxmox task status
POST /api/lxc                         create LXC container
POST /api/qemu                        create VM

Bunches (Resource Pools)

GET    /api/bunches                         list bunches
GET    /api/bunches/{slug}/quota            quota usage for bunch
POST   /api/platform/bunches               create bunch
PUT    /api/platform/bunches/{slug}         update bunch
DELETE /api/platform/bunches/{slug}         delete bunch
POST   /api/platform/bunches/sync           sync Proxmox pool membership

DNS

GET  /api/dns/zones                          list DNS zones (operator)
POST /api/subdomains                         create CNAME record
DELETE /api/subdomains/{providerID}/{leaf}   delete CNAME record

GET    /api/accounts/{slug}/dns/providers    list DNS providers for account
POST   /api/accounts/{slug}/dns/providers    register DNS provider
DELETE /api/accounts/{slug}/dns/providers/{id}
GET    /api/accounts/{slug}/dns/zones        list zones for account

POST /api/subdomains body:

{
  "providerId": "<provider-id>",
  "leaf": "myapp",
  "target": "tls-10-11-0-1.a.bnna.net"
}

Or use "vmid": 12345 to derive the target automatically.

Databases — PostgreSQL

Account-scoped. All credentials are stored encrypted; the plaintext password is only returned at creation time.

GET    /api/accounts/{slug}/pg-instances                   list PG instances
POST   /api/accounts/{slug}/pg-instances                   link PG instance
DELETE /api/accounts/{slug}/pg-instances/{id}

GET    /api/accounts/{slug}/pg-databases                   list databases
POST   /api/accounts/{slug}/pg-databases                   create database
POST   /api/accounts/{slug}/pg-databases/adopt             adopt existing database
POST   /api/accounts/{slug}/pg-databases/{name}/reset-password
DELETE /api/accounts/{slug}/pg-databases/{name}

GET    /api/accounts/{slug}/pg-databases/{name}/usage      usage history

GET    /api/platform/pg-instances                 list all PG instances
POST   /api/platform/pg-instances                 register PG instance
PATCH  /api/platform/pg-instances/{id}            update instance
DELETE /api/platform/pg-instances/{id}

Databases — MariaDB

GET    /api/accounts/{slug}/maria-instances
POST   /api/accounts/{slug}/maria-instances
DELETE /api/accounts/{slug}/maria-instances/{id}

GET    /api/accounts/{slug}/maria-databases
POST   /api/accounts/{slug}/maria-databases
POST   /api/accounts/{slug}/maria-databases/adopt
POST   /api/accounts/{slug}/maria-databases/{name}/reset-password
DELETE /api/accounts/{slug}/maria-databases/{name}
GET    /api/accounts/{slug}/maria-databases/{name}/usage

GET    /api/platform/maria-instances
POST   /api/platform/maria-instances
PATCH  /api/platform/maria-instances/{id}
DELETE /api/platform/maria-instances/{id}

Databases — SQL Server

GET    /api/accounts/{slug}/mssql-databases
POST   /api/accounts/{slug}/mssql-databases
POST   /api/accounts/{slug}/mssql-databases/adopt
POST   /api/accounts/{slug}/mssql-databases/{name}/reset-password
DELETE /api/accounts/{slug}/mssql-databases/{name}

GET    /api/platform/mssql-instances
POST   /api/platform/mssql-instances
PATCH  /api/platform/mssql-instances/{id}
DELETE /api/platform/mssql-instances/{id}

Databases — Shared

GET  /api/databases              list all databases (operator, all engines)
POST /api/databases              create database (auto-select instance)
POST /api/databases/{engine}     create database for specific engine

GET  /api/platform/databases                    list all (all engines)
POST /api/platform/databases/{name}/reassign    move database to different instance

POST /api/platform/databases/{name}/reassign body:

{ "account_slug": "acme", "engine": "postgres" }

Engine values: postgres, mysql / mariadb, mssql / sqlserver.

Usage Metering

GET  /api/accounts/{slug}/usage           per-account usage summary
GET  /api/platform/usage                  platform-wide overview
POST /api/platform/usage/collect          trigger immediate collection

Storage

GET  /api/templates                              list VM/LXC templates
GET  /api/platform/storages                      list Proxmox storage
POST /api/platform/storages/fix                  repair storage config
GET  /api/platform/storage-nodes                 list storage nodes
GET  /api/platform/storage-nodes/{id}/test       test storage node
DELETE /api/platform/storage-nodes/{id}
POST /api/platform/storage/{storage}/upload         broadcast upload to all nodes
POST /api/platform/storage/{storage}/download-url   broadcast download-URL

PBS (Proxmox Backup Server)

GET    /api/platform/pbs-instances
POST   /api/platform/pbs-instances
GET    /api/platform/pbs-instances/{id}/test
DELETE /api/platform/pbs-instances/{id}

POST   /api/platform/accounts/{slug}/assign-pbs   assign PBS to account

Clusters

GET  /api/platform/clusters               list clusters
PUT  /api/platform/clusters/{host}        update cluster config
PUT  /api/platform/clusters/{host}/operator-token   rotate operator token
GET  /api/nodes                           list Proxmox nodes

SSH Keys

GET  /api/platform/ssh-keys    list operator SSH public keys

Inventory

GET /api/platform/inventory                 full platform inventory
GET /api/platform/accounts/{slug}/inventory account inventory

Doctor

GET  /api/platform/doctor    run diagnostics (read-only)
POST /api/platform/doctor    run diagnostics + auto-fix

Sheets (CSV Export)

Designed for Google Sheets IMPORTDATA. Returns TSV by default; append ?format=json or ?format=csv.

GET /api/sheets/nodes      node resource usage
GET /api/sheets/instances  instance list with metrics
GET /api/sheets/bunches    bunch quota overview

Credentials

GET  /api/platform/credentials/export    export credentials bundle (dev only)
POST /api/platform/credentials/import    import credentials bundle (dev only)

Requires --enable-dev-credentials-export flag. Never enable in production.

SSO — Trusted Issuers

Per-account list of OIDC issuer URLs that are allowed to exchange tokens for that account. Platform-level issuers are configured via BNNA_SSO_ISSUERS env var and trust all accounts.

GET    /api/platform/accounts/{slug}/trusted-issuers
POST   /api/platform/accounts/{slug}/trusted-issuers
DELETE /api/platform/accounts/{slug}/trusted-issuers/{id}

POST body:

{ "issuer_url": "https://id.example.com" }

SSO — Roles

Named capability sets. Each role maps to a list of PVE and PBS ACL entries. Exactly one role may be is_default: true per account; SSO users without an explicit role receive the default. No default role = no SSO access.

GET    /api/accounts/{slug}/roles
POST   /api/accounts/{slug}/roles
DELETE /api/accounts/{slug}/roles/{id}

GET    /api/accounts/{slug}/roles/{id}/pve-acls
POST   /api/accounts/{slug}/roles/{id}/pve-acls
DELETE /api/accounts/{slug}/roles/{id}/pve-acls/{aclid}

GET    /api/accounts/{slug}/roles/{id}/pbs-acls
POST   /api/accounts/{slug}/roles/{id}/pbs-acls
DELETE /api/accounts/{slug}/roles/{id}/pbs-acls/{aclid}

POST /api/accounts/{slug}/roles body:

{ "name": "viewer", "is_default": true }

POST …/pve-acls body:

{ "path": "/", "pve_role": "PVEVMAdmin", "propagate": true }

POST …/pbs-acls body:

{
  "pbs_instance_id": "<id>",
  "datastore": "backups",
  "namespace": "",
  "pbs_role": "DatastoreReader",
  "propagate": true
}

SSO — Token Exchange

Exchange an SSO identity JWT for a bnna bearer token scoped to an account and optional role. This endpoint is public — the SSO JWT is the credential.

POST /api/auth/sso/exchange

Request:

POST /api/auth/sso/exchange
Authorization: Bearer <sso-jwt-from-oidc-provider>
Content-Type: application/json

{
  "account_slug": "acme",
  "label": "my-session",
  "role_id": "<optional-role-id>",
  "expires_in": 86400
}

Response:

{
  "result": {
    "token": "bn_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "token_id": "<uuid>",
    "account_slug": "acme",
    "role_id": "",
    "label": "my-session",
    "expires_at": "2026-04-14T20:00:00Z",
    "sub": "user@example.com"
  }
}

The returned token value is the full bearer token — store it securely. It is not recoverable after this response.

SSO — Token Management

GET    /api/accounts/{slug}/tokens       list active bnna tokens
DELETE /api/accounts/{slug}/tokens/{id}  revoke token

GET response does not include token values — only metadata (id, label, role_id, expires_at).