Docs/Developer/Atlas API - Domains
DeveloperAPI

Atlas API - Domains

A domain is the unit Atlas models the world. It is a folder in Atlas's git repository holding one ontology.

Updated 29 Oct 2026Timebase Docs

Overview#

A domain is the unit Atlas models the world in. It is a folder in Atlas's git repository holding one ontology.jsonld entry point plus whatever instance maps sit beside it, and it is the thing every ontology, instance, query and compile call is addressed against. This article documents the domain lifecycle surface: creating a domain, listing and reading the catalog, deleting a domain, managing the shared libraries a domain imports, and compiling a domain into a queryable world.

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

Everything here lives under /api/v1. Base address, authentication, the RFC-9457 error envelope, the full route table, and the two failure shapes that catch people out (an unauthenticated call answered with an HTML sign-in page, and a retired route answered with 200 text/html instead of 404) are documented once in the Atlas API Overview. Read that first; this article does not restate it.

Four route groups make up this surface:

Group Route Branch addressing
Catalog /api/v1/domains optional ?branch= on list and delete
Imports /api/v1/branches/{branch}/domains/{domain}/imports mandatory {branch} path segment
World /api/v1/branches/{branch}/domains/{domain}/world mandatory {branch} path segment
World catalog /api/v1/worlds optional ?branch= filter

The /app/domains and /app/branches/{branch}/domains/{domain}/imports routes you will see in the Atlas single-page application's network traffic are the same MediatR handlers behind a private cookie-authenticated plane. They are excluded from the published API documents and are not an integration surface. Script against /api/v1.

Every call needs a bearer#

Pulse:Auth:Enabled is true by default, and every route in this article is registered with .RequireAtlasApiAuth(configuration). Send a Pulse-issued bearer whose aud claim includes Atlas's Pulse:Auth:ClientId (Atlas by default) on every request. Each curl example below includes it. See the Atlas API Overview for how the token is obtained and what an unauthenticated call actually returns.

The git commit author for every write on this surface is derived from the authenticated principal, falling back to Anonymous when authentication is disabled. There is no request field and no header for it:

  • createdBy in a create body is ignored, because it is not one of the accepted fields.

How branching works on this surface#

Domains live in a git repository, so every read is against some commit and every write is a commit. The three route groups address the branch differently, and the difference is load-bearing.

Call Branch parameter With no branch given
GET /api/v1/domains ?branch= (optional) Scans the live working tree at whatever the server has checked out as HEAD
POST /api/v1/domains branch in the request body (optional) Targets the server's current HEAD branch
GET /api/v1/domains/{domain} none accepted Always the working tree at HEAD
DELETE /api/v1/domains/{domain} ?branch= (optional) Targets the server's current HEAD branch
imports (all three verbs) {branch} path segment (mandatory) n/a
world (both verbs) {branch} path segment (mandatory) n/a

Two consequences worth designing around:

A bad branch name on the catalog list does not error. resolves ?branch= to a commit SHA and reads the pinned tree. If the branch cannot be resolved, or the data root is not a git repository, it falls back to the working-tree scan. A typo returns a plausible-looking wrong list, never a 404. On the imports read the same unresolvable branch maps to 404 Domain '<domain>' not found instead, because that handler has no separate branch error channel.

Branch names containing a slash must be percent-encoded. A branch such as feature/cool is sent as feature%2Fcool in the {branch} path segment. ASP.NET routing preserves the encoded slash in the route value, and the imports handlers call Uri.UnescapeDataString(branch) to recover it.

Important

the world endpoints do not perform that unescape. passes the raw route value straight into the command, so a %2F-encoded branch reaches git still encoded and fails to resolve.

Domain names#

The name you send is used verbatim as the folder name and as the name in every response. It is not normalised.

What is derived from it, lowercased with spaces turned into hyphens, is the domain's URN prefix written into the new ontology.jsonld. Sending Cooling System produces a folder literally named Cooling System with the prefix cooling-system and the URN base urn:atlas:ontology:cooling-system:. Sending cooling-system makes the two agree, which is what you want. Use lowercase, hyphen-separated names.

The only hard rule the writer enforces is that the name must be non-empty and must contain no character illegal in a file name; either failure is a 400.


List all domains#

Returns every domain visible on the requested branch, ordered by name.

Request

GET /api/v1/domains
GET /api/v1/domains?branch=feature/cool
Parameter In Required Description
branch query No Git branch to read from. Resolved to a commit SHA and read from that pinned tree. Omitted, unresolvable, or a non-repository root all fall back to the working tree at HEAD. Not percent-encoded here, it is an ordinary query value.
curl -H "Authorization: Bearer $ATLAS_TOKEN" \
  "https://<your-atlas-server>:4552/api/v1/domains?branch=main"

Response - 200 OK

[
  {
    "name": "brewing-operations",
    "hasOntology": true,
    "description": "Wort production and fermentation"
  },
  {
    "name": "packaging-line",
    "hasOntology": true
  }
]
Field Type Description
name string The domain identifier used in every other call. Equals the folder name.
hasOntology boolean Whether domains/{name}/ontology.jsonld exists. false is normal for a domain whose folder exists but whose entry point has not been written.
description string The rdfs:comment on the root owl:Ontology node. Omitted entirely when absent, as in packaging-line above, because the global serializer drops nulls.

There is no path field. Responses on this surface never carry a filesystem path, for privacy and portability. DomainInfo still holds Path internally, but the endpoint projects it out. A client reading path gets undefined.

This collection does not paginate. It returns every domain in one array.


Create a domain#

Creates the domain folder and writes a minimal ontology.jsonld into it, as a single git commit on the target branch.

Request

POST /api/v1/domains
Content-Type: application/json
{
  "name": "cooling-system",
  "branch": "feature/cool",
  "libraries": ["isa95-common"],
  "description": "Chilled water and glycol loops"
}
Field Type Required Description
name string Yes Domain identifier and folder name. Use lowercase, hyphen-separated words.
branch string No Target git branch. Defaults to the server's current HEAD branch. Send the real name here (feature/cool), not a percent-encoded one, since this is a body value.
libraries string[] No Library names imported into the new domain's @context, in the order given.
description string No Written as rdfs:comment on the root owl:Ontology node. Trimmed, and skipped entirely when blank.

Only name is required. createdBy is not a field. See "Every call needs a bearer" above.

curl -X POST \
  -H "Authorization: Bearer $ATLAS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"cooling-system","libraries":["isa95-common"],"description":"Chilled water and glycol loops"}' \
  "https://<your-atlas-server>:4552/api/v1/domains"

Response - 201 Created

{
  "name": "cooling-system",
  "hasOntology": false
}

Location: /api/v1/domains/cooling-system is set. hasOntology is hard-coded false in the create response even though the handler has just written ontology.jsonld; read the domain back if you need its real state.

What gets written#

The new domains/{name}/ontology.jsonld is:

{
  "@context": [
    "../../libraries/atlas-core.jsonld",
    "../../libraries/isa95-common.jsonld",
    { "cooling-system": "urn:atlas:ontology:cooling-system:" }
  ],
  "@graph": [
    {
      "@id": "urn:atlas:ontology:cooling-system",
      "@type": "owl:Ontology",
      "rdfs:label": "Cooling System Domain",
      "rdfs:comment": "Chilled water and glycol loops"
    }
  ]
}

Three things to note:

  • atlas-core is always imported first, whether or not you list it. It is de-duplicated, so listing it again does not write it twice.
  • Each entry in libraries becomes "../../libraries/{name}.jsonld", in the order you gave.
  • The library names are not validated at create time. Unlike the add-import call, a name that does not resolve to a library file is written into @context anyway, and the failure surfaces later as a compile error. Prefer creating bare and adding imports one at a time if you want the 404.

rdfs:label is a title-cased rendering of the name with hyphens turned into spaces.

Errors

Status Detail Cause
400 Domain name cannot be empty Blank name
400 Domain name contains invalid characters name contains a character illegal in a file name
400 Root directory is not a Git repository The Atlas data root is not a git repo
404 Branch '<branch>' not found The branch in the body does not exist. Atlas does not create it
409 Domain '<name>' already exists See below
Important

the duplicate check reads the working tree at HEAD, not the target branch. So creating x on branch feature/cool fails with 409 if x exists on HEAD but not on feature/cool, and succeeds if x exists on feature/cool but not on HEAD, overwriting that branch's ontology.jsonld. If you create on non-HEAD branches, check GET /api/v1/domains?branch=<target> first.


Get a single domain#

Request

GET /api/v1/domains/{domain}

This route accepts no branch parameter. It always reads the working tree at HEAD. To read a domain that exists only on another branch, use GET /api/v1/domains?branch=... and pick it out of the list.

curl -H "Authorization: Bearer $ATLAS_TOKEN" \
  "https://<your-atlas-server>:4552/api/v1/domains/brewing-operations"

Response - 200 OK

{
  "name": "brewing-operations",
  "hasOntology": true,
  "description": "Wort production and fermentation"
}

Same three fields as a list element, same null omission for description, and no path.

There is no message field, and there is no POST /api/sessions endpoint anywhere in Atlas. If you have a document telling you to start an editing session that way, it is describing something that never shipped. Editing is ordinary writes against the ontology and instance-map routes; the draft-session resource, when one exists, is /api/v1/branches/{branch}/draft.

Errors

Status Detail
404 Domain '<domain>' not found

Update a domain's description#

PUT /api/v1/domains/{domain}?branch={branch}

The description is the only editable piece of a domain's metadata.

{
  "description": "Bottling line 1, filler through palletiser."
}

The response echoes the description as persisted, which is trimmed, and null when you clear it by sending an empty value:

{
  "name": "juice-factory",
  "description": "Bottling line 1, filler through palletiser."
}

The name is not editable, deliberately. A domain's name is its identity: it derives the urn:atlas:ontology:{prefix} namespace that every entity URI in the domain is built from, the folder on disk, and the {domain} segment of every i3X element id. Renaming would break every external reference at once, so it is not offered here or anywhere else.

Delete a domain#

Hard-deletes the domain folder and everything under it, as one git commit on the target branch.

Request

DELETE /api/v1/domains/{domain}
DELETE /api/v1/domains/{domain}?branch=feature/cool
Parameter In Required Description
branch query No Target git branch. Defaults to the server's current HEAD branch.
curl -X DELETE \
  -H "Authorization: Bearer $ATLAS_TOKEN" \
  "https://<your-atlas-server>:4552/api/v1/domains/cooling-system?branch=main"

Response - 204 No Content

Existence is checked inside the branch's worktree rather than at HEAD, so a domain that lives only on a feature branch can be deleted on that branch. This differs from the create path's HEAD-scoped duplicate check.

The delete-blocked 409#

Before deleting, Atlas scans every other domain on the branch and inspects the @context of every *.jsonld beneath it, recursively. Any entry that resolves into domains/{target}/ blocks the delete, and the full blocker list is returned. Nothing is removed.

Response - 409 Conflict, application/problem+json

{
  "type": "https://atlas.timebase.io/errors/delete-blocked",
  "title": "Delete Blocked",
  "status": 409,
  "detail": "Cannot delete 'iso-base' — it is referenced by 1 domain.",
  "target": { "name": "iso-base", "kind": "domain" },
  "dependents": [
    {
      "id": "juice-factory",
      "name": "juice-factory",
      "kind": "domain",
      "referenceType": "domain-imports-domain"
    }
  ]
}

target and dependents are RFC-9457 extensions carried only by this problem type. The dependents array is never truncated. There is no code field here, so branch on the type URI: a plain name collision is .../errors/conflict while a blocked delete is .../errors/delete-blocked, and both are 409.

The detail string is emitted verbatim as shown, including its em dash, and pluralises to domains for counts other than one. Do not parse it; read dependents.

Domains do not currently import other domains in any shipped model, so in practice this scan finds nothing and deletes succeed. The check exists because the reference model allows domain-to-domain imports and the loader would follow them.

The open-draft-session 409#

A delete is also refused while the calling identity holds a draft session on the resolved branch. Creates, updates and imports are not: they go into the draft and merge on publish. A deletion has nothing to validate and no half-finished state, so it is never parked in a draft.

Response - 409 Conflict, application/problem+json

{
  "type": "https://atlas.timebase.io/errors/conflict",
  "title": "Conflict",
  "status": 409,
  "detail": "Refused 'Delete domain iso-base' on branch 'main': you have an unpublished draft ('draft/alice/main') on this branch, and a deletion cannot be parked in a draft. Publish the draft to promote its changes, or discard it (the Discard action beside Publish, or DELETE /api/v1/branches/main/draft), then delete again."
}

This one is not distinguishable by type. It carries the plain .../errors/conflict URI, the same as a name collision, with no code field and no extensions. On the delete routes the two plain-conflict cases are the draft refusal and nothing else, so on a DELETE a .../errors/conflict means an open draft. Call GET /api/v1/branches/{branch}/draft to confirm, rather than matching on the detail string.

The check runs inside the branch lock, so it cannot be raced by a concurrent save that creates a session. Another identity's draft never blocks you: only your own.

Errors

Status Detail
400 Domain name cannot be empty / Domain name contains invalid characters
404 Domain '<domain>' not found (absent on the requested branch)
404 Branch '<branch>' not found
409 delete-blocked, shape above
409 Refused '<message>' on branch '<branch>': you have an unpublished draft ('<draft>') on this branch, and a deletion cannot be parked in a draft.

There is no rename and no update. The group maps exactly four routes: GET "", POST "", GET "/{domain}", DELETE "/{domain}". No PUT, no PATCH, and no rename code path exists anywhere in the Atlas interface. To rename a domain, create the new one, re-point whatever referenced the old one, and delete the old one. Note that a PATCH or PUT to this group matches no route, so Atlas answers with the single-page application shell as 200 text/html rather than 405.


List library imports#

Returns the libraries a domain imports through its ontology.jsonld @context, on one branch.

Request

GET /api/v1/branches/{branch}/domains/{domain}/imports
curl -H "Authorization: Bearer $ATLAS_TOKEN" \
  "https://<your-atlas-server>:4552/api/v1/branches/main/domains/brewing-operations/imports"

Response - 200 OK

["atlas-core", "isa95-common"]

An array of short names. Every @context string entry containing libraries/ is reduced to its file name without the .jsonld extension. The domain's own prefix object is not a string entry, so it never appears.

atlas-core heads the list for every domain created by the current writer, because create always writes it first.

Unlike everything else on this surface, this read is fully branch-scoped: it resolves the branch to a SHA and reads the pinned tree, so a domain that exists only on a branch resolves correctly.

Errors

Status Detail Cause
404 Domain '<domain>' not found No .jsonld directly under domains/{domain} at that commit, or the branch could not be resolved, or the data root is not a git repository. All three collapse into this one answer

A domain that exists but has no ontology.jsonld returns 200 [], not an error.


Add a library import#

Inserts a library reference into the domain's @context and commits.

Request

POST /api/v1/branches/{branch}/domains/{domain}/imports
Content-Type: application/json
{ "library": "isa95-common" }
Field Type Required Description
library string Yes Short name of an existing library. Must resolve under the libraries directory.
curl -X POST \
  -H "Authorization: Bearer $ATLAS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"library":"isa95-common"}' \
  "https://<your-atlas-server>:4552/api/v1/branches/main/domains/brewing-operations/imports"

Response - 201 Created

{
  "domain": "brewing-operations",
  "library": "isa95-common"
}

No Location header on this one.

The entry is written as "../../libraries/{library}.jsonld" and inserted after the existing string entries but before the prefix object, so the @context ordering stays "libraries first, prefixes last". Use GET /api/v1/libraries (optional ?branch=) to see what library names exist.

Errors

Status Detail Cause
404 Domain '<domain>' not found Checked first, before the library
404 Library '<library>' not found No such library file
404 Domain '<domain>' has no ontology.jsonld The folder exists on the branch but the entry point does not
404 Branch '<branch>' not found
409 Library '<library>' already imported Re-importing is a conflict, not a no-op. Treat 409 here as success if you are retrying after a network failure
Important

the domain-existence pre-check on add and remove reads the working tree at HEAD, even though the route is branch-scoped and the read half is not. A domain that exists only on feature/cool returns 404 Domain '<domain>' not found from a POST to /api/v1/branches/feature%2Fcool/domains/{domain}/imports, while a GET on the identical path succeeds. If you need imports on a branch-only domain, merge it to HEAD first, or edit ontology.jsonld through the ontology routes.


Remove a library import#

Request

DELETE /api/v1/branches/{branch}/domains/{domain}/imports/{library}
curl -X DELETE \
  -H "Authorization: Bearer $ATLAS_TOKEN" \
  "https://<your-atlas-server>:4552/api/v1/branches/main/domains/brewing-operations/imports/isa95-common"

Response - 204 No Content

This call is idempotent in the loosest sense: the rewrite skips the matching @context entry and writes the file back, so removing a library that was never imported still commits and still returns 204. A 204 does not confirm that the import was there. Read the imports list back if you need to know.

Note

removing an import does not delete anything else. Types in the domain that extend or reference the library's types stay exactly where they are, in their own files, untouched. What changes is that their type URIs no longer resolve, which surfaces as validation violations the next time the domain is compiled. Removing an import is a schema change, not a cleanup.

Errors

Status Detail
404 Domain '<domain>' not found (HEAD-scoped, same warning as add)
404 Domain '<domain>' has no ontology.jsonld
404 Branch '<branch>' not found

Compile a domain#

PUT .../world is the compile trigger. A URL outside the API surface returns the single-page application shell as 200 text/html.

Request

PUT /api/v1/branches/{branch}/domains/{domain}/world

No request body.

curl -X PUT \
  -H "Authorization: Bearer $ATLAS_TOKEN" \
  "https://<your-atlas-server>:4552/api/v1/branches/main/domains/brewing-operations/world"

A compile is weighed against the licence before its world is published: if the compiled types or instances would take the installation over its Types or Instances entitlement, the call is refused with 403 Forbidden and an error code naming the entitlement, nothing is stored, and the previously cached world is untouched. Unload a domain you do not need (below) or model within the limit, then compile again. See Licensing and Entitlements.

Response - 200 OK

{
  "entityCount": 412,
  "edgeCount": 933,
  "archetypeCount": 27,
  "compiledAt": "2026-08-07T14:03:11.482Z",
  "functionErrors": []
}
Field Type Description
entityCount int Entity instances in the compiled world
edgeCount int A-Box edges
archetypeCount int Archetypes, that is T-Box classes
compiledAt date-time UTC timestamp of this compile
functionErrors array Functions whose body reported a compiler error. Each entry is { "functionUri": "...", "diagnostics": ["..."] }. The world still builds and the response is still 200

There is no warnings member and no tripleCount.

The call is idempotent in the useful sense: every call is a full recompile that replaces the cached world for that (branch, domain) pair.

Compilation aborts, it does not degrade. If validation finds any error-severity violation, no world is built at all. The offending types are not skipped; nothing is compiled.

Response - 422 Unprocessable Entity, application/problem+json

{
  "type": "https://atlas.timebase.io/errors/validation-failed",
  "title": "Validation failed",
  "status": 422,
  "detail": "3 violation(s) found in brewing-operations.",
  "violations": [
    {
      "ruleId": "O-B3",
      "severity": "Error",
      "filePath": "ontology.jsonld",
      "jsonPointer": "/@graph/7",
      "instanceId": null,
      "message": "...",
      "suggestion": "..."
    }
  ]
}

Each violation also carries occurrences: how many violations that one entry stands for. It is almost always 1, and larger only on a summary entry a rule emits in place of a fan-out it deliberately bounded. The count in detail is the sum of occurrences, not the length of the array, so a detail of "24431 violation(s) found" beside forty entries is correct. Sum occurrences if you total the list yourself.

The body is written by AtlasExceptionHandler.WriteValidationProblemAsync, which pins those eight violation fields. Rule IDs are O-A1 to O-A12 and O-B1 to O-B10 (there is no O-B5) for ontology rules, and I-A1 to I-A11 and I-B1 to I-B22 for instance rules. severity is the .NET enum name, so it is "Error" or "Warning" with a capital letter here, not camelCase like the rest of the surface. Only Error reaches a compile response; warnings never block and cannot appear in a 422.

Errors

Status Detail Cause
400 Root directory is not a Git repository
404 Domain '<domain>' not found No .jsonld directly under domains/{domain} at that branch's commit
404 Branch '<branch>' not found Includes the percent-encoded-slash case described earlier
422 validation failed, shape above
500 An internal error occurred. Any other compile failure. The real message is logged server-side and never returned

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


Read compile status#

Request

GET /api/v1/branches/{branch}/domains/{domain}/world

Reads the cached world statistics without recompiling.

curl -H "Authorization: Bearer $ATLAS_TOKEN" \
  "https://<your-atlas-server>:4552/api/v1/branches/main/domains/brewing-operations/world"

Response - 200 OK

{
  "entityCount": 412,
  "edgeCount": 933,
  "archetypeCount": 27,
  "compiledAt": "2026-08-07T14:03:11.482Z",
  "isStale": false,
  "compiledAtSha": "9f3c1ab...",
  "currentSha": "9f3c1ab..."
}
Field Type Description
isStale boolean true when the branch has advanced past compiledAtSha in a way that touches this domain's own files or one of its imported libraries. Computed fresh on every read from git object ids, never a stored bit
compiledAtSha string The commit the compile actually read. Blank for a world compiled before this was tracked, in which case isStale is always true
currentSha string The branch head at the moment of this read. Blank when the branch could not be resolved, and isStale then defaults to true

Note the shape difference from the PUT: this response has isStale, compiledAtSha and currentSha but no functionErrors. They are two different records (WorldStatus versus CompileResult).

isStale: true is the answer to "did my import take effect?". After adding or removing an import, GET .../world will report the world stale, because the staleness check fans out across the domain's imported libraries. Compile again to clear it.

Errors

Status Detail
404 Domain '<domain>' on branch '<branch>' has not been compiled. Use PUT .../world to build.

The 404 means "never compiled on this branch", not "no such domain". The cache is in-process and in-memory.


List compiled worlds#

The discovery endpoint: which (branch, domain) pairs are compiled and therefore queryable right now.

Request

GET /api/v1/worlds
GET /api/v1/worlds?branch=main&domain=brewing-operations
Parameter In Required Description
branch query No Case-sensitive exact match
domain query No Case-sensitive exact match
curl -H "Authorization: Bearer $ATLAS_TOKEN" \
  "https://<your-atlas-server>:4552/api/v1/worlds?branch=main"

Response - 200 OK

{
  "items": [
    {
      "branch": "main",
      "domain": "brewing-operations",
      "compiledAt": "2026-08-07T14:03:11.482Z",
      "entityCount": 412,
      "edgeCount": 933,
      "archetypeCount": 27,
      "isStale": false,
      "compiledAtSha": "9f3c1ab...",
      "currentSha": "9f3c1ab..."
    }
  ]
}

Note the envelope: an object with an items array, not a bare array like GET /api/v1/domains. A client that follows this list before querying will never hit Query.NotCompiled, because only compiled worlds appear. The filters are exact-match and case-sensitive, so a near-miss returns an empty items array rather than an error.


Unload a domain#

DELETE .../world removes a domain's compiled world from the cache. The domain stops answering queries and stops counting against the licensed Instances entitlement; its files are untouched, and a later PUT .../world rebuilds it.

Request

DELETE /api/v1/branches/{branch}/domains/{domain}/world
curl -X DELETE \
  -H "Authorization: Bearer $ATLAS_TOKEN" \
  "https://<your-atlas-server>:4552/api/v1/branches/main/domains/brewing-operations/world"

Response - 204 No Content, whether or not a world was cached. The call is idempotent on purpose: a teardown script never has to treat "already unloaded" as an error, and the machine plane does not say which of the two happened.

Deleting the domain, deleting the branch, or discarding a draft session that held a compiled world also evicts it, with the same effect on the licensed count.


Draft sessions and this surface#

Elsewhere in the Atlas API a save that fails validation returns 2xx while its content lands in the caller's private draft session and the target branch does not advance. The domain lifecycle writes are different, and the difference is intended.

Create, delete, add-import and remove-import all run through ExecuteInBranchAsync, which does raw file I/O and never runs the validation pipeline. They pass no validation outcome, so they can never produce an invalid verdict and therefore never create a draft session. A 201 or 204 from these routes means the commit is on the target branch, and no Atlas-Validation-Violations response header is stamped.

One exception, and it is the one to design around: if you already have a draft session on that branch from some earlier ontology save, these writes are routed into that existing draft worktree instead, and the target branch is not advanced. So a create can return 201 and still be invisible on the branch to everyone else. Check GET /api/v1/branches/{branch}/draft and publish with POST /api/v1/branches/{branch}/draft/publish.

Compile behaves the same way from the other side: when the caller is draft-routed, PUT .../world returns the full CompileResult but does not store the result in the shared world cache, so other callers keep reading the last compile of the real branch. Combined with the above, a caller who holds a draft session can create a domain, compile it, receive two success responses, and see no change on the branch and no change in anyone else's query results.


Troubleshooting#

Symptom Likely cause What to do
POST /api/v1/domains returns 409 A domain with that name exists on HEAD, which is what the duplicate check reads, even if you targeted another branch GET /api/v1/domains with no branch to see HEAD's catalog. Choose another name, or target the branch where the name is free and accept that the check is HEAD-scoped
The domain you created is not in GET /api/v1/domains You created it on a non-HEAD branch. The list defaults to the working tree at HEAD Add ?branch=<the branch you created on>
?branch= returns a list that looks like the wrong branch An unresolvable branch falls back to the HEAD working-tree scan, with no error Confirm the branch exists with GET /api/v1/branches. Treat a suspicious list as a possible typo, not as truth
hasOntology is false on a domain you just created The create response hard-codes false; it is not a live read Read the domain back with GET /api/v1/domains/{domain}
Your createdBy value never appears in git history createdBy is not a body field and is discarded on deserialization Authorship comes from the bearer token's principal. Authenticate as the identity you want recorded. With auth disabled everything is Anonymous
path is undefined in your client path is projected out of every response on this surface Address domains by name. If you need the on-disk layout, it is {data-root}/domains/{name}/ontology.jsonld
Imports calls 404 on a branch-only domain, but the GET on the same path works Add and remove pre-check the domain against HEAD; only the read is branch-scoped Merge the domain to HEAD, or make the change through the ontology routes
POST .../imports returns 409 on a retry Re-importing the same library is a conflict, not a no-op Treat 409 with detail Library '<x>' already imported as success for retry purposes
DELETE .../imports/{library} returned 204 and the import list is unchanged The call is idempotent, so 204 does not mean it was there Read the imports list back to confirm the intended state
DELETE /api/v1/domains/{domain} returns 409 and your handler reports "already exists" You are branching on the status code. A blocked delete and a name collision are both 409 Branch on the type URI: .../errors/delete-blocked carries target and dependents; .../errors/conflict does not
POST /api/v2/branches/main/domains/{domain}/compile returns 200 with an HTML body That URL is outside the API surface, and is answered by the single-page application shell Use PUT /api/v1/branches/{branch}/domains/{domain}/world
Library import added but the domain still does not resolve its types The compiled world is stale until you recompile GET .../world and check isStale, then PUT .../world
PUT .../world returns 403 with an error code naming Types or Instances The compiled world would take the installation over a licence ceiling Unload a domain you do not need compiled, or model within the limit, then compile again. See Licensing and Entitlements
PUT .../world returns 422 Compilation aborted on error-severity violations. No world was built, and the previously cached world is untouched Read violations[], fix the reported O-A* / O-B* / I-A* / I-B* rules, compile again. GET .../validation lists outstanding violations without compiling
PUT .../world returns 200 but functionErrors is not empty Function compilation errors are soft. The world built without those functions Fix each functionUri listed and recompile. Queries calling those functions will fail until you do
GET .../world returns 404 on a domain you know exists Nothing has been compiled for that (branch, domain) pair since the process started PUT .../world first
A branch with a / in its name 404s on the world routes but works on imports The imports handlers percent-decode the branch segment; the world handlers do not Compile on a slash-free branch, or through the SPA, until this is fixed. See the warning in "How branching works on this surface"
A create or delete returned 2xx but the branch did not move You hold a draft session on that branch, so the write went into your draft worktree GET /api/v1/branches/{branch}/draft, then POST /api/v1/branches/{branch}/draft/publish
You cannot find a rename or update endpoint There is none. The lifecycle is create plus delete Create the new domain, re-point references, delete the old one
esc
↑↓ navigate · ↵ open · ⌘K anywhere