Skip to content

REST API

Reference for every endpoint the Organization Manager serves. The service speaks JSON over HTTP on the http.address listener; if TLS is configured, the same listener serves HTTPS.

Conventions

Base path

Every endpoint sits under /org/{org} where {org} is the organisation identifier. Outside that tree the listener serves the status page (/), /health, and /metrics; see Operations → Observability.

Content type

Requests and responses are application/json. Every API response sets Content-Type: application/json.

Standard response headers

Header Description
X-Request-ID Unique identifier for the request, generated if not supplied by the client. Echoed in logs and useful in support tickets.
X-Org-Version The organisation's current org_version after the call. Bumped on every successful write that touches the database. Set on every endpoint that has an organisation to report a version for, which is everything except POST /org/{org}/redeploy (which does not bump the version) and DELETE /org/{org} (which returns 204 after the organisation has been removed). Useful for cache invalidation.

CIDRs in query parameters

CIDRs always travel as query parameters, not as path segments — slash encoding in path segments confuses some proxies and clients:

GET /org/{org}/overrides?cidr=192.0.2.1/32

Error response shape

Most error responses are:

{ "error": "Human-readable error description" }

500 Internal Server Error responses additionally carry the underlying error detail and the request ID, so a support ticket can be cross-referenced against the server logs without an extra round-trip:

{
  "error": "Failed to modify prefixes",
  "detail": "acquire advisory lock: ...",
  "request_id": "req-abc123xyz"
}

The HTTP status code carries the error class; see Status codes.

Idempotency

Every endpoint on this page is idempotent in the sense most useful to clients with a retry policy:

  • PUT, PATCH, DELETE and the override POSTs converge to the same end state when retried with the same input.
  • POST /org/{org}/overrides/batch is idempotent per item — replaying the same batch produces the same final state.
  • POST /org/{org}/redeploy repeats are safe; multiple calls converge to the same deployed state in the IP Mapper.

CIDR semantics

Worth understanding before using the override endpoints; the rules also drive the Architecture → Effective range behaviour the IP Mapper sees.

Per-organisation isolation

CIDRs are isolated per organisation. Two organisations may have overlapping or identical override CIDRs in their own namespaces without conflict. (Prefixes themselves cannot overlap across organisations — that check is global; see Modify prefixes.)

active vs outside-prefixes

Every override is either active (its CIDR is fully contained within the union of the organisation's current prefixes) or outside-prefixes (not contained). Coverage is tested against the union, so two adjacent /9 prefixes together cover a /8 and an override on that /8 is active.

The status is computed from the live prefix set on every read — it is not stored. So:

  • Posting an override whose CIDR is not yet covered is accepted; the override stores in outside-prefixes and is held in reserve.
  • Removing a prefix does not remove the overrides under it. The next read of those overrides reports outside-prefixes. The IP Mapper-side application of that flip happens on the next deploy — until then, the previous deployed state is what queries see.
  • Adding a prefix that covers a previously inactive override is reflected in the API status on the next read; the IP Mapper sees the override re-applied on the next deploy.

Overwrite-on-POST

Posting a CIDR that already exists overwrites the previous entry completely — every field including meta is replaced. There is no partial update on the override level; PATCH-style merging is not supported.

POST /org/{org}/overrides
{"cidr": "10.0.1.0/24", "username": "alice", "meta": {"dept": "eng"}}

POST /org/{org}/overrides
{"cidr": "10.0.1.0/24", "username": "bob"}
# Previous entry replaced; meta is now empty.

Most-specific-wins, no inheritance

When override CIDRs overlap, the most specific (longest prefix) wins for each IP. Less specific overrides apply to the unsplit pieces. There is no attribute inheritance — see the worked example in Architecture → Effective range.

Status codes

Status Meaning
200 OK Success with a JSON body.
204 No Content Success with no body — DELETE endpoints.
400 Bad Request Malformed input: invalid JSON, invalid CIDR, missing required field, missing ?confirm=true, batch larger than the limit.
404 Not Found The organisation or single override does not exist.
405 Method Not Allowed Wrong method for the path.
422 Unprocessable Entity Valid input that conflicts with state — currently only "prefix overlaps another organisation".
500 Internal Server Error Unexpected server-side failure.

Organisation endpoints

Create or update organisation

PUT /org/{org}
PATCH /org/{org}

Creates a new organisation or bumps the version of an existing one. PATCH is currently equivalent to PUT — there are no per-field organisation attributes today.

Response: 200 OK

{ "success": true }

X-Org-Version is set to the new version after the call.

Get organisation

GET /org/{org}

Response: 200 OK

{
  "org_id": "acme",
  "version": 42,
  "created_at": "2026-01-28T08:58:08Z",
  "updated_at": "2026-04-30T11:00:00Z"
}

Errors: 404 if the organisation does not exist.

Delete organisation

DELETE /org/{org}?confirm=true

Deletes the organisation, every prefix under it, and every override under it. Asks the IP Mapper to clean every session for this organisation.

The ?confirm=true query parameter is required; without it the call is refused with 400.

Response: 204 No Content

Errors: 400 without ?confirm=true; 404 if the organisation does not exist.

Force redeploy

POST /org/{org}/redeploy

Forces a full redeploy of the organisation to the IP Mapper — every current session is republished under a fresh revision and the IP Mapper is asked to clean any older revision for this organisation. Useful when the IP Mapper is suspected to be out of sync; the deploy queue otherwise picks up changes on its own.

Response: 200 OK

{ "success": true }

Errors: 404 if the organisation does not exist.

Prefix endpoints

Modify prefixes

PATCH /org/{org}/prefixes

Adds or removes IP CIDR prefixes belonging to the organisation. Both arrays are optional; either or both may be empty.

Request:

{
  "add": ["10.0.0.0/8", "2001:db8::/32"],
  "remove": ["192.168.0.0/16"]
}

Response: 200 OK

{ "success": true }

The deploy that follows includes a cleanup call bounded by the removed subnets, so any session under a removed prefix is cleaned up at the IP Mapper without touching unrelated sessions.

Errors:

  • 400 — malformed CIDR in either array.
  • 404 — organisation does not exist.
  • 422 — one of the CIDRs in add overlaps a prefix owned by another organisation.

Override endpoints

List overrides

GET /org/{org}/overrides
GET /org/{org}/overrides?fields=cidr
GET /org/{org}/overrides?fields=full

Lists every override for the organisation.

Parameter Values Description
fields cidr (default), full Response detail level.

Response with fields=cidr (default):

{
  "cidrs": [
    "192.0.2.1/32",
    "2001:db8:1::/48"
  ]
}

Response with fields=full:

{
  "overrides": [
    {
      "cidr": "192.0.2.1/32",
      "username": "alice",
      "deviceid": "device-123",
      "location": "office-east",
      "meta": { "employee-id": "12345" },
      "_status": "active",
      "_mtime": "2026-01-28T08:58:08Z",
      "_ctime": "2026-01-28T08:58:08Z"
    }
  ]
}

Errors: 404 if the organisation does not exist.

Pagination by limit / cursor, filtering by address family, by prefix containment, or by status are not yet supported. The endpoint returns every override.

Get a single override

GET /org/{org}/overrides?cidr=192.0.2.1/32

Response: 200 OK — the override object directly, no wrapper.

{
  "cidr": "192.0.2.1/32",
  "username": "alice",
  "deviceid": "device-123",
  "location": "office-east",
  "meta": { "employee-id": "12345" },
  "_status": "active",
  "_mtime": "2026-01-28T08:58:08Z",
  "_ctime": "2026-01-28T08:58:08Z"
}

Errors:

  • 400 — malformed CIDR.
  • 404 — organisation or override does not exist; the message indicates which.

Create or update an override

POST /org/{org}/overrides

Creates a new override or replaces an existing one (overwrite — see Overwrite-on-POST).

Request:

{
  "cidr": "192.0.2.1/32",
  "username": "alice",
  "deviceid": "device-123",
  "location": "office-east",
  "meta": { "employee-id": "12345" }
}

Response: 200 OK

{ "success": true, "cidr": "192.0.2.1/32" }

The response does not carry the override's status. Posting a CIDR that lies outside the organisation's prefixes is still accepted — the override stores in outside-prefixes and waits for a covering prefix. To check the resulting status, follow up with GET /org/{org}/overrides?cidr=....

Errors:

  • 400 — invalid input (missing cidr, malformed CIDR, invalid JSON).
  • 404 — organisation does not exist.

Delete an override

DELETE /org/{org}/overrides?cidr=192.0.2.1/32

Response: 204 No Content

The endpoint is idempotent: deleting a CIDR that does not exist returns 204 just like deleting one that did. There is no "override not found" reply for the single-CIDR delete.

Errors:

  • 400 — missing cidr parameter or malformed CIDR.
  • 404 — organisation does not exist.

Batch override operations

POST /org/{org}/overrides/batch

Bulk create/update/delete in a single call. Up to 10 000 items per request.

Request:

{
  "data": [
    {
      "cidr": "192.0.2.1/32",
      "username": "alice",
      "deviceid": "device-123",
      "location": "office-east"
    },
    {
      "cidr": "192.0.2.40/32",
      "delete": true
    }
  ]
}

Each item without "delete": true is an upsert. Each item with "delete": true is a delete by CIDR.

Response: 200 OK

{
  "success": true,
  "processed": 998,
  "skipped": 2,
  "errors": [
    {
      "index": 42,
      "cidr": "bad/cidr",
      "error": "CIDR format is invalid"
    },
    {
      "index": 87,
      "error": "Required field 'cidr' is missing"
    }
  ]
}

success is false when at least one item produced an error. The HTTP status is still 200 OK; clients must inspect the body. Items listed in errors were skipped and not applied; the rest are processed atomically.

Errors:

  • 400 — invalid request envelope, batch larger than 10 000.
  • 404 — organisation does not exist.

Effective ranges

GET /org/{org}/effective-ranges
GET /org/{org}/effective-ranges?force_latest=1

Returns the post-collapse, most-specific-wins output that the IP Mapper sees — the deployed snapshot from the in-process deploy cache.

The force_latest=1 query parameter recomputes from PostgreSQL on the fly, bypassing the deploy cache. Useful for verifying what the next deploy will publish, ahead of the worker actually picking the organisation up.

Response:

{
  "version": 42,
  "dirty": false,
  "dirty_since": "",
  "ranges": [
    {
      "from": "192.0.2.0",
      "to": "192.0.2.12",
      "orig_cidr": "192.0.2.0/24",
      "username": "department1",
      "location": "building-a"
    },
    {
      "from": "192.0.2.13",
      "to": "192.0.2.13",
      "orig_cidr": "192.0.2.13/32",
      "username": "employee123",
      "deviceid": "laptop-456"
    },
    {
      "from": "192.0.2.14",
      "to": "192.0.2.255",
      "orig_cidr": "192.0.2.0/24",
      "username": "department1",
      "location": "building-a"
    }
  ]
}
Field Meaning
version The org_version of the deployed snapshot (or of the SQL state when force_latest=1).
dirty true when there is queued or in-flight deploy work the snapshot does not yet reflect.
dirty_since RFC 3339 timestamp at which the queued work started waiting. Empty when not dirty.
ranges[].orig_cidr The override CIDR a range derives from. A range may be a partial slice of the original CIDR if a more specific override split it.
ranges[].meta Not included — meta is not part of effective ranges.

Errors: 404 if the organisation does not exist.

Field reference

Override fields

Field Type Required Description
cidr string Yes IP CIDR block, e.g. 192.0.2.1/32, 2001:db8::/48.
username string No Override username for downstream filtering.
deviceid string No Device identifier in prefix:value form (see below).
location string No Tiered filtering location identifier.
meta object No Customer metadata, key-value. Most keys pass through opaquely; see Reserved meta keys.

deviceid format

prefix:value. The prefix is customer-defined and tells the downstream consumer how to interpret the value:

Prefix Description Example
mac MAC address mac:000036123456
msisdn Mobile phone number msisdn:0123456789
custom Customer-defined foo:bar

System fields (read-only)

Field Type Description
_status string active or outside-prefixes. Computed at runtime.
_mtime string Modification time, RFC 3339.
_ctime string Creation time, RFC 3339.

Reserved meta keys

meta.trace_id is reserved and gets special handling:

{ "meta": { "trace_id": "abc-123" } }

When meta.trace_id is a string, orgmanager forwards it on the IP Mapper session update as trace_id. When absent or non-string, no trace ID is forwarded. The trace ID is observational only — it does not affect override identity, session IDs, or diffing.

To avoid future clashes, customer-defined meta keys should use a private prefix:

{ "meta": { "acme.employee_id": "12345", "acme.department": "engineering" } }

Treat unprefixed names like trace_id as potentially reserved for platform use.

Limits

Limit Value Description
Batch size 10 000 items Maximum items per POST /org/{org}/overrides/batch. Larger batches are refused with 400.

Validation

CIDRs are validated in this order:

  1. Syntax — must be a valid CIDR.
  2. Prefix length0..32 for IPv4, 0..128 for IPv6.
  3. Canonical form — host bits must be zero. 192.0.2.1/24 is refused; use 192.0.2.0/24.
  4. Prefix coverage — checked against the organisation's prefixes; not an error, only the active / outside-prefixes status.