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:
Error response shape¶
Most error responses are:
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,DELETEand the overridePOSTs converge to the same end state when retried with the same input.POST /org/{org}/overrides/batchis idempotent per item — replaying the same batch produces the same final state.POST /org/{org}/redeployrepeats 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-prefixesand 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¶
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
X-Org-Version is set to the new version after the call.
Get organisation¶
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¶
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¶
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
Errors: 404 if the organisation does not exist.
Prefix endpoints¶
Modify prefixes¶
Adds or removes IP CIDR prefixes belonging to the organisation. Both arrays are optional; either or both may be empty.
Request:
Response: 200 OK
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 inaddoverlaps a prefix owned by another organisation.
Override endpoints¶
List overrides¶
Lists every override for the organisation.
| Parameter | Values | Description |
|---|---|---|
fields |
cidr (default), full |
Response detail level. |
Response with fields=cidr (default):
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¶
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¶
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
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 (missingcidr, malformed CIDR, invalid JSON).404— organisation does not exist.
Delete an override¶
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— missingcidrparameter or malformed CIDR.404— organisation does not exist.
Batch override operations¶
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¶
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:
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:
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:
- Syntax — must be a valid CIDR.
- Prefix length —
0..32for IPv4,0..128for IPv6. - Canonical form — host bits must be zero.
192.0.2.1/24is refused; use192.0.2.0/24. - Prefix coverage — checked against the organisation's prefixes;
not an error, only the
active/outside-prefixesstatus.