Atlas API - Instance Maps
The API for authoring instance-map definitions: the shapes Atlas accepts, property strategies, and what a compile expands them into.
Overview#
The instance-map surface is the machine-plane API for authoring the A-Box of a domain: the definitions that expand into the concrete instances your queries return. It is organised in two levels. A domain owns a set of .map.json files, and each file holds a list of definitions. You create and delete files, and you create, read, update and delete definitions inside them.
This article is the API reference: routes, verbs, request bodies, response bodies and status codes. It is written for developers building an integration against Atlas.
Three companion articles own material this one does not repeat:
- Atlas API Overview - base address, ports, authentication, the RFC-9457 error envelope, pagination, draft sessions and the retirement of
/api/v2. Read it first. - Instance Maps - the concept model: the three expansion shapes,
[Name]placeholders, variables, transforms, relationships, compile behaviour. - Property Sourcing Strategies - Static, Bound, and Calculated - what each strategy means and the exact format of a
bindingPath.
The one thing to take away: almost every field name and route shape in circulation for this surface is out of date. Update is PATCH, the by-id routes carry no {fileName}, instanceBinding and relationships[] no longer exist, the strategy discriminator is type, and property keys must be full urn:atlas:ontology: URIs. Worse, most of those mistakes do not produce an error. See The unknown-property asymmetry and Troubleshooting.
Base path and authentication#
https://<your-atlas-server>:4552/api/v1/branches/{branch}/domains/{domain}/instance-maps
{branch} is a Git branch name, usually main. {domain} is the domain name. Instance maps are domain-scoped only: there is no library half, because A-Box data never lives in a shared library.
Every route in this article is registered under RequireAtlasApiAuth, so a Pulse-issued bearer token is required whenever Pulse:Auth:Enabled is true, which is the shipped default. Every example below sends one. See Atlas API Overview for how to obtain the token and why an unauthenticated call may return an HTML sign-in page rather than a 401.
Git commit authorship comes from the authenticated caller, falling back to Anonymous when authentication is disabled.
There is a parallel /app/branches/{branch}/domains/{domain}/instance-maps group with the same five route shapes. That is the private cookie-authenticated glue for the Atlas single-page application, not an integration surface. Do not build against it.
Route table#
Relative to /api/v1/branches/{branch}/domains/{domain}:
| Verb | Route | Purpose | Success |
|---|---|---|---|
GET |
/instance-maps/ |
List file stems | 200 |
POST |
/instance-maps/ |
Create an empty file | 201 |
DELETE |
/instance-maps/{fileName} |
Delete a file and every definition in it | 204 |
GET |
/instance-maps/{fileName}/instances |
List definitions in one file | 200 |
POST |
/instance-maps/{fileName}/instances |
Add a definition to a file | 201 |
GET |
/instance-maps/instances/{id} |
Read one definition | 200 |
PATCH |
/instance-maps/instances/{id} |
Partially update one definition | 200 |
DELETE |
/instance-maps/instances/{id} |
Delete one definition | 204 |
GET |
/instances |
List compiled instances | 200 |
GET |
/instances/{instanceId} |
Read one compiled instance | 200 |
GET |
/instances/{instanceId}/relationships |
Edges touching one compiled instance | 200 |
GET |
/instances/{instanceId}/values |
Current property values for one instance | 200 |
A relationship's two endpoints come back from this route as columns, named
atlas:Sourceandatlas:Target. See Atlas Value Hub.
An instance map names a connector by its alias. The interface shows the alias rather than the minted
con_id, which is the same identifier a binding path is written against, so what you read in a map and what you type into a path now match. The id still resolves wherever an alias does.
The first eight are authoring routes and read from the Git branch. The last four are read-only projections of the compiled world and are covered in Read compiled instances.
Instance map files#
List files#
Returns a bare JSON array of file stems, sorted, with the .map.json suffix removed.
GET /api/v1/branches/main/domains/brewing/instance-maps/
Authorization: Bearer <token>
200 OK
["fermentation-area", "line-a", "utilities"]
Create a file#
POST /api/v1/branches/main/domains/brewing/instance-maps/
Authorization: Bearer <token>
Content-Type: application/json
{ "name": "line-b" }
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | The file stem. Atlas strips a trailing .map.json if you include it, and strips any directory part, so line-b, line-b.map.json and instances/line-b.map.json all produce the same file |
201 Created
{ "name": "line-b" }
The response echoes only the name. There is no Location header on this surface: Location is emitted only on domain, library and branch creation.
409 Conflict when the stem already exists. Matching is case-insensitive, so Line-B collides with line-b.
{
"type": "https://atlas.timebase.io/errors/conflict",
"title": "Conflict",
"status": 409,
"detail": "Instance map file 'line-b' already exists"
}
Lowercase hyphen-separated stems are a convention, not a rule. Nothing enforces a character class. The one rule that bites is case-insensitive uniqueness.
Delete a file#
Removes the file and every definition in it. Git history retains the content, but there is no API route to restore it.
DELETE /api/v1/branches/main/domains/brewing/instance-maps/line-b
Authorization: Bearer <token>
204 No Content. 404 Not Found when the stem does not exist.
Instance map definitions#
A definition is a template, not a single entity. Depending on what it declares, one definition expands into exactly one instance, a fixed set of instances, or one instance per row a connector returns. The three shapes and the rules that select between them are covered in Instance Maps; this article covers the wire shape only.
List the definitions in a file#
GET /api/v1/branches/main/domains/brewing/instance-maps/line-a/instances
Authorization: Bearer <token>
200 OK with a JSON array of raw definitions, exactly as stored. There is no read projection: what you get back is what is in the file. 404 Not Found when the file does not exist.
Read one definition#
GET /api/v1/branches/main/domains/brewing/instance-maps/instances/line-a-pumps
Authorization: Bearer <token>
200 OK with the raw definition. 404 Not Found when no file in the domain contains that id.
Note the route shape: /instance-maps/instances/{id}, with no file segment. This route wins precedence over /instance-maps/{fileName}/instances, which is exercised by the shipped HTTP tests.
Add a definition#
POST /api/v1/branches/main/domains/brewing/instance-maps/line-a/instances
Authorization: Bearer <token>
Content-Type: application/json
{
"id": "line-a-pumps",
"name": "Line A Transfer Pumps",
"type": "urn:atlas:ontology:brewing:Pump",
"instanceId": "urn:atlas:instance:brewing:pump-[Num, 00]",
"label": "Transfer Pump [Num, 00]",
"description": "Transfer pumps on Line A",
"map": [
{ "name": "Num", "data": "1-4", "wrap": false }
],
"properties": {
"urn:atlas:ontology:brewing:nominalCapacity": [
{ "type": "StaticStrategy", "value": 250.0 }
],
"urn:atlas:ontology:brewing:speed": [
{ "type": "BoundStrategy",
"bindingPath": "historian-juice-factory/The Juice Factory/131-PT-[Num, 00].PV" }
]
}
}
201 Created with the created definition as stored.
| Status | When |
|---|---|
404 |
The {fileName} does not exist. Create the file first |
409 |
A definition with that id already exists anywhere in the domain, not just in this file |
Request body fields
The accepted set is exactly id, name, type, instanceId, description, sources, map, properties, label, source, target. Nothing else is read.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Identifier of the definition. Must be unique across every .map.json file in the domain. Immutable once created: PATCH cannot change it |
name |
string | Yes | Metadata about the definition, for example Line A Transfer Pumps. This is not the per-instance display name. See label |
type |
string | Yes | The full URI of the entity type or relationship archetype, for example urn:atlas:ontology:brewing:Pump. A short local name such as "Pump" is stored verbatim, then fails validation rule I-B1 and never compiles |
instanceId |
string | Yes | The identity template for each expanded instance. Supports [Name] placeholders and format specifiers such as [Num, 00]. The interpolated result becomes the instance identity verbatim: no namespace is prepended |
label |
string | No | Per-instance display-name template, same placeholder syntax as instanceId. Omit it and instances display their identity's local name |
description |
string | No | Free text |
sources |
object | No | Named connector rowset declarations, keyed by your own source name. See Rowset sources |
map |
array | No | The named variables that instanceId, label, binding paths and static values interpolate. See The map array |
properties |
object | No | Full property URI to a list of strategy entries. See Property strategies |
source |
string | No | Relationship only. Instance-URN template of the source endpoint. See Relationships |
target |
string | No | Relationship only. Instance-URN template of the target endpoint |
id, name, type and instanceId are positional constructor parameters on the request record, so they are marked required in the OpenAPI document. Persisted-file validation (I-A2) is narrower: it enforces only id, type and instanceId. If you are reading a validation report and cannot find a rule for a missing name, that is why.
Fields that no longer exist
| Superseded field | Replacement |
|---|---|
instanceBinding |
sources, a keyed map of { bindingPath, transforms } |
relationships[] |
A separate definition whose type is a relationship archetype, with top-level source and target |
Bindings, BindKey |
properties, keyed by full URI, holding bindingPath on a BoundStrategy entry |
Both instanceBinding and relationships[] were removed from the model. Read the next section before you assume a 201 means they worked.
The unknown-property asymmetry#
This is the single most dangerous behaviour on this surface, and it is worth stating precisely because the two halves of Atlas disagree.
- On disk,
InstanceMapStoreparses.map.jsonwithUnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow. One unrecognised property name fails the parse, and the failure is reported against the whole file as ruleI-A1, not against the one bad definition. A single staleinstanceBindingtakes out every definition in that file. - Over the API, the request bodies are bound with the global Atlas wire options, which do not set
UnmappedMemberHandling. The default applies, so an unrecognised JSON property is ignored.
So a POST carrying the relationships[] array returns 201 Created, persists a definition with no endpoints, and creates no edges when you compile. You get a green light and an empty graph. The same is true of instanceBinding: accepted, dropped, never applied.
Nothing in the response tells you a property was discarded. The only reliable check is to GET the definition back and compare it with what you sent.
a round trip does not confirm the file either. A definition written through the API without an unrecognised property lands on disk without it, so the file parses cleanly. The failure is silent at every layer.
The map array#
map declares the named variables the rest of the definition interpolates. Each entry:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | The variable name, referenced as [name] |
data |
string | Yes | The value expression. In singleton and static-template mode this is the count-encoded grammar (North, a, b, c, 1-5, 01-05, 0-8/2, High*3, Low*1). In rowset mode it is a [sourceName.columnName] reference |
wrap |
boolean | No | Default false. When a shorter variable runs out of values, false repeats its last value and true cycles from the beginning |
Two rules that decide how many instances you get, both covered in full in Instance Maps:
Expansion is linear over the longest map entry, not a Cartesian product. The entry producing the most values sets the instance count; every other entry is read at the same index. Two entries of three values each produce three instances, not nine.
The placeholder syntax is [Name]. It is not {i} and it is not $-prefixed. {i} is not recognised, so it is left in the text exactly as typed, the definition has no variables at all, and you get one instance whose identity literally contains {i}. Nothing warns you.
Every declared variable must be referenced by something (instanceId, label, a property value, a binding path or a relationship endpoint), or the save reports rule I-A8.
Rowset sources#
sources replaces instanceBinding. It is a map from a source name you choose to a declaration with a connector bindingPath and an optional transforms map. Each transform is a regular expression with named capture groups, evaluated against one column of each row; captures become new columns, and a row whose regex does not match is dropped.
Taken from the shipped golden fixture:
{
"id": "bound-machines",
"name": "Bound Test Machines",
"type": "urn:atlas:ontology:juice-factory:Machine",
"instanceId": "BoundMachine-[Num, 00]",
"sources": {
"fillers": {
"bindingPath": "historian-juice-factory/The Juice Factory/FillerTemps",
"transforms": {
"name": { "regex": "(?<Area>\\d+)\\-(?<Instrument>\\w+)\\-(?<FillerNumber>\\d+)\\.PV" }
}
}
},
"map": [
{ "name": "Num", "data": "[fillers.FillerNumber]", "wrap": false },
{ "name": "Tag", "data": "[fillers.name]", "wrap": false }
],
"properties": {
"urn:atlas:ontology:juice-factory:temperature": [
{ "type": "BoundStrategy",
"bindingPath": "historian-juice-factory/The Juice Factory/[Tag]" }
]
}
}
A definition may declare several sources, but its map entries may reference only one of them. Referencing two is rejected.
A sources[].bindingPath is validated when you save (rule I-B10, resolved through the connector registry). A BoundStrategy.bindingPath in properties is not. That asymmetry is the reason a rowset that produces nothing usually tells you, while a property that binds nothing never does. See Property Sourcing Strategies.
Relationships#
A relationship is not a sub-list inside an entity definition. It is an ordinary instance map whose type is a relationship archetype and which carries top-level source and target.
POST /api/v1/branches/main/domains/brewing/instance-maps/topology/instances
Authorization: Bearer <token>
Content-Type: application/json
{
"id": "feeds-pump01-tank01",
"name": "Feeds-Pump01-Tank01",
"type": "urn:atlas:ontology:juice-factory:Feeds",
"instanceId": "urn:atlas:instance:juice-factory:feeds-pump01-tank01",
"source": "urn:atlas:instance:juice-factory:pump-01",
"target": "urn:atlas:instance:juice-factory:tank-01",
"properties": {
"urn:atlas:ontology:juice-factory:flowRate": [
{ "type": "StaticStrategy", "value": 2.5 }
]
}
}
Four rules govern the two endpoint fields:
- Both or neither. A map that declares either endpoint must declare both. Rule
I-A9reportsA relationship instance map must declare both 'source' and 'target'; 'target' is missing.An entity map declares neither. - They carry instance identities, not definition ids. Each endpoint is matched against the set of expanded instance identities across the whole domain. Putting a definition
idthere produces a dangling-endpoint violation (I-B6). - They are templates. Both accept the same
[Name]placeholder syntax asinstanceIdand are resolved once per expanded row, so one definition can create many edges. Because resolution runs over the full instance set, the two ends may come from entirely different definitions and different files. That decoupling is the point. - Edge properties are ordinary properties. A relationship's
propertiesblock works exactly like an entity's. There is no separate edge-property surface, and the inline edge sub-resource routes were retired withrelationships[].
Rule I-B5 additionally requires the type to be a relationship archetype, and I-B7 checks each resolved endpoint against the archetype's source and target type constraints.
Property strategies#
properties maps a full property URI to a list of strategy entries. Both halves matter.
The key must be a full URI. A short name is accepted by the POST, then flagged by rule I-A4 and dropped at compile:
Property key 'speed' is not a full URI. Property keys must start with 'urn:atlas:ontology:' (short names are dropped at compile).
There is one URN namespace for all five ontology kinds: urn:atlas:ontology:{scope}:{LocalName}. There is no urn:atlas:fn:, no urn:atlas:prop:, no urn:atlas:class: and no urn:atlas:rel:. A functionUri and a propertyUri both use the ontology namespace. Instance identities use the separate urn:atlas:instance: base.
The value is a list because strategies are time-intervalled. Each entry may carry validFrom; when omitted the entry applies from the beginning of time. If a property only ever has one source, the list has one entry.
The discriminator is type, not strategy, and there are six legal values:
type |
Required fields | What it does |
|---|---|---|
StaticStrategy |
value |
A fixed value. May be a string, number, boolean, or a nested object for a compound type. Supports [Name] interpolation, including inside object leaves |
BoundStrategy |
bindingPath |
Live values from a connector |
CalculatedStrategy |
functionUri, arguments |
Computed by an ontology function |
NullStrategy |
none | Explicitly unset |
CompositeStrategy |
members |
Composed strategy for a data-type property. members is keyed by the full property URI of each field and holds the same shape as properties, so it nests: a data-type field can carry its own CompositeStrategy. Another instance of the same type may source the whole value with one strategy |
ReferenceStrategy |
instanceUri, propertyUri |
A live alias of another instance's property. No copy, no edge |
Anything else is rejected by rule I-A5:
Strategy type 'Manual' is not recognised. Allowed values: StaticStrategy, BoundStrategy, CalculatedStrategy, NullStrategy, CompositeStrategy, ReferenceStrategy.
There is no Manual strategy. The place to type a value in by hand is StaticStrategy.
Full strategy entry shape:
| Field | Type | Applies to |
|---|---|---|
type |
string | All |
validFrom |
string | All. ISO 8601 instant; omitted means "from the beginning" |
value |
any | StaticStrategy |
bindingPath |
string | BoundStrategy. Slash-delimited, starting from the connector instance id. Never a scheme:// URI |
functionUri |
string | CalculatedStrategy |
arguments |
array | CalculatedStrategy |
members |
object | CompositeStrategy |
instanceUri |
string | ReferenceStrategy |
propertyUri |
string | ReferenceStrategy |
A CalculatedStrategy argument entry has type ("literal" or "propertyRef"), parameter, order, plus value for a literal or propertyUri for a property reference. A propertyRef may also carry instanceUri to read the input from another instance, which is how you aggregate across instances. parameter accepts either the bare parameter name or the full parameter URI; bare names are rejected only when ambiguous.
"urn:atlas:ontology:juice-factory:temperatureCelsius": [
{
"type": "CalculatedStrategy",
"functionUri": "urn:atlas:ontology:juice-factory:FahrenheitToCelsius",
"arguments": [
{ "type": "propertyRef",
"parameter": "urn:atlas:ontology:juice-factory:param/fahrenheit",
"propertyUri": "urn:atlas:ontology:juice-factory:temperature",
"order": 0 }
]
}
]
For what each strategy means in practice and the binding-path format per connector, see Property Sourcing Strategies.
Update a definition#
Update is PATCH, not PUT. A PUT matches no route and is answered with the single-page application shell as 200 text/html.
PATCH /api/v1/branches/main/domains/brewing/instance-maps/instances/line-a-pumps
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "Line A Transfer Pumps (rev B)",
"description": "Motor replaced 2026-04"
}
200 OK with the merged definition. 404 Not Found when no file contains that id.
The merge rule is: an omitted field is left unchanged. This is what makes clearing a collection impossible without the typed clear array below. Note also that id is not on the update body at all: it cannot be changed by PATCH.
Accepted fields: name, description, type, instanceId, sources, map, properties, label, source, target, clear.
The typed clear array
clear names the fields to erase. A field named here is cleared even when its value member is absent or null. Token matching is case-insensitive.
| Token | Erases |
|---|---|
"sources" |
The whole sources map |
"map" |
The whole map array |
"properties" |
The whole properties map |
"label" |
The per-instance label template |
"source" |
The relationship source endpoint |
"target" |
The relationship target endpoint |
PATCH /api/v1/branches/main/domains/brewing/instance-maps/instances/feeds-pump01-tank01
Authorization: Bearer <token>
Content-Type: application/json
{ "clear": ["source", "target"] }
200 OK, and both endpoints are erased.
Sending {"sources": {}} is not an erase. An empty collection on the wire is indistinguishable from "unchanged" in the merge, which is exactly why the typed clear exists. Use clear.
"relationships" is not a clear token on the current surface and is ignored.
Delete a definition#
DELETE /api/v1/branches/main/domains/brewing/instance-maps/instances/line-a-pumps
Authorization: Bearer <token>
204 No Content. 404 Not Found when no file contains that id.
There is no dependents guard on this delete: nothing in the ontology references an instance map by id. A relationship definition whose endpoint you have just orphaned will surface as an I-B6 violation at the next save or compile, not as a 409 here.
Read compiled instances#
These four routes read the compiled world, not the branch. They return nothing until the domain has been compiled with PUT /api/v1/branches/{branch}/domains/{domain}/world. An uncompiled domain returns 400, not 404:
{
"type": "https://atlas.timebase.io/errors/invalid-request",
"title": "Invalid Request",
"status": 400,
"detail": "Domain 'brewing' on branch 'main' has not been compiled."
}
The {instanceId} path segment is the durable instance URN, percent-encoded. urn:atlas:instance:plant:pump-01 becomes urn%3Aatlas%3Ainstance%3Aplant%3Apump-01.
List instances#
GET /api/v1/branches/main/domains/brewing/instances?type=urn:atlas:ontology:brewing:Pump&page=1&pageSize=100
Authorization: Bearer <token>
| Parameter | Description |
|---|---|
type |
Full type URI. Includes subclasses, expanded by breadth-first walk over rdfs:subClassOf |
roots |
true restricts to instances with no incoming A-Box edges |
page |
1-based. Defaults to 1 |
pageSize |
Defaults to 100 |
200 OK
{
"items": [
{ "elementId": "urn:atlas:instance:brewing:pump-01",
"typeUri": "urn:atlas:ontology:brewing:Pump",
"label": "pump-01" }
],
"totalCount": 4,
"page": 1,
"pageSize": 100
}
label here is derived from the last colon-separated segment of elementId, and is null when the identity does not follow that convention. It is not the label template from the instance map definition.
Read one instance#
GET /api/v1/branches/main/domains/brewing/instances/urn%3Aatlas%3Ainstance%3Abrewing%3Apump-01
Authorization: Bearer <token>
200 OK with a single {elementId, typeUri, label} record. 404 when the instance is not in the compiled world. 400 when the domain is not compiled.
Read an instance's relationships#
GET /api/v1/branches/main/domains/brewing/instances/urn%3A…%3Apump-01/relationships?direction=both
Authorization: Bearer <token>
direction accepts outgoing, incoming or both; omitted means both.
200 OK
{
"items": [
{ "edgeElementId": "urn:atlas:instance:brewing:feeds-pump01-tank01",
"relationshipTypeUri": "urn:atlas:ontology:brewing:Feeds",
"relationshipTypeLabel": "Feeds",
"sourceElementId": "urn:atlas:instance:brewing:pump-01",
"targetElementId": "urn:atlas:instance:brewing:tank-01",
"direction": "outgoing" }
]
}
edgeElementId is populated when edges are reified in the compiled world, and is null otherwise. This is a structural traversal: edge property values are not included here. Read them by querying the relationship instance itself.
Read an instance's current values#
GET /api/v1/branches/main/domains/brewing/instances/urn%3A…%3Apump-01/values
Authorization: Bearer <token>
200 OK
{
"items": [
{ "propertyUri": "urn:atlas:ontology:brewing:nominalCapacity",
"value": 250.0,
"quality": "Good" }
]
}
This is a pull-read snapshot. For a live feed, subscribe to the value hub at /hubs/v1/branches/{branch}/domains/{domain}/subscribe.
quality is one of Good, GoodNoData, Uncertain, Bad. In the shipped implementation a property that holds a value maps to Good and one that holds no value to GoodNoData, with "value": null. No value means nothing has arrived from the source yet, the static value is blank, or a calculation returned none. A timestamp member exists on the record but is omitted from the response when null, and null responses are stripped by the global wire options.
These endpoints are read-only. There is no route to write an instance value. To change a value you change the strategy in the instance map definition and recompile.
Status codes and errors#
| Status | Where it occurs |
|---|---|
200 |
Reads, and PATCH |
201 |
Create file, add definition |
204 |
Delete file, delete definition |
400 |
Instance reads against an uncompiled domain (Query.NotCompiled) |
404 |
File not found, definition id not found, instance not in the compiled world |
409 |
File stem already exists, definition id already exists |
422 |
A fatal-rule save verdict. Ordinary validation failures do not produce a 422 here. See below |
500 |
Always the generic detail "An internal error occurred." |
Every failure is RFC-9457 application/problem+json with a frozen type URI. There is no error field, no message field, no path field, and no universal code extension on this surface. Branch your client on the type URI. See Atlas API Overview for the full type table.
Responses use camelCase names, omit null members entirely, and serialize enums as camelCase strings. A client that expects null members to be present will break.
A 2xx does not mean the change is on the branch#
Instance-map writes go through the same write orchestrator as every other Atlas save, so two behaviours apply here in full:
- A save carrying error-severity validation violations returns success, lands in the caller's private draft session, and does not advance the target branch. The only signal is the
Atlas-Validation-Violationsresponse header, carrying the count of outstanding violations. The header is absent, not0, on a clean save. - Once a draft session exists, every subsequent write on that branch, valid or not, stays in the session until you publish with
POST /api/v1/branches/{branch}/draft/publish.
So the read-your-write check for this surface is: inspect Atlas-Validation-Violations on every write, then GET /api/v1/branches/{branch}/domains/{domain}/validation for the rule ids, then publish if a session exists. The mechanism is described once in Atlas API Overview.
Validation rules you will actually hit#
Instance-map rules are I-A1 to I-A11 (format, checked against the persisted file) and I-B1 to I-B22 (semantics, checked against the ontology and the expanded instance set). The ones this API surface generates most often:
| Rule | Meaning |
|---|---|
I-A1 |
The file is not parseable JSON, or its root is not an array. An unrecognised property name lands here and fails the whole file |
I-A2 |
A definition is missing id, type or instanceId |
I-A3 |
id is not globally unique across the domain's .map.json files |
I-A4 |
A property key is not a full urn:atlas:ontology: URI |
I-A5 |
Unrecognised strategy type |
I-A6 |
A strategy is missing a field its type requires |
I-A8 |
A placeholder references a variable no map entry declares, or a map entry is referenced by nothing |
I-A9 |
A relationship map declares only one of source and target |
I-B1 |
The type URI is not registered in the ontology as an entity type |
I-B3 |
The property is not attached to the instance's type, inheritance honoured |
I-B5, I-B6, I-B7 |
Relationship archetype, endpoint resolution, endpoint typing |
I-B10 |
A sources[].bindingPath does not resolve through the connector registry |
I-B22 |
An unbound function parameter. This is the one Warning-severity rule. It never blocks |
GET /api/v1/branches/{branch}/domains/{domain}/validation returns the authoritative current list.
After you write: compile#
Nothing you author here exists as an instance until the domain is compiled.
PUT /api/v1/branches/main/domains/brewing/world
Authorization: Bearer <token>
200 OK returns CompileResult {entityCount, edgeCount, archetypeCount, compiledAt, functionErrors[]}. There is no warnings member.
422 with a violations[] array means compilation aborted and no world was built. Compilation does not degrade: it does not skip the offending definitions and build the rest.
GET .../world reports isStale and compiledAtSha without recompiling. Atlas recompiles every recorded branch and domain pair at startup, so a manual recompile after a restart is normally unnecessary. The exception is a domain with rowset sources whose connector has not advertised yet: that domain waits for the connector and stays uncompiled if it never appears.
Troubleshooting#
| Symptom | Likely cause | What to do |
|---|---|---|
Every call returns 200 text/html and your JSON parser throws on < |
The path matched no route and the single-page application shell was served, or an unauthenticated call was redirected to the sign-in page | Check Content-Type before parsing. Then check the route against the table above, especially /api/v2 to /api/v1, PUT to PATCH, and the dropped {fileName} on by-id routes. Send a bearer on every call |
PUT .../instances/{id} "succeeds" but nothing changes |
Update is PATCH. PUT matches no route on this group |
Use PATCH /api/v1/branches/{branch}/domains/{domain}/instance-maps/instances/{id} |
GET, PATCH or DELETE by id returns HTML |
The URL still carries {fileName}. By-id routes are /instance-maps/instances/{id} |
Drop the file segment. Ids are globally unique and the owning file is resolved server-side |
POST a definition returns 404 |
The {fileName} does not exist |
Create the file first with POST /instance-maps/, then add definitions to it |
POST a definition returns 409 |
A definition with that id already exists somewhere in the domain, not necessarily in this file |
Choose a different id, or PATCH the existing one |
POST a file returns 409 for a name you have not used |
File stems are compared case-insensitively | List the existing stems with GET /instance-maps/ before creating |
POST returns 201 and the graph has no edges |
The relationships[] form was used, and an unrecognised property is ignored on the API path |
Author each connection as its own definition with a relationship type and both source and target |
| A field you sent is missing from the definition when you read it back | Same cause: it is not one of the accepted fields and was dropped. instanceBinding behaves this way too |
Compare the response body against what you sent. Use only the fields in the request-body table |
Compiling reports I-A1 against a whole file and every definition in it vanishes |
A hand-edited file contains an unrecognised property name. On disk, unknown members are fatal to the entire file, unlike over the API | Remove the stale field. The message names the line and column. Prefer authoring through the API |
| A property never appears in the compiled world, with no error on the write | The property key was a short name. It is accepted by the POST, flagged as I-A4, and dropped at compile |
Key properties by the full URI: urn:atlas:ontology:{domain}:{property} |
A definition saves but never compiles, with I-B1 |
type is a local name such as "Pump" rather than a full URI |
Use the full URI. List valid ones with GET /api/v1/branches/{branch}/domains/{domain}/entity-types |
A save is rejected with Strategy type '...' is not recognised |
The discriminator key or its value is wrong. It is type, not strategy, and the values end in Strategy |
Use "type": "StaticStrategy" / "BoundStrategy" / "CalculatedStrategy". There is no Manual |
| A calculated property computes from nothing and returns 0 or null | functionUri or propertyUri used an invented namespace such as urn:atlas:fn: or urn:atlas:prop: |
All five ontology kinds share one namespace. Use urn:atlas:ontology:{scope}:{LocalName} for both |
| You expected six instances and got three | Expansion is linear over the longest map entry, not a Cartesian product |
Make every map entry the same length, or split it into one definition per group. See Instance Maps |
| You got one instance whose identity contains the literal placeholder text | The placeholder was written {i}. Only [Name] is recognised |
Rewrite instanceId with [Num] or [Num, 00] and declare a matching map entry |
| A bound property never receives a value | The bindingPath does not match a path the connector advertises |
Copy the path a working property uses. Slash-delimited, starting from the connector instance id, never scheme://. See Property Sourcing Strategies |
A relationship reports a dangling endpoint (I-B6) |
source or target holds a definition id rather than an expanded instance identity, or the padding differs (tank-01 against tank-1) |
Read the exact identities from GET .../instances and use those |
A relationship save fails I-A9 |
Only one endpoint was supplied | Supply both. An entity map supplies neither |
Sending {"properties": {}} does not clear the properties |
An omitted or empty collection means "unchanged" in the PATCH merge |
Send {"clear": ["properties"]} |
Instance reads return 400 for a domain you know exists |
The domain has never been compiled on that branch | PUT /api/v1/branches/{branch}/domains/{domain}/world, then retry |
GET .../instances/{instanceId} returns 404 for an id you can see in a query |
The path segment was not percent-encoded, so the colons broke the route | Percent-encode the URN: urn%3Aatlas%3Ainstance%3A... |
| A list of instances looks truncated | pageSize defaults to 100 |
Page with page and pageSize, and read totalCount from the response |
A write returned 2xx and a compile returned 200, but nothing changed |
The save carried violations and was routed into your private draft session; the branch never advanced, and a draft-routed compile is not cached | Check the Atlas-Validation-Violations response header, read GET .../validation, fix the rules, then POST /api/v1/branches/{branch}/draft/publish and compile again |
Related articles#
- Atlas API Overview
- Instance Maps
- Property Sourcing Strategies - Static, Bound, and Calculated - Static, Bound, and Calculated
- Atlas API Overview - Ontology
- Atlas API Overview - Query
- Atlas API Overview - Connectors