Quick start.

Three steps from zero to your first streaming agent response.

01

Get an API key

Create a key in your workspace settings at Settings → API Keys. Keys are scoped to a workspace and can be restricted to read-only.

shell Store this securely — it won't be shown again.
export OMNITWIN_API_KEY=ot_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
02

Make your first request

List subnets in your workspace. Pagination defaults to 50 per page.

curl
curl -X GET \
  https://api.myomnitwin.com/v1/subnets \
  -H "Authorization: Bearer $OMNITWIN_API_KEY" \
  -H "Accept: application/json"
response · 200 OK
{
  "data": [
    {
      "id":       "sn_01HX7K2M3N4P5Q6R7S8T9U0V",
      "cidr":     "10.128.0.0/16",
      "status":   "allocated",
      "purpose":  "prod-us-east"
    }
  ],
  "meta": { "total": 142, "page": 1, "per_page": 50 }
}
03

Read a streaming agent response

The POST /agents/command endpoint returns a Server-Sent Events stream. Each line is a typed JSON envelope.

curl · SSE
curl -N -X POST \
  https://api.myomnitwin.com/v1/agents/command \
  -H "Authorization: Bearer $OMNITWIN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"command":"Find available /24 blocks in 10.128.0.0/16"}'
stream · text/event-stream
data: { "type": "agent_status",    "body": { "message": "Searching intent database..." } }
data: { "type": "data_chunk",      "body": { "matches": 3 } }
data: { "type": "agent_tool_call", "body": { "fn": "ipam_search" } }
data: { "type": "final_result",    "body": { "available_blocks": 2 } }
data: [DONE]

Authentication.

All API requests must be authenticated. Include your API key in the Authorization header on every request.

Bearer token (recommended)

HTTP header
Authorization: Bearer ot_live_xxxxxxxxxxxx

API key header (service accounts)

HTTP header
X-OmniTwin-Key: ot_live_xxxxxxxxxxxx

Never expose API keys in client-side code. For browser integrations, proxy requests through your backend.

Base URLs

Production https://api.myomnitwin.com/v1
Sandbox https://sandbox.myomnitwin.com/v1 No real data. Safe for integration testing.

Subnets IPAM

Manage IP address allocations. All writes are identity-bound — every mutation records the actor, timestamp, and intent in the immutable audit trail.

GET /subnets List subnets

Returns a paginated list of subnets. Filter by CIDR prefix, status, or purpose tag.

Query parameters
cidr string Filter to subnets within this CIDR block. e.g. 10.0.0.0/8
status string One of allocated · reserved · available · deprecated
purpose string Free-text purpose tag. Supports partial match.
page integer Page number. Default: 1
per_page integer Results per page. Max: 200. Default: 50
response · 200 OK
{
  "data": [
    {
      "id":         "sn_01HX7K2M3N4P5Q6R7S8T9U0V",
      "cidr":       "10.128.0.0/16",
      "status":     "allocated",
      "purpose":    "prod-us-east",
      "vlan_id":    128,
      "owner":      "team:platform",
      "created_at": "2024-11-03T14:22:00Z"
    }
  ],
  "meta": { "total": 142, "page": 1, "per_page": 50 }
}
GET /subnets/:id Get subnet detail

Returns a single subnet with full metadata, child allocations, and current utilization.

response · 200 OK
{
  "id":           "sn_01HX7K2M3N4P5Q6R7S8T9U0V",
  "cidr":        "10.128.0.0/16",
  "status":      "allocated",
  "purpose":     "prod-us-east",
  "total_ips":   65536,
  "used_ips":    18943,
  "utilization": 0.289,
  "children":    ["sn_01HX...", "sn_01HY..."],
  "tags":        { "region": "us-east-1", "env": "prod" }
}
POST /subnets Create subnet allocation

Allocates a new subnet. The math engine validates the CIDR, checks for overlap, and records intent in Postgres.

Request body · application/json
cidr required string CIDR notation. e.g. 10.128.16.0/20
purpose required string Human-readable purpose label. Used for search and audit.
parent_id string ID of the parent subnet. Enforces hierarchy.
vlan_id integer VLAN association. 1–4094.
tags object Arbitrary key-value metadata.
response · 201 Created
{
  "id":         "sn_01HZ9A3B4C5D6E7F8G9H0I1J",
  "cidr":      "10.128.16.0/20",
  "status":    "allocated",
  "created_at": "2024-11-03T15:00:00Z"
}
PATCH /subnets/:id Update subnet

Update mutable fields on an existing subnet. Writes are identity-bound: the actor is recorded and the change is appended to the immutable audit trail. CIDR itself cannot be changed — allocate a new subnet instead.

Request body · application/json (all fields optional)
purpose string Update the purpose label.
status string Transition status: allocated · reserved · deprecated
owner string Owner reference. e.g. team:platform or user:alice
tags object Merged (not replaced) with existing tags.
GET /subnets/:id/audit Get immutable audit trail

Returns the full immutable audit log for a subnet. Every mutation — create, update, agent action — is recorded with actor identity and timestamp. Append-only; records cannot be deleted.

response · 200 OK
{
  "subnet_id": "sn_01HX7K2M3N4P5Q6R7S8T9U0V",
  "events": [
    {
      "seq":    1,
      "action": "created",
      "actor":  "user:alice@acme.com",
      "at":     "2024-11-03T14:22:00Z"
    },
    {
      "seq":    2,
      "action": "status_changed",
      "actor":  "agent:ipam-allocator",
      "at":     "2024-11-04T09:15:32Z"
    }
  ]
}

Devices DCIM

Physical and virtual infrastructure objects. Each device carries two parallel states: twin state (what OmniTwin believes is true) and detected state (what was last observed via discovery). The gap between them is drift.

GET /devices List devices

Returns a paginated list of devices. Filterable by site, rack, role, or drift status.

Query parameters
site string Site slug or ID.
role string Device role: switch · router · server · firewall · pdu
has_drift boolean Filter to devices with unresolved drift. Default: false
GET /devices/:id Get device with twin + detected state

Returns the full device object including both twin state (Postgres intent) and detected state (Neo4j observed). The drift_score is a normalized float from 0 (aligned) to 1 (fully diverged).

response · 200 OK
{
  "id":             "dv_01HX9B3C4D5E6F7G8H9I0J1K",
  "hostname":      "spine-01.nyc1",
  "role":          "switch",
  "drift_score":   0.17,
  "twin_state":    { "interfaces": 48, "mgmt_ip": "10.0.1.1" },
  "detected_state": { "interfaces": 46, "last_seen": "2024-11-03T18:42:11Z" }
}
GET /devices/:id/drift Get drift events for a device

Returns the time-ordered list of drift events for a device. Each event records what diverged, when it was detected, and whether it has been reconciled.

response · 200 OK
{
  "device_id": "dv_01HX9B3C4D5E6F7G8H9I0J1K",
  "events": [
    {
      "id":           "dr_01HZA1B2C3",
      "field":        "interfaces",
      "twin_value":   48,
      "actual_value": 46,
      "detected_at":  "2024-11-03T18:42:11Z",
      "status":       "open"
    }
  ]
}
POST /devices/:id/reconcile Trigger one-click reconcile

Launches a reconciliation job for the device. The agent computes the diff between twin state and detected state, applies the resolution strategy, and updates both databases. Returns immediately with a job ID.

Request body · application/json (all optional)
strategy string trust_twin · trust_detected · interactive. Default: interactive
fields string[] Limit reconciliation to specific fields. Omit to reconcile all drift.
response · 202 Accepted
{
  "job_id": "job_01HZA1B2C3D4E5F6G7H8I9J",
  "status": "queued",
  "stream": "/v1/agents/sessions/sess_xxx/events"
}

Agents Core

The agent API is the primary interface to the platform. POST /agents/command accepts natural-language or structured commands and returns a streaming SSE response. Every session is persisted with full telemetry.

The command endpoint is streaming-first. Set Accept: text/event-stream to receive typed events as they arrive. Omit the header to receive a single JSON response after the agent finishes.

POST /agents/command Execute a command · main API SSE

Executes a command against the OmniTwin agent runtime. Accepts natural language ("Find available /24 blocks in the prod VPC") or deterministic CIDR/hardware queries. Returns a Server-Sent Events stream of typed JSON envelopes.

Request body · application/json
command required string The command to execute. Natural language or deterministic query.
session_id string Continue an existing session. Omit to start a new session.
context object Additional context keys: subnet_id, device_id, scope
model string Override the workspace default LLM. e.g. anthropic/claude-3-5-sonnet
curl · streaming request
curl -N -X POST \
  https://api.myomnitwin.com/v1/agents/command \
  -H "Authorization: Bearer $OMNITWIN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"command":"Allocate a /24 for the payments service in us-east"}'
stream · text/event-stream
data: { "type": "agent_status",    "seq": 1, "body": { "message": "Parsing intent..." } }

data: { "type": "data_chunk",      "seq": 2, "body": { "source": "ipam", "matches": 2 } }

data: { "type": "agent_tool_call", "seq": 3, "body": { "fn": "ipam_allocate" } }

data: { "type": "final_result",    "seq": 4, "body": { "committed": true } }

data: [DONE]
GET /agents/sessions List active agent sessions

Returns active and recent sessions for the workspace. Sessions expire after 24 hours of inactivity.

GET /agents/sessions/:id Get session with telemetry

Returns a session object including full telemetry: model used, token counts, tool calls made, latency per step, and final status.

response · 200 OK
{
  "id":        "sess_01HZ9A3B4C5D6E7F",
  "command":  "Allocate a /24 for the payments service",
  "status":   "completed",
  "model":    "anthropic/claude-3-5-sonnet",
  "telemetry": { "input_tokens": 842, "output_tokens": 316, "tool_calls": 2, "duration_ms": 1840 }
}
GET /agents/sessions/:id/events SSE stream of agent events SSE

Subscribe to the live event stream for a session. If the session is still active, this is a live stream. If the session has completed, the full event history is replayed and the stream closes.

Import Migration

Ingest data from existing IPAM tools. Supported sources: Infoblox, BlueCat, phpIPAM, and CSV. Import jobs run asynchronously; conflicts are surfaced for human or agent review before committing to the intent database.

POST /import Start import job

Starts an import job. For file-based sources (CSV, export files), upload using multipart/form-data. For API-connected sources, provide credentials in the request body.

Request body · multipart/form-data or application/json
source required string One of: infoblox · bluecat · phpipam · csv
file file CSV or export file. Required when source is csv.
credentials object API credentials for connected sources. Shape varies by source.
dry_run boolean Validate and surface conflicts without committing. Default: false
response · 202 Accepted
{
  "job_id":  "imp_01HZ9A3B4C5D6E7F8G9H0I",
  "source":  "infoblox",
  "status":  "queued",
  "dry_run": false
}
GET /import/:jobId Get import job status

Returns the current status of an import job including progress, counts, and any fatal errors.

response · 200 OK
{
  "job_id":    "imp_01HZ9A3B4C5D6E7F8G9H0I",
  "status":    "processing",
  "progress":  { "processed": 1240, "total": 3817 },
  "conflicts": 14,
  "committed": 1226
}
GET /import/:jobId/conflicts List import conflicts

Returns a list of conflicts detected during import — overlapping CIDRs, duplicate hostnames, mismatched ownership. Each conflict has a suggested resolution that can be accepted via the command bar or API.

response · 200 OK
{
  "conflicts": [
    {
      "id":          "cf_01HZB1C2D3",
      "type":        "cidr_overlap",
      "import_cidr": "10.128.0.0/20",
      "existing_id": "sn_01HX...",
      "suggestion":  "mark_as_child"
    }
  ]
}

Topology Neo4j

Graph queries against the live network topology. The topology layer is backed by Neo4j and reflects observed physical and logical connections. Use it to find paths, understand blast radius, and navigate the network graph programmatically.

GET /topology/graph Get topology subgraph

Returns a subgraph from the topology. Scope the query with a device ID, subnet ID, or site to avoid full-graph responses. The response is a node-edge graph suitable for rendering with D3, Cytoscape, or similar.

Query parameters
scope_id string Center the subgraph on this node (device, subnet, or site ID).
depth integer Hop depth from the scope node. Default: 2. Max: 5
include_drift boolean Annotate edges with drift status. Default: false
response · 200 OK
{
  "nodes": [
    { "id": "dv_01HX...", "type": "switch", "label": "spine-01.nyc1" },
    { "id": "dv_01HY...", "type": "server",  "label": "rack-a-01" }
  ],
  "edges": [
    { "from": "dv_01HX...", "to": "dv_01HY...", "type": "connected_to" }
  ]
}
GET /topology/path/:from/:to Find path between nodes

Finds the shortest path between two nodes in the topology graph (Dijkstra on Neo4j). Useful for blast-radius analysis, cable tracing, and network segmentation review.

response · 200 OK
{
  "from": "dv_01HX...",
  "to":   "dv_01HZ...",
  "hops": 3,
  "path": ["dv_01HX...", "dv_01HY...", "dv_01HZ..."],
  "edges": [
    { "port_a": "eth1/1", "port_b": "eth1/24", "cable": "lc-01482" }
  ]
}

Streaming contract.

The agent runtime is gRPC/MCP-streaming-first. The REST API surface exposes this via Server-Sent Events (SSE). Every message in the stream is a typed JSON envelope with a consistent shape.

Envelope shape

TypeScript interface
interface StreamEvent {
  type:    EventType;
  seq:     number;     // monotonic per session
  session: string;     // session ID
  ts:      string;     // ISO 8601
  body:    unknown;    // typed per EventType
}

type EventType =
  "agent_status"    // status update, reasoning step
  | "data_chunk"      // partial result data
  | "agent_tool_call" // tool invocation + args
  | "final_result"    // terminal event with result
  | "error";          // fatal error, stream closes

Event type reference

agent_status Reasoning step or status message. body.message is display-safe prose.
data_chunk Partial result data. Shape varies by query type. Safe to render incrementally.
agent_tool_call Tool invocation. body.fn is the tool name; body.args are the call arguments.
final_result Terminal event. Always the last data event before [DONE]. Contains the committed result.
error Fatal error. body.code and body.message. Stream closes after this event.

The stream always terminates with data: [DONE] on a line by itself. Treat the absence of final_result before [DONE] as an error condition.

SDKs.

Native client libraries are in development for pre-beta release. The REST API is stable now — build against it directly while the SDKs land.

Python

Async-first. Typed dataclasses. Generator-based streaming interface. Targets Python 3.11+.

pip install omnitwin
Coming soon

Go

Idiomatic Go client. Context-aware. Channel-based streaming. First-class gRPC support.

go get github.com/omnitwin/sdk-go
Coming soon

TypeScript

Full types for all endpoints and event envelopes. AsyncIterable streaming. Node + edge runtime compatible.

npm install @omnitwin/sdk
Coming soon

Rate limits & errors.

Rate limit headers

Every response includes rate limit state. The default limit is 1,000 requests per minute per workspace.

response headers
X-RateLimit-Limit:     1000
X-RateLimit-Remaining: 842
X-RateLimit-Reset:     1730654460   # Unix timestamp
Retry-After:           12           # seconds, if 429

Error format

All errors return a consistent JSON body with a machine-readable code and a human-readable message.

error response · 4xx / 5xx
{
  "error": {
    "code":       "cidr_overlap",
    "message":    "10.128.0.0/20 overlaps existing sn_01HX...",
    "request_id": "req_01HZB1C2D3E4F5G6H7"
  }
}

HTTP status codes

200 OK — request succeeded.
201 Created — resource was created. Body contains the new resource.
202 Accepted — async job queued. Poll the returned job_id for status.
400 Bad Request — validation failed. Check error.code for specifics.
401 Unauthorized — missing or invalid API key.
403 Forbidden — authenticated but insufficient scope.
404 Not Found — resource does not exist or is not accessible to this key.
409 Conflict — CIDR overlap, duplicate hostname, or state transition conflict.
429 Too Many Requests — rate limit exceeded. Respect Retry-After.
500 Server Error — includes a request_id for support escalation.

Sandbox

Try the API without touching production.

The sandbox environment mirrors the production API with isolated data. Use it for integration testing, demos, and experimenting with the streaming agent interface.

sandbox base URL
https://sandbox.myomnitwin.com/v1

All sandbox keys are prefixed ot_sandbox_. Data resets every 24 hours.