Data Model Mismatches — TerminusDB Troubleshooting

Open inAnthropic

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 null vs field entirely absent
  • @id value 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:

  1. Understand the keying strategies:

    Strategy@id formatExample
    LexicalClassName/key-field-valuePerson/alice
    HashClassName/sha256hashPerson/a3f2b8c...
    ValueHashClassName/sha256(all-fields)Event/b7d1e4f...
    RandomClassName/random-idPerson/lkj23sdf...
  2. For Lexical keys, the @id is deterministic — it is built from the @key fields:

    Example: JSON
    {
      "@type": "Class",
      "@id": "Person",
      "@key": { "@type": "Lexical", "@fields": ["name"] },
      "name": "xsd:string"
    }

    Inserting {"@type": "Person", "name": "Alice"} produces @id = "Person/Alice".

  3. 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"
  4. 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:

TypeOrderedDuplicatesUse case
SetNoNo (deduplicated)Tags, categories, unique references
ArrayYes (indexed)YesOrdered sequences, time series
ListYes (linked)YesQueues, ordered collections needing insertion

Fix:

  1. If you need order preserved, use Array or List, not Set:

    Example: JSON
    {
      "@type": "Class",
      "@id": "Playlist",
      "tracks": { "@type": "Array", "@class": "Track" }
    }
  2. If you are seeing duplicates in a Set, that means the items are actually different documents (different @id values). Check whether you are accidentally creating new documents instead of referencing existing ones.

  3. 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:

  1. 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) { ... }
  2. When inserting, simply omit optional fields you do not want to set:

    Example: JSON
    {
      "@type": "Person",
      "name": "Alice"
    }

    Do not set them to null explicitly — this may cause a validation error.

  3. 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:

  1. If you get "already exists" with a Lexical key — you are trying to insert a document with key field values that already exist. Use replace_document instead:

    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"
  2. If duplicates appear with Hash or Random keys — every insert creates a new document because the @id is always unique. If you want deduplication, switch to Lexical or ValueHash:

    • Lexical — deduplicates on specific fields you choose
    • ValueHash — deduplicates on all field values combined
  3. Choose your strategy based on semantics:

    • Use Lexical when documents have a natural identifier (username, ISBN, SKU)
    • Use ValueHash when the document is defined entirely by its content (events, facts)
    • Use Hash or Random when every insertion should always create a new record

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:

  1. 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 @id from multiple parents.

  2. 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" }
    }
  3. 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:

  1. Accept the immutable semantics — if you use ValueHash, treat documents as immutable facts. Modifications create new facts, old ones are superseded.

  2. 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 value does not change the document's @id.

  3. If you must use ValueHash, query by field values rather than storing @id references:

    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?

Was this helpful?