Skip to main content

Nodes API

Manage nodes in the Paradigm identity graph.

Base URL

https://api.ofself.ai/api/v1

Authentication

All requests from third-party apps require:

  • API Key: X-API-Key: <your-key> + X-User-ID: <user-id>
Important

User JWT authentication (Authorization: Bearer <token> via email/password login) is exclusively for app.ofself.ai. Third-party developers cannot use this method.

Endpoints

POST /nodes

POST Create a new node.

curl -X POST "https://api.ofself.ai/api/v1/nodes" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123" \
-H "Content-Type: application/json" \
-d '{
"title": "First day teaching my own class",
"value": "This was the moment everything clicked...",
"node_type": "EXPERIENCE",
"meaning_level": "IDENTITY",
"graph_view": "identity",
"importance_score": 0.98,
"metadata": {
"properties": {
"when": {
"type": "instance",
"status": "done",
"start": "2020-09-15"
},
"where": "University lecture hall",
"significance": "Realized teaching was my calling"
}
},
"agent_metadata": {
"agent_id": "agent_001",
"agent_name": "Memory Extraction Agent v2.1",
"agent_type": "experience_extractor",
"confidence_score": 0.92
},
"last_modified_by_agent_id": "agent_001",
"tags": ["formative", "career"]
}'

Request Body:

FieldTypeRequiredDescription
titlestringYesMain description/content
valuestringNoAdditional notes
node_typestringYesIDENTITY layer: EXPERIENCE, BELIEF, ENTITY, or GOAL. DATA/CONTEXT layers: any string (e.g. document, note, recipe)
meaning_levelenumNoDATA, CONTEXT, or IDENTITY (defaults to CONTEXT if omitted)
graph_viewstringNo"identity" (default) or "neutral"
importance_scorefloatNo0.0-1.0 relevance score

| metadata | object | No | Contains properties with type-specific fields | | agent_metadata | object | No | Agent information | | last_modified_by_agent_id | UUID | No | Agent ID for traceability | | tags | array | No | Tag names (auto-created if needed) |

Type-Specific Properties (in metadata.properties):

EXPERIENCE:

{
"when": {
"type": "instance | series",
"status": "done | ongoing | future",
"start": "2020-09-15",
"end": "2020-09-16"
},
"where": "string",
"significance": "string"
}

BELIEF:

{
"category": "about_self | about_world",
"strength_of_belief": 8,
"status": "active | outdated"
}

ENTITY:

{
"name": "string",
"category": "person | place | idea | practice | community | organization"
}

GOAL:

{
"motivation": "string",
"time_horizon": "immediate | short_term | long_term | lifelong",
"status": "active | paused | achieved | abandoned"
}

Agent Metadata:

{
"agent_id": "uuid",
"agent_name": "string",
"agent_type": "string",
"agent_version": "string",
"processing_time_ms": 1850,
"confidence_score": 0.92,
"source_models": ["gpt-4"],
"extraction_method": "string",
"validation_status": "verified | pending | rejected",
"timestamp": "2024-01-15T10:30:00Z",
"session_id": "string",
"contribution_details": {
"fields_extracted": ["when", "where"],
"verification_passed": true,
"quality_score": 0.92
}
}

Response: 201 Created

note

The graph_view field is stored but not returned in API responses. You must track it client-side or always pass it explicitly.

{
"id": "223845bf-4397-4135-899f-c72f84289caa",
"title": "First day teaching my own class",
"value": "This was the moment everything clicked...",
"node_type": "EXPERIENCE",
"meaning_level": "IDENTITY",
"importance_score": 0.98,
"owner_id": "user_123",
"metadata": {
"properties": {
"when": {...},
"where": "University lecture hall",
"significance": "Realized teaching was my calling"
}
},
"agent_metadata": {
"agent_id": "agent_001",
"agent_name": "Memory Extraction Agent v2.1",
"confidence_score": 0.92
},
"last_modified_by_agent_id": "agent_001",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}

GET /nodes

GET List nodes with optional filters.

curl -X GET "https://api.ofself.ai/api/v1/nodes?node_type=EXPERIENCE&meaning_level=IDENTITY&limit=20" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123"

Query Parameters:

ParameterTypeDefaultDescription
accessstringallAccess scope: owned, shared, or all
node_typestring-Filter by node type (e.g. EXPERIENCE, BELIEF, ENTITY, GOAL, or any free-text type)
meaning_levelstring-DATA, CONTEXT, or IDENTITY
viewstring-Graph view: identity or neutral
tag_idUUID-Filter by tag
searchstring-Search title/value
include_tagsbooleanfalseInclude associated tags
include_ownerbooleanfalseInclude owner info (useful for grouping shared nodes)
sortstringupdated_atupdated_at, created_at, or title
orderstringdescasc or desc
limitinteger20Items to return (max 100)
offsetinteger0Pagination offset

Response: 200 OK

{
"nodes": [
{
"id": "node_1",
"title": "First day teaching",
"node_type": "EXPERIENCE",
"meaning_level": "IDENTITY",
"importance_score": 0.98,
"metadata": {"properties": {...}},
"agent_metadata": {...},
"created_at": "2024-01-15T10:30:00Z",
"tags": [{"id": "tag_1", "name": "formative"}],
"is_shared": false,
"owner": {"id": "user_123", "username": "alice"}
}
],
"total": 42
}

Shared With Me

To list only nodes shared to you by other users:

curl -X GET "https://api.ofself.ai/api/v1/nodes?access=shared&include_owner=true&include_tags=true&limit=50&offset=0" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123"

GET /nodes/:node_id

GET Get a single node with full details.

curl -X GET "https://api.ofself.ai/api/v1/nodes/223845bf-4397-4135-899f-c72f84289caa" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123"

Response: 200 OK

{
"id": "223845bf-4397-4135-899f-c72f84289caa",
"title": "First day teaching",
"value": "Full content...",
"node_type": "EXPERIENCE",
"meaning_level": "IDENTITY",
"owner_id": "user_123",
"metadata": {
"properties": {
"when": {"type": "instance", "status": "done", "start": "2020-09-15"},
"where": "University",
"significance": "Realized my calling"
}
},
"agent_metadata": {
"agent_id": "agent_001",
"agent_name": "Memory Extraction Agent",
"confidence_score": 0.92
},
"last_modified_by_agent_id": "agent_001",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}

PUT /nodes/:node_id

PUT Update a node (creates history entry).

curl -X PUT "https://api.ofself.ai/api/v1/nodes/node_abc" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123" \
-H "Content-Type: application/json" \
-d '{
"title": "Updated title",
"meaning_level": "IDENTITY",
"agent_metadata": {
"agent_id": "agent_002",
"agent_name": "Enhancement Agent",
"confidence_score": 0.88
},
"last_modified_by_agent_id": "agent_002"
}'

Request Body: All fields are optional - only include what you want to update.

Response: 200 OK (returns updated node)


DELETE /nodes/:node_id

DELETE Soft delete a node.

curl -X DELETE "https://api.ofself.ai/api/v1/nodes/node_abc" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123"

Response: 204 No Content


POST /nodes/batch

POST Create multiple nodes in a single request (up to 100).

curl -X POST "https://api.ofself.ai/api/v1/nodes/batch" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123" \
-H "Content-Type: application/json" \
-d '{
"nodes": [
{
"title": "First day teaching my own class",
"value": "This was the moment everything clicked...",
"node_type": "EXPERIENCE",
"meaning_level": "IDENTITY",
"graph_view": "identity",
"importance_score": 0.98,
"tags": ["formative", "career"]
},
{
"title": "Education shapes society",
"node_type": "BELIEF",
"meaning_level": "IDENTITY",
"graph_view": "identity",
"importance_score": 0.85,
"metadata": {
"properties": {
"category": "about_world",
"strength_of_belief": 9,
"status": "active"
}
}
}
],
"on_duplicate": "skip"
}'

Request Body:

FieldTypeRequiredDescription
nodesarrayYesArray of node objects (1-100 items). Each node follows the same schema as POST /nodes.
on_duplicatestringNo"skip" (default) or "overwrite". Controls behavior when a node with the same (owner_id, graph_view, title, value) already exists.

Duplicate Detection:

A node is considered a duplicate when an existing node matches on all of:

  • owner_id (the authenticated user)
  • graph_view
  • title
  • value
on_duplicateBehavior
skipDuplicate nodes are silently skipped and returned in the skipped array.
overwriteDuplicate nodes are updated with the new data and returned in the updated array.

Response: 201 Created (all created) or 200 OK (mixed results)

{
"created": [
{
"index": 0,
"id": "223845bf-4397-4135-899f-c72f84289caa",
"title": "First day teaching my own class"
}
],
"skipped": [
{
"index": 1,
"id": "existing-node-uuid",
"title": "Education shapes society"
}
],
"updated": [],
"errors": [],
"summary": {
"total": 2,
"created": 1,
"skipped": 1,
"updated": 0,
"errors": 0
}
}

Error Handling:

Per-node validation errors do not fail the entire batch. Invalid nodes are reported in the errors array while valid nodes are still processed:

{
"created": [...],
"skipped": [],
"updated": [],
"errors": [
{
"index": 2,
"title": "",
"error": "title and node_type are required"
}
],
"summary": {
"total": 3,
"created": 2,
"skipped": 0,
"updated": 0,
"errors": 1
}
}
When to use batch vs single create

Use POST /nodes/batch when ingesting multiple nodes at once (e.g., AI agent extracting entities from a document, importing data from another system). Use POST /nodes for interactive single-node creation. The batch endpoint processes all nodes in a single database transaction for consistency and performance.

Node Type Rules
  • IDENTITY meaning_level: node_type must be one of EXPERIENCE, BELIEF, ENTITY, or GOAL
  • DATA / CONTEXT meaning_level: node_type can be any string (e.g. document, log, meeting_notes, recipe)
  • If meaning_level is omitted, it defaults to CONTEXT

GET /nodes/count

GET Count nodes matching filters.

curl -X GET "https://api.ofself.ai/api/v1/nodes/count?schema_ids=schema-uuid-1,schema-uuid-2&tag_ids=tag-uuid-1" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123"

Query Parameters:

ParameterTypeDescription
schema_idsstringComma-separated schema UUIDs. Use __freeform__ for nodes with no schema.
tag_idsstringComma-separated tag UUIDs

Response: 200 OK

{
"count": 42
}

GET /nodes/history

GET Get all node history entries for the current user (global timeline).

curl -X GET "https://api.ofself.ai/api/v1/nodes/history?limit=50" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123"

Query Parameters:

ParameterTypeDescription
node_idstringFilter to a specific node's history
limitintegerMax entries to return

Response: 200 OK

{
"history": [
{
"id": "history-uuid",
"node_id": "node-uuid",
"version": 3,
"change_type": "update",
"change_reason": "Agent updated metadata",
"changed_by_type": "agent",
"changed_by_user_id": "user-uuid",
"changed_at": "2025-01-15T10:30:00Z",
"snapshot": { "title": "...", "node_type": "EXPERIENCE" }
}
],
"total": 1
}

POST /nodes/bulk-delete

POST Delete multiple nodes at once.

curl -X POST "https://api.ofself.ai/api/v1/nodes/bulk-delete" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123" \
-H "Content-Type: application/json" \
-d '{
"node_ids": ["node-uuid-1", "node-uuid-2", "node-uuid-3"]
}'

Request Body:

FieldTypeRequiredDescription
node_idsarrayYesArray of node UUIDs to delete (1-100 items).

Response: 200 OK

{
"deleted": 3,
"errors": []
}

Tag Management

POST /nodes/:node_id/tags

POST Add a tag to a node.

curl -X POST "https://api.ofself.ai/api/v1/nodes/node_abc/tags" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123" \
-H "Content-Type: application/json" \
-d '{
"tag_id": "tag-uuid",
"relevance_score": 0.9,
"is_auto_generated": false
}'

Response: 201 Created


DELETE /nodes/:node_id/tags/:tag_id

DELETE Remove a tag from a node.

curl -X DELETE "https://api.ofself.ai/api/v1/nodes/node_abc/tags/tag_xyz" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123"

Response: 200 OK

Note: If this is the last tag on the node, the node will be soft-deleted.


Relationships

GET /nodes/:node_id/relationships

GET Get all relationships for a node.

curl -X GET "https://api.ofself.ai/api/v1/nodes/node_abc/relationships?direction=both" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123"

Query Parameters:

ParameterTypeDefaultDescription
directionstringbothoutgoing, incoming, or both
relationship_typestring-Filter by relationship type

Response: 200 OK

{
"node_id": "node_abc",
"outgoing": [
{"id": "rel_1", "from_node_id": "node_abc", "to_node_id": "node_xyz", "relationship_type": "references"}
],
"incoming": [
{"id": "rel_2", "from_node_id": "node_def", "to_node_id": "node_abc", "relationship_type": "mentions"}
],
"total_outgoing": 1,
"total_incoming": 1
}

PUT /nodes/:node_id/embedding

PUT Update embedding for a single node.

curl -X PUT "https://api.ofself.ai/api/v1/nodes/node_abc/embedding" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123" \
-H "Content-Type: application/json" \
-d '{
"embedding": [0.123, 0.456, 0.789, ...]
}'

Response: 200 OK


PUT /nodes/batch/embeddings

PUT Update embeddings for multiple nodes in batch.

curl -X PUT "https://api.ofself.ai/api/v1/nodes/batch/embeddings" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123" \
-H "Content-Type: application/json" \
-d '{
"embeddings": [
{"id": "node_1", "embedding": [0.1, 0.2, ...]},
{"id": "node_2", "embedding": [0.3, 0.4, ...]}
]
}'

Response: 200 OK

{
"updated": 2,
"failed": 0,
"errors": []
}

POST /nodes/search/vector

POST Search for similar nodes using vector embedding.

curl -X POST "https://api.ofself.ai/api/v1/nodes/search/vector" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123" \
-H "Content-Type: application/json" \
-d '{
"embedding": [0.1, 0.2, ...],
"limit": 10,
"min_score": 0.7,
"graph_view": "identity"
}'

Request Body:

FieldTypeDefaultDescription
embeddingarrayrequiredQuery vector
limitinteger10Max results (max 100)
min_scorefloat0.0Minimum similarity score
graph_viewstringidentityidentity or neutral

Response: 200 OK

{
"nodes": [
{
"id": "node_abc",
"title": "Similar node",
"similarity_score": 0.92,
...
}
],
"total": 5
}

Agent & History Endpoints

GET /nodes/:node_id/ledger

GET Get all agents that contributed to a node.

curl -X GET "https://api.ofself.ai/api/v1/nodes/node_abc/ledger" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123"

Response: 200 OK

{
"node_id": "node_abc",
"total_agents": 3,
"agents": [
{
"agent_id": "agent_001",
"agent_name": "Memory Extraction Agent v2.1",
"agent_type": "experience_extractor",
"contribution_count": 1,
"last_contribution": "2024-01-15T10:30:00Z"
},
{
"agent_id": "agent_002",
"agent_name": "Enhancement Agent v2.5",
"agent_type": "content_enhancer",
"contribution_count": 2,
"last_contribution": "2024-01-16T14:20:00Z"
}
]
}

GET /nodes/:node_id/history

GET Get version history for a node.

curl -X GET "https://api.ofself.ai/api/v1/nodes/node_abc/history?limit=10&include_snapshots=false" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123"

Query Parameters:

ParameterTypeDefaultDescription
limitinteger100Number of versions to return
offsetinteger0Pagination offset
include_snapshotsbooleanfalseInclude full node snapshots
start_datedatetime-Filter from date
end_datedatetime-Filter to date

Response: 200 OK

{
"node_id": "node_abc",
"history": [
{
"version": 3,
"changed_at": "2024-01-17T09:15:00Z",
"change_type": "update",
"changed_by_type": "agent",
"changed_by_agent_id": "agent_003",
"agent_metadata": {
"agent_name": "QA Agent",
"confidence_score": 0.95,
"validation_status": "verified"
},
"changed_fields": {
"metadata": {...}
}
},
{
"version": 2,
"changed_at": "2024-01-16T14:20:00Z",
"change_type": "update",
"changed_by_type": "agent",
"changed_by_agent_id": "agent_002",
"agent_metadata": {
"agent_name": "Enhancement Agent",
"confidence_score": 0.88
},
"changed_fields": {
"title": "Enhanced title",
"meaning_level": "IDENTITY"
}
}
],
"total": 3,
"limit": 10,
"offset": 0
}

GET /nodes/:node_id/contributions

GET Get detailed contributions to a node.

curl -X GET "https://api.ofself.ai/api/v1/nodes/node_abc/contributions" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123"

Response: 200 OK

{
"node_id": "node_abc",
"contributions": [
{
"id": "contrib_001",
"contributor_id": "agent_001",
"field_name": "title",
"contributed_value": "First day teaching",
"confidence_score": 0.92,
"contributed_at": "2024-01-15T10:30:00Z"
},
{
"id": "contrib_002",
"contributor_id": "agent_002",
"field_name": "metadata.properties.significance",
"contributed_value": "Realized my calling",
"confidence_score": 0.88,
"contributed_at": "2024-01-16T14:20:00Z"
}
],
"total": 2
}

File Upload & Download

POST /nodes/upload

POST Upload a file and create a Node with the paradigm:RawFile system schema. The binary is stored in blob storage; the node's metadata holds the blob key, filename, MIME type, size, and checksum.

curl -X POST "https://api.ofself.ai/api/v1/nodes/upload" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123" \
-F "file=@document.pdf" \
-F 'tags=["reports", "q4"]' \
-F 'metadata={"project":"acme"}'

Form Fields (multipart/form-data):

FieldTypeRequiredDescription
filebinaryYesThe file to upload
tagsJSON stringNoJSON array of tag IDs or tag names
metadataJSON stringNoAdditional metadata object

The endpoint auto-classifies:

  • data_type: text, image, audio, video, document, or other
  • format: file extension (pdf, csv, json, etc.)
  • structure_type: structured (json, csv, xml, yaml, tsv, parquet) or unstructured

Response: 201 Created

{
"id": "node-uuid",
"title": "document.pdf",
"node_type": "RAW_FILE",
"meaning_level": "DATA",
"schema_id": "rawfile-schema-uuid",
"metadata": {
"filename": "document.pdf",
"mime_type": "application/pdf",
"size_bytes": 102400,
"blob_key": "abc123/document.pdf",
"checksum_sha256": "e3b0c44...",
"data_type": "document",
"format": "pdf",
"structure_type": "unstructured",
"upload_source": "user"
},
"created_at": "2026-01-15T10:30:00Z"
}
Third-Party Apps

Third-party apps require schema write permission for the paradigm:RawFile schema. If the app's exposure profile doesn't grant write access to this schema, the upload will be rejected with a 403 error.


GET /nodes/:node_id/download

GET Generate a short-lived download URL for a Raw File node. The node must have the paradigm:RawFile schema.

curl -X GET "https://api.ofself.ai/api/v1/nodes/node-uuid/download" \
-H "X-API-Key: your-api-key" \
-H "X-User-ID: user-123"

Response: 200 OK

{
"download_url": "https://storage.blob.core.windows.net/..."
}

The download URL is a short-lived signed URL. Access is governed by the same tag + schema permissions as node reads.


Error Responses

All endpoints return standard error responses:

400 Bad Request

{
"error": {
"code": "INVALID_NODE_TYPE",
"message": "IDENTITY nodes must use one of: EXPERIENCE, BELIEF, ENTITY, GOAL"
}
}

401 Unauthorized

{
"error": {
"code": "UNAUTHORIZED",
"message": "Missing or invalid authentication"
}
}

404 Not Found

{
"error": {
"code": "NODE_NOT_FOUND",
"message": "Node not found or access denied"
}
}

422 Unprocessable Entity

{
"error": {
"code": "VALIDATION_ERROR",
"message": "Entity nodes require name and category in properties"
}
}

Rate Limits

  • Default: 1000 requests/hour per user
  • Burst: 50 requests/minute

Headers included in responses:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 950
X-RateLimit-Reset: 1705323600

Examples

Create an EXPERIENCE Node

import requests

API_KEY = "ofs_tp_xxxxxxxxxxxx.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
USER_ID = "user-123" # User who authorized your app

response = requests.post(
"https://api.ofself.ai/api/v1/nodes",
headers={
"X-API-Key": API_KEY,
"X-User-ID": USER_ID,
"Content-Type": "application/json"
},
json={
"title": "First day teaching my own class",
"node_type": "EXPERIENCE",
"meaning_level": "IDENTITY",
"graph_view": "identity", # Required to appear in app.ofself.ai
"metadata": {
"properties": {
"when": {
"type": "instance",
"status": "done",
"start": "2020-09-15"
},
"significance": "Realized teaching was my calling"
}
},
"agent_metadata": {
"agent_id": "agent_001",
"agent_name": "Memory Extractor",
"confidence_score": 0.92
},
"last_modified_by_agent_id": "agent_001"
}
)

node = response.json()
print(f"Created node: {node['id']}")

Multi-Agent Collaboration

# Agent 1: Initial extraction
node = create_node({
"title": "Started teaching",
"node_type": "EXPERIENCE",
"meaning_level": "CONTEXT",
"graph_view": "identity", # Set at creation - cannot be changed later
"agent_metadata": {
"agent_id": "agent_001",
"agent_name": "Extractor",
"confidence_score": 0.75
},
"last_modified_by_agent_id": "agent_001"
})

# Agent 2: Enhancement
update_node(node['id'], {
"meaning_level": "IDENTITY",
"metadata": {
"properties": {
"significance": "Defining moment"
}
},
"agent_metadata": {
"agent_id": "agent_002",
"agent_name": "Enhancer",
"confidence_score": 0.88
},
"last_modified_by_agent_id": "agent_002"
})

# View all contributors
ledger = get_ledger(node['id'])
print(f"Total agents: {ledger['total_agents']}")

Query by Type and Level

# Get all identity-level experiences for an authorized user
experiences = requests.get(
"https://api.ofself.ai/api/v1/nodes",
headers={
"X-API-Key": API_KEY,
"X-User-ID": USER_ID
},
params={
"node_type": "EXPERIENCE",
"meaning_level": "IDENTITY"
}
).json()

for node in experiences['nodes']:
print(f"{node['title']}")
print(f" Confidence: {node['agent_metadata']['confidence_score']}")

Next Steps