Curiosity

Schema Reference

This page is the authoritative reference for declaring node and edge schemas with the Curiosity.Library SDK. Use it as a lookup when you're writing a connector, ingestion pipeline, or any code that talks to a Curiosity Workspace.

For the conceptual "why" — when to use a key vs a property, how schemas evolve, what to index — see Graph model and Schema design.

Two ways to declare a schema

You can declare a node schema either declaratively (with attributes on a C# class) or fluently (by calling builder methods). Edge schemas are always fluent — they're just a name.

Declarative (attributes on a class)

using Curiosity.Library;

[Node]
public class SupportCase
{
    [Key]       public string         Id          { get; set; }
    [Property]  public string         Summary     { get; set; }
    [Property]  public string         Status      { get; set; }
    [Property]  public int            Priority    { get; set; }
    [Timestamp] public DateTimeOffset OpenedAt    { get; set; }
}

var schema = Schema.NodeFromType<SupportCase>();

The class name (or [Node(Name = "…")]) becomes the node type. The [Key] property is the identity field; every node type has exactly one and it must be a string. The [Timestamp] property — if present — is what time filters and recency sort use.

Fluent (builder methods)

var supportCase = Schema
    .NewNode("SupportCase", key: "Id")
    .Field("Summary",  FieldType.String)
    .Field("Status",   FieldType.String)
    .Field("Priority", FieldType.Int32)
    .Field("OpenedAt", FieldType.Time);

var hasDevice = Schema.NewEdge("HasDevice");

Use the fluent form when the shape isn't known at compile time — for example when a connector reads a remote source's metadata and maps it into a schema at startup.

Attribute reference

The C# SDK ships four attributes. Anything implied by other audits ([VectorIndex], [Encrypted], etc.) is not a Library attribute — those concerns are configured at the workspace level, not in code.

Attribute Applies to Required Notes
[Node] class yes Marks a class as a node type. Name overrides the class name.
[Key] property yes Exactly one per node type. Must be string. Inherits from [Property].
[Property] property no Maps a CLR property to a schema field. Name overrides the property name.
[Timestamp] property no At most one per node type. Property type must be DateTimeOffset or Time.

[Key] and [Timestamp] both derive from [Property], so on a single property only one of the three applies.

Schema-level constraints

These are enforced at registration time. Violating them throws when the schema is built.

  • Node: exactly one [Key] property of type string.
  • Node: at most one [Timestamp] property.
  • Node: field names Type, UID, Timestamp, Edges, EdgeCount are reserved and cannot be used as properties.
  • Edge: must not declare a key (edges are not addressable by key).
  • Edge: field names Type, To, ToType, UID, Timestamp are reserved.
  • Field names must be unique within a schema (case-sensitive). Re-adding the same field throws Duplicated field {name}.

Field types

FieldType is the enum used by the fluent builder. The declarative form maps CLR types onto these automatically.

FieldType C# type JSON wire shape Notes
String string string Default for text.
Boolean bool true / false
Byte byte number (0–255)
SByte sbyte number (−128–127) Signed byte.
Char char string (1 char)
Int32 int number
Int64 long number / string Large values may serialize as string to avoid loss.
UInt32 uint number
UInt64 ulong number / string
Float float number
Double double number
Decimal decimal string Encoded as a string to preserve precision.
Time DateTime, DateTimeOffset, Time ISO-8601 string Use for [Timestamp] properties.
GeoPoint GeoPoint { lat, lon } Resume indexing after enabling.
Language Language ISO 639 code
UID128 UID128 base64 string The workspace's internal node identifier.

Collections

The same set of FieldType values is supported through three collection variants:

Builder method C# property type Wire shape
Field(name, type) scalar scalar
ListField(name, type) T[] or List<T> JSON array
DictionaryField(name, type) Dictionary<string, T> JSON object
TableField(name, type) (fluent only — tabular data) array of arrays

In the declarative form Curiosity infers ListField for arrays and List<T>, and DictionaryField for Dictionary<string, T>. Dictionaries must have string keys; any other key type throws at schema-build time. Nullable scalars (int?, DateTime?) collapse to their non-nullable form.

Edge schemas

An edge schema is just a name. Edges have no properties of their own at the schema level — the meaning of the edge lives in the type name and the two endpoints.

var hasDevice = Schema.NewEdge("HasDevice");
var assignedTo = Schema.NewEdge("AssignedTo");

Conventionally, name edges as a verb phrase that reads naturally from source to target: Case -> HasDevice -> Device. Add a reverse edge if you traverse both ways often: Device -> AppearsInCase -> Case. Both edges are independent — committing the forward one does not create the reverse.

Keys and identity

Keys define identity. Two nodes of the same type with the same key are the same node — re-ingesting will update, not duplicate.

Constraints:

  • exactly one [Key] per node type
  • type must be string
  • non-empty; the SDK rejects empty keys at commit time
  • stable over time; changing a key changes the node's identity

If your source doesn't expose a natural key, build one deterministically from fields that do uniquely identify the record (e.g. "jira:" + projectKey + "-" + issueNumber). Never use a generated UUID for the key — re-runs will create duplicates.

Indexing

What gets indexed for text or vector search is not a schema-level decision in code. You declare schemas, then configure indexes against them in the workspace (UI or admin API):

This keeps the schema layer small and lets you change retrieval behaviour without re-deploying connectors.

Aliases (conceptual)

Aliases are alternate strings that resolve to the same node — useful for spelling variants, abbreviations, and cross-source normalization. They're managed at the entity-extraction layer, not declared in the schema. See Entity capture.

Reserved / system node types

The workspace stores some configuration (pipelines, search indexes, scheduled tasks) as nodes in a hidden system namespace. You won't see them in user-facing search but they're inspectable for debugging through the admin UI.

© 2026 Curiosity. All rights reserved.
Powered by Neko