Data Model Mismatches
This page covers errors that arise from misunderstanding how TerminusDB generates document identifiers, handles collection types (Set, Array, List), treats optional fields, or applies keying strategies — issues where the data structure does not behave as expected.
Symptoms
"Document not found"when retrieving by@id- Duplicate entries appearing in a Set field
- Array elements returned in unexpected order
- Optional field present as
nullvs field entirely absent @idvalue does not match what you expected after insertion
Common causes
Document not found (wrong @id format)
Error message: HTTP 404 or "api:ErrorMessage": "Document not found" when fetching by @id
Cause: TerminusDB generates @id values based on the @key strategy defined in the schema. If you construct an @id manually, it may not match the format TerminusDB actually used.
Fix:
Understand the keying strategies:
Strategy @idformatExample LexicalClassName/key-field-valuePerson/aliceHashClassName/sha256hashPerson/a3f2b8c...ValueHashClassName/sha256(all-fields)Event/b7d1e4f...RandomClassName/random-idPerson/lkj23sdf...For Lexical keys, the
@idis deterministic — it is built from the@keyfields:Example: JSON{ "@type": "Class", "@id": "Person", "@key": { "@type": "Lexical", "@fields": ["name"] }, "name": "xsd:string" }Inserting
{"@type": "Person", "name": "Alice"}produces@id = "Person/Alice".For Hash or Random keys, you cannot predict the
@id. Retrieve it from the insert response:# The response includes the generated @id curl -u admin:root -X POST \ -H "Content-Type: application/json" \ -d '{"@type": "Event", "title": "Meeting"}' \ "http://localhost:6363/api/document/admin/mydb"Query by field values instead of guessing the
@id:curl -u admin:root \ "http://localhost:6363/api/document/admin/mydb?type=Person&query=%7B%22name%22%3A%22Alice%22%7D"
Set vs Array confusion
Error message: No error, but unexpected behaviour — duplicates appear or order is lost.
Cause: TerminusDB distinguishes between Set, Array, and List types, each with different semantics:
| Type | Ordered | Duplicates | Use case |
|---|---|---|---|
Set | No | No (deduplicated) | Tags, categories, unique references |
Array | Yes (indexed) | Yes | Ordered sequences, time series |
List | Yes (linked) | Yes | Queues, ordered collections needing insertion |
Fix:
If you need order preserved, use
ArrayorList, notSet:Example: JSON{ "@type": "Class", "@id": "Playlist", "tracks": { "@type": "Array", "@class": "Track" } }If you are seeing duplicates in a Set, that means the items are actually different documents (different
@idvalues). Check whether you are accidentally creating new documents instead of referencing existing ones.When inserting into an
Array, include the index explicitly in WOQL or let the Document API manage ordering:Example: JSON{ "@type": "Playlist", "tracks": [ { "@type": "Track", "title": "Song A" }, { "@type": "Track", "title": "Song B" } ] }
Optional field handling
Error message: No error, but field appears as null in some clients or is entirely absent from the response.
Cause: TerminusDB represents Optional fields that have no value by omitting the field entirely from the document — it does not use null. Some client libraries may normalise this to null.
Fix:
When reading documents, check for field presence rather than checking for
null:Example: JavaScript// WRONG — assumes null means "not set" if (doc.nickname === null) { ... } // CORRECT — check for field existence if (!("nickname" in doc)) { ... } // or if (doc.nickname === undefined) { ... }When inserting, simply omit optional fields you do not want to set:
Example: JSON{ "@type": "Person", "name": "Alice" }Do not set them to
nullexplicitly — this may cause a validation error.Schema definition for optional fields:
Example: JSON{ "@type": "Class", "@id": "Person", "name": "xsd:string", "nickname": { "@type": "Optional", "@class": "xsd:string" } }
@key strategy issues (unexpected duplicates or conflicts)
Error message: "api:ErrorMessage": "Document already exists" on insert, or unintended overwrites on update
Cause: The @key strategy determines when two documents are considered "the same". With Lexical, documents with the same key field values are the same document. With Hash, every insertion with different non-key field values creates a new document.
Fix:
If you get "already exists" with a
Lexicalkey — you are trying to insert a document with key field values that already exist. Usereplace_documentinstead:curl -u admin:root -X PUT \ -H "Content-Type: application/json" \ -d '{"@type": "Person", "name": "Alice", "age": 31}' \ "http://localhost:6363/api/document/admin/mydb"If duplicates appear with
HashorRandomkeys — every insert creates a new document because the@idis always unique. If you want deduplication, switch toLexicalorValueHash:Lexical— deduplicates on specific fields you chooseValueHash— deduplicates on all field values combined
Choose your strategy based on semantics:
- Use
Lexicalwhen documents have a natural identifier (username, ISBN, SKU) - Use
ValueHashwhen the document is defined entirely by its content (events, facts) - Use
HashorRandomwhen every insertion should always create a new record
- Use
Subdocument reference violation
Error message: "vio:message": "Subdocument cannot be referenced from multiple parents" or unexpected behaviour where editing a subdocument affects the wrong parent
Cause: A subdocument (@subdocument: []) is owned by exactly one parent document. Unlike regular documents, subdocuments cannot be shared or referenced from multiple places. If you attempt to insert the same subdocument object into two different parent documents, TerminusDB rejects the second insertion.
Fix:
If you need the same data referenced from multiple places, make it a regular document (remove
@subdocument):Example: JSON{ "@type": "Class", "@id": "Address", "street": "xsd:string", "city": "xsd:string" }Then reference it by
@idfrom multiple parents.If you need embedded copies (not shared references), create separate subdocument instances in each parent:
Example: JSON{ "@type": "Person", "name": "Alice", "home_address": { "@type": "Address", "street": "123 Main St", "city": "London" } }Rule of thumb: use subdocuments for structures that have no identity outside their parent (line items, address blocks, metadata). Use regular documents for entities that exist independently or are referenced from multiple places.
ValueHash key — document appears to be lost after update
Error message: No error, but after updating a field on a ValueHash-keyed document, the old @id returns 404 and you cannot find the document.
Cause: ValueHash computes the @id from ALL field values. Changing any field changes the @id. The old document effectively disappears (masked) and a new one is created with a different @id. This is by design — ValueHash documents are content-addressed and immutable in identity.
Fix:
Accept the immutable semantics — if you use ValueHash, treat documents as immutable facts. Modifications create new facts, old ones are superseded.
Switch to Lexical or Random if you need to update documents while preserving their
@id:Example: JSON{ "@type": "Class", "@id": "Measurement", "@key": { "@type": "Lexical", "@fields": ["sensor_id", "timestamp"] }, "sensor_id": "xsd:string", "timestamp": "xsd:dateTime", "value": "xsd:decimal" }Now updating
valuedoes not change the document's@id.If you must use ValueHash, query by field values rather than storing
@idreferences:curl -u admin:root \ "http://localhost:6363/api/document/admin/MyDatabase?type=Measurement&query=%7B%22sensor_id%22%3A%22sensor-1%22%7D"
See Schema Reference — Key strategies for detailed documentation on when to use each keying strategy.
Still stuck?
- Open an issue with your schema definition and the operation that fails
- Check the Schema Reference for
@keystrategy documentation - Check the Documents explanation for how TerminusDB models data
- See the Document Types Compared page for Set vs Array vs List details