Docs/Developer/Atlas API Overview
DeveloperReference

Atlas API Overview

The Atlas HTTP API: base address, authentication, the full route table, error format and pagination.

Updated 5 Oct 2026Timebase Docs

Overview#

Atlas exposes a versioned HTTP API that lets you read and change domains, libraries, ontology types, instance maps, compiled worlds, and query results without opening the Atlas user interface. This article is the map of that API: the base address, how to authenticate, the full list of route groups, the response and error formats, and the three failure shapes that confuse people most. Every other Atlas API article links back here for the route table and the version story.

This article is written for developers building an integration against Atlas.

The one thing to take away: the machine-facing API lives under /api/v1. If a script or code sample points at /api/v2/..., /api/domains, /api/libraries or /api/git/..., it is targeting URLs outside the API surface. Those do not return 404: Atlas answers with the single-page application shell as 200 text/html, so check the content type before parsing. See Troubleshooting at the end of this article.

Paths that are not part of the API#

Nothing answers on these. If a script or sample you have targets one, point it at the replacement.

Path family Use instead
/api/v2/branches/{branch}/... (the whole family) /api/v1/branches/{branch}/...
/api/domains, /api/domains/{domain}/imports /api/v1/domains, /api/v1/branches/{branch}/domains/{domain}/imports
/api/libraries /api/v1/libraries
/api/git/* /api/v1/branches, /api/v1/commits
/api/logs /api/v1/logs
/api/connectors /api/v1/connectors

Four resources were renamed at the same time. Correcting only the version prefix is not enough:

Old Current
.../nodes .../entity-types
.../relationships .../relationship-types
POST .../compile PUT .../world
POST .../query and POST .../query/filter POST .../queries (with an opt-in projections body flag)

The current families are listed below. There is no /api/v2, no /api/domains and no /api/libraries") registration left.

Base address and ports#

Atlas runs as a single container that serves the user interface and the API from the same process. The default listeners are:

Protocol Port Source
HTTP 4551 settings.config Kestrel:Endpoints:Http
HTTPS 4552 settings.config Kestrel:Endpoints:Https
https://<your-atlas-server>:4552

HTTPS redirection is active, so a plain HTTP call to 4551 is answered with a redirect to 4552. A plain HTTP call to http://<host>:4551/ returns 307 to https://<host>:4552/. The stock docker-compose.yml publishes only 4551:4551, so if you deploy with that file unchanged, the redirect target is not reachable from outside the container. Publish 4552 as well, or terminate TLS in front of Atlas.

Note

ASPNETCORE_URLS does not control these addresses. Kestrel:Endpoints in settings.config wins, and settings.config is loaded after environment variables, so the file beats the environment for Auth:*, Kestrel:*, Serilog:* and Pulse:*.

Authentication#

Atlas ships with authentication on. settings.config contains:

"Pulse": {
  "Host": "localhost",
  "Port": 4542,
  "UseTls": true,
  "Auth": {
    "Enabled": true,
    "ClientId": "Atlas",
    "ClientSecret": ""
  }
}

Every /api/v1 route group is gated by RequireAtlasApiAuth, which attaches the bearer-only ApiAccess policy whenever Pulse:Auth:Enabled=true. To call the API you need:

  1. A Pulse-issued bearer token (Pulse is the Timebase identity component that checks credentials and issues tokens), sent as Authorization: Bearer <token>.
  2. That token's aud (audience) claim must include the value of Atlas's Pulse:Auth:ClientId, which is Atlas by default. Your calling client's Pulse registration must list Atlas in its audiences.

With a valid bearer, a read such as GET /api/v1/domains returns 200 application/json.

Without a token you may get HTML, not a 401#

This is worth building into your client once:

On a deployment with authentication enabled, an unauthenticated GET /api/v1/domains returned:

302 Found
Location: https://<pulse-host>:4542/auth/authorize?client_id=Atlas&redirect_uri=.../callback&response_type=code&scope=openid profile

and, after following the redirect, an HTML sign-in page. Not 401, not application/problem+json. Any HTTP client configured to follow redirects therefore receives HTML where it expects JSON, and your JSON parser throws on the leading <. The same shape applies to /api/v1/branches and /api/v1.json.

The ApiAccess policy pins the JWT bearer scheme, so a missing or invalid token produces a 401 rather than a browser redirect. Some deployments answer with a 302 to the Pulse sign-in page instead, so write your client for both: always send a bearer, and turn off redirect-following so you see the status rather than a page of HTML.

Turning authentication off#

Setting Pulse:Auth:Enabled=false leaves the machine plane open, and named policies are then not attached at all. Use this only for local development. In open mode, writes are attributed to Anonymous rather than to a named identity.

The X-Atlas-Author header is gone#

Git commit authorship comes from the authenticated caller, defaulting to Anonymous when authentication is disabled.

The eight top-level surfaces#

Atlas maps eight top-level route families. Only the first is a general-purpose integration surface.

Surface Route prefix Credential What it is
Machine plane /api/v1 Pulse bearer The API this article documents. Use this for integrations.
UI plane /app Session cookie, admin role for most writes Private glue for the Atlas single-page application. Excluded from the published API documents. A bearer token does not clear it. Do not build against it.
Connector hub /hubs/connector Cookie or bearer SignalR hub that data-source connectors handshake into.
Value hub /hubs/v1/branches/{branch}/domains/{domain}/subscribe Cookie or bearer SignalR hub for live values, pinned to one branch and domain by the URL. This is how Bridge and Explorer read live values out of Atlas. It also streams type descriptions: a type's columns, endpoints or enum roster.
Log hub /hubs/logs Cookie or bearer SignalR live log tail. Its history twin is GET /api/v1/logs.
MCP /mcp Pulse bearer Model Context Protocol endpoint exposing whitelisted read and skill tools to external AI clients. See the Atlas MCP Server article.
i3X /i3x/v1 Pulse bearer, except GET /i3x/v1/info which is anonymous A standards-conformant interoperability read surface, 20 routes. It uses its own {success, result} / {success, results} envelope, not application/problem+json. The branch and domain are pinned by the I3x config section and never appear on the wire.
API documentation /api/help, /api/v1.json, /api/i3x.json See below Swagger UI and the machine-readable OpenAPI documents.

The three SignalR hubs share the HubAccess policy, the only Atlas policy that accepts either the session cookie or a bearer.

The /api/v1 route table#

Every machine-plane module is registered in one place, (the five ontology modules are the block). These are the route groups, with the resources in each.

Catalogs and repository (not branch-scoped in the path)#

Group Routes Source
Domains GET /api/v1/domains (optional ?branch=), POST /api/v1/domains, GET /api/v1/domains/{domain}, DELETE /api/v1/domains/{domain} (optional ?branch=)
Libraries GET, POST /api/v1/libraries, GET, DELETE /api/v1/libraries/{library}
Branches GET, POST /api/v1/branches, DELETE, PATCH /api/v1/branches/{branchName}, GET /api/v1/branches/{branchName}/commits, GET .../compare, POST .../merge
Commits GET /api/v1/commits/{sha}/files, GET /api/v1/commits/{sha}/diff
Connectors GET /api/v1/connectors
Worlds catalog GET /api/v1/worlds
Views GET, POST /api/v1/domains/{domain}/views, GET, PATCH, DELETE /api/v1/domains/{domain}/views/{id}
Logs GET /api/v1/logs?count=N

Branch-scoped resources#

All of these sit under /api/v1/branches/{branch}/....

Group Routes (relative to /api/v1/branches/{branch}) Source
Domain imports GET, POST /domains/{domain}/imports, DELETE /domains/{domain}/imports/{library}
Instance maps GET, POST /domains/{domain}/instance-maps, DELETE .../{fileName}, GET, POST .../{fileName}/instances, GET, PATCH, DELETE .../instances/{id}
World (compile) PUT /domains/{domain}/world compiles, GET /domains/{domain}/world reports status
Queries POST /domains/{domain}/queries
Instances GET /domains/{domain}/instances, .../instances/{instanceId}, .../{instanceId}/relationships, .../{instanceId}/values
Validation GET /domains/{domain}/validation, GET /libraries/{library}/validation
Draft sessions GET /draft, POST /draft/publish, DELETE /draft, POST /draft/refresh

Ontology resources (five kinds, domain and library scope)#

Each of the five ontology kinds is mapped twice, once for domains and once for libraries:

/api/v1/branches/{branch}/domains/{domain}/{kind}
/api/v1/branches/{branch}/libraries/{library}/{kind}
Kind Verbs Source
entity-types GET, GET /{name}, POST, PATCH /{name}, DELETE /{name}, plus PUT and DELETE /{name}/properties/{property} to attach and detach a property
relationship-types GET, GET /{name}, POST, PATCH /{name}, DELETE /{name}
properties GET, GET /{name}, POST, PATCH /{name}, DELETE /{name}
data-types GET, GET /{name}, POST, PUT /{name}, DELETE /{name}
functions GET, GET /{name}, POST, PUT /{name}, DELETE /{name}

The verb rule: entity types, relationship types, properties, instance-map instances and branch renames update with PATCH (partial merge). Data types and functions update with PUT (a full atomic re-emit of the whole definition). Sending PUT where PATCH is expected does not match a route, and Atlas answers with the single-page application shell as 200 text/html rather than 404 or 405.

data-types is a full ontology resource in its own right, including the isTimeAxis flag.

Request and response format#

  • Send Content-Type: application/json on every POST, PUT and PATCH body.
  • Responses use camelCase property names, omit null values entirely, and serialize enums as camelCase strings (the entity node kind becomes "entity"). These options are applied globally to /api/v1, /app/v1 and /i3x/v1. A strongly typed client that expects null members to be present, or expects integer enums, will break.
  • Response compression is applied to the public /api surface, excluding /api/help, plus the i3X surface. text/event-stream is never compressed so that streaming responses are not buffered.
  • 201 Created returns the created resource in the body. A Location header is present only on domain, library and branch creation. Ontology creates return 201 with no Location header, so do not build a client that blindly follows it.

The query body field is cypher, not query#

The most common first-call mistake against POST /api/v1/branches/{branch}/domains/{domain}/queries:

{
  "cypher": "MATCH (n:Plant) RETURN n.Label",
  "parameters": {},
  "projections": false
}

Sending {"query": "..."} returns 400 with the detail "Cypher query string is required.".

A successful response is {"columns": [...], "data": [[...]]}. Set "projections": true to get the enriched shape, which adds classesTouched and instanceIdsByClass; the latter carries durable instance URN strings, not integers.

Querying a domain that has never been compiled returns 400 with "code": "Query.NotCompiled", not 404. Compile with PUT .../world first.

Pagination#

Collections paginate, and one of them applies a default page size when you omit limit.

Collection Parameters Default
GET /api/v1/branches/{branchName}/commits limit (1 to 100), cursor (opaque base64url) limit=10 when omitted or out of range. The response carries nextCursor; null means the end of history.
GET .../domains/{domain}/instances page, pageSize, plus type and roots filters

If you request commit history without a limit, you get ten commits and no error. Page with nextCursor until it comes back null.

Errors#

Every /api/v1 failure returns RFC-9457 Problem Details as application/problem+json. There is no error field and no message field on the current surface, and responses carry no filesystem path.

{
  "type": "https://atlas.timebase.io/errors/not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "Domain 'my-domain' not found",
  "traceId": "00-…"
}

The type URIs are frozen once emitted, so branch your client on the type token, never on the human-readable title.

Status Type URI Meaning
400 https://atlas.timebase.io/errors/invalid-request Bad input, schema mismatch, Cypher syntax error, uncompiled domain
401 https://atlas.timebase.io/errors/unauthorized Not authenticated
403 https://atlas.timebase.io/errors/forbidden Authenticated but not permitted
404 https://atlas.timebase.io/errors/not-found Resource does not exist
409 https://atlas.timebase.io/errors/conflict General state conflict, including a name already in use
409 https://atlas.timebase.io/errors/delete-blocked Delete refused because dependents reference the target. Adds target and dependents extensions
409 https://atlas.timebase.io/errors/publish-conflict A draft publish could not merge. Adds a conflictFiles extension
422 https://atlas.timebase.io/errors/validation-failed Compile or persistence validation failed. Adds a violations[] array
500 https://atlas.timebase.io/errors/internal-error Always the generic detail "An internal error occurred." No stack traces are ever returned

Successful responses are 200 OK for reads and updates, 201 Created for creates, and 204 No Content for deletes.

There is no universal code field#

Write your error handler to branch on the type URI first. The generic error path emits type, title, status and detail, plus the traceId the framework adds, and carries no code extension.

A code extension appears only on specific problems:

  • Query errors, where code carries the stable error key such as Query.NotCompiled.
  • Draft.NothingToPublish.
  • Delete-blocked 409s, which add target and dependents rather than code.

Write your client to branch on the type URI, and read code only where the specific problem documents it.

Writes, draft sessions and compilation#

Two behaviours change what a successful write means. Both are on by default.

A save that fails validation still returns 2xx. Draft sessions are enabled by default (Atlas:DraftSessions:Enabled). When an ontology save carries outstanding error-severity violations, the content lands in the caller's private draft session and the target branch is not advanced. The only signal is the Atlas-Validation-Violations response header, which carries the count of outstanding violations. Check that header on every write. To promote the draft onto the branch, call POST /api/v1/branches/{branch}/draft/publish.

Compilation validates the whole domain before it builds. PUT /api/v1/branches/{branch}/domains/{domain}/world returns 422 with a violations[] array while any error-severity violation is outstanding, and builds the world once the last one is cleared. On success it returns CompileResult {entityCount, edgeCount, archetypeCount, compiledAt, functionErrors[]}. GET .../world reports isStale, compiledAtSha and currentSha without recompiling. There is no warnings member and no tripleCount; read warnings from GET .../validation.

So a 422 is the compile response; a save with outstanding violations returns 2xx and is held in your draft session.

Note

when the caller is draft-routed, the compile builds your copy and leaves the shared world in place. Combined with the draft-routed save above, you can receive two successful responses and see no change on the branch. If a write and a compile both succeed but nothing moves, check Atlas-Validation-Violations and GET /api/v1/branches/{branch}/draft.

Atlas recompiles every recorded branch and domain pair at startup, so a manual recompile after a restart is normally unnecessary.

Browse the live API reference#

Atlas serves Swagger UI at:

https://<your-atlas-server>:4552/api/help

with machine-readable OpenAPI documents at /api/v1.json and /api/i3x.json. The documents are selected by URL prefix: v1 covers /api/v1/, i3x covers /i3x/v1/.

The v1 and i3X documents are served on every deployment, including Docker, so /api/help is available in production. The third document, covering the internal /app/v1 routes, is served in Development only.

Note

on a deployment with authentication enabled, /api/help may redirect to the Pulse sign-in page. Sign in first, or read the machine-readable documents at /api/v1.json and /api/i3x.json with a bearer.

Troubleshooting#

Symptom Cause What to do
The API returns HTML and your JSON parser throws on the first character < Either you did not send a bearer token and the request was redirected to the Pulse sign-in page, or the path matched no route and Atlas served the single-page application shell (see the next two rows) Print the status code and the Content-Type before parsing. text/html means one of these two causes, never a real API response
You get 302 instead of 401 on /api/v1/... The request has no bearer token, and the deployment challenges through the browser sign-in flow rather than returning a JSON 401 Send Authorization: Bearer <token> from Pulse, with Atlas in the token's aud. Disable redirect-following in your HTTP client so you see the 302 rather than a sign-in page
You get 200 instead of 404 for a path you expect to be wrong Atlas serves its single-page application for any URL outside the API surface, so GET .../domains/lonestar/nodes returns 200 text/html Check Content-Type: application/json rather than the status. Then check the route against the table above - the ontology resources are entity-types and relationship-types
Calls to /api/v2/..., /api/domains, /api/libraries or /api/git/... return 200 with an HTML body Those URLs are outside the API surface and are answered by the fallback above Move to /api/v1. See the retirement table at the top of this article
401 on /app/... even though your bearer works on /api/v1 /app is the private browser plane and accepts the session cookie only. A bearer credential does not clear it Do not integrate against /app. Use the /api/v1 twin of the same resource
POST .../queries returns 400 "Cypher query string is required." The body field is cypher. query is ignored Rename the field to cypher
POST .../queries returns 400 with "code": "Query.NotCompiled" The domain has never been compiled on that branch Call PUT /api/v1/branches/{branch}/domains/{domain}/world, then retry
A write returned 2xx but the change is not on the branch The save carried validation violations and was routed to your private draft session; the branch did not advance Check the Atlas-Validation-Violations response header, then GET /api/v1/branches/{branch}/draft and publish with POST /api/v1/branches/{branch}/draft/publish
PUT .../world returns 422 Compilation aborted on error-severity violations, and no world was built Read the violations[] array in the problem body, fix the reported rules (O-A*, O-B*, I-A*, I-B*), then compile again
Commit history looks short GET /api/v1/branches/{branchName}/commits returns 10 entries when no limit is given Pass limit (up to 100) and follow nextCursor until it returns null
DELETE returns 409 and your handler reports "name already exists" A blocked delete uses the distinct type .../errors/delete-blocked and carries target and dependents Branch on the type URI rather than the status code, and show the dependents list to the user
Your error handler reads body.code and gets undefined The generic problem path emits only type, title, status, detail and traceId Branch on type. Read code only for query errors and Draft.NothingToPublish
Connection refused, or a redirect to a port that does not answer HTTPS redirection sends 4551 traffic to 4552, and the stock compose file publishes only 4551 Publish 4552, or terminate TLS in front of Atlas. Confirm both ports in your firewall and container port mappings
An i3X client cannot read your error details /i3x/v1 uses the i3X {success, result} envelope, not application/problem+json Handle the two surfaces with separate error parsers
esc
↑↓ navigate · ↵ open · ⌘K anywhere