Developers

API reference

Everything in the admin console is scriptable. Generate an API key, point at the endpoint below, and manage your tailnet as code.

One key, scoped to exactly what it needs

API keys are bearer tokens with granular scopes — read-only, devices, ACLs, or keys. A CI pipeline gets a key that can enroll nodes and nothing else; your Terraform runner gets one that can push policy. Both expire on a schedule you set.

  • ✓Expiring keys by default for CI and automation
  • ✓Every call lands in the audit log with the key's name
  • ✓100 requests/minute per key, raised on request
$ curl https://api.openvlan.com/v1/devices \
  -H "Authorization: Bearer $OV_API_KEY"
 
# {"devices":[{"id":"node-1","name":
# "ci-runner","os":"linux","online":true ...}]}

Policy writes validate before they apply

Nothing reaches your tailnet half-parsed. PUT a malformed or over-permissive ACL and the API rejects it with the offending line — the dry-run endpoint lets your CI test a policy change the same way it tests code.

# dry-run a policy change from CI
$ curl -X POST .../v1/acl/validate \
  -d @policy-draft.json
 
# {"valid":false,"errors":[
# {"line":14,"msg":"group 'eng-production' undefined"}]}
 
# your tailnet: untouched, as designed

Endpoints

MethodPathDescription
GET/v1/devicesList nodes; filter by user, tag, or online state
GET/v1/devices/{id}Node detail: addresses, routes, last-seen
DELETE/v1/devices/{id}Remove a node and revoke its keys
POST/v1/devices/{id}/tagsApply or replace tags
GET/v1/usersList tailnet users and their roles
GET/v1/aclFetch the current ACL policy
PUT/v1/aclReplace the policy (validated before applying)
POST/v1/acl/validateDry-run a policy without applying it
GET/v1/keysList auth keys
POST/v1/keysCreate a scoped, expiring auth key
GET/v1/routesList advertised subnet routes and approval state
POST/v1/routes/{id}/approveApprove a pending subnet route
GET/v1/logs/connectionsPaginated connection audit log

Common recipes

Copy, paste, adjust the variables.

# create a 7-day CI key that auto-tags nodes
$ curl -X POST https://api.openvlan.com/v1/keys \
  -H "Authorization: Bearer $OV_API_KEY" \
  -d '{"reusable":true,"expiresIn":"168h",
      "tags":["tag:ci"]}'
# list every node tagged for production
$ curl ".../v1/devices?tag=tag:prod&online=true" \
  -H "Authorization: Bearer $OV_API_KEY"
# → 14 devices, all online, keys rotated < 30d
# approve a pending subnet route
$ curl -X POST .../v1/routes/route-77/approve \
  -H "Authorization: Bearer $OV_API_KEY"
# → 10.0.4.0/22 now advertised to the tailnet
# pull yesterday's connection log for the SIEM
$ curl ".../v1/logs/connections?since=24h" \
  -H "Authorization: Bearer $OV_API_KEY" \
  > siem-inbound.ndjson

API FAQs

How do I rotate an API key without downtime?
Create the new key first, swap it in your secret manager, then delete the old one. Both keys work simultaneously, so there's no window where automation is locked out. Most teams put the rotation on the same schedule as their TLS certs — every 90 days.
What happens when I hit the rate limit?
You get a 429 with a Retry-After header, and the limit resets within the minute. Persistent 429s on legitimate workloads usually mean you're polling — switch to reading the connection log with a since parameter, or ask us to raise the ceiling.
Are there SDKs, or just raw REST?
Open-source client libraries in Go, Python, and TypeScript wrap this API — same endpoints, typed responses. Terraform users get the provider instead, which is the same API underneath with declarative resources.
Does the API work with my SSO identity?
API keys are separate from user identity by design — automation shouldn't break when a person leaves. Keys inherit tailnet-level SSO enforcement for console access, and SCIM offboarding doesn't affect them; expiry and scoping do the limiting.
Can I test against a sandbox tailnet?
Yes — the free tier is the sandbox. Spin up a throwaway tailnet, generate a key, and break things freely. The API surface is identical; only seat counts and premium features differ.

Prefer Terraform?

The provider wraps this API with declarative resources.