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>
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:
| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Main description/content |
value | string | No | Additional notes |
node_type | string | Yes | IDENTITY layer: EXPERIENCE, BELIEF, ENTITY, or GOAL. DATA/CONTEXT layers: any string (e.g. document, note, recipe) |
meaning_level | enum | No | DATA, CONTEXT, or IDENTITY (defaults to CONTEXT if omitted) |
graph_view | string | No | "identity" (default) or "neutral" |
importance_score | float | No | 0.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
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:
| Parameter | Type | Default | Description |
|---|---|---|---|
access | string | all | Access scope: owned, shared, or all |
node_type | string | - | Filter by node type (e.g. EXPERIENCE, BELIEF, ENTITY, GOAL, or any free-text type) |
meaning_level | string | - | DATA, CONTEXT, or IDENTITY |
view | string | - | Graph view: identity or neutral |
tag_id | UUID | - | Filter by tag |
search | string | - | Search title/value |
include_tags | boolean | false | Include associated tags |
include_owner | boolean | false | Include owner info (useful for grouping shared nodes) |
sort | string | updated_at | updated_at, created_at, or title |
order | string | desc | asc or desc |
limit | integer | 20 | Items to return (max 100) |
offset | integer | 0 | Pagination 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:
| Field | Type | Required | Description |
|---|---|---|---|
nodes | array | Yes | Array of node objects (1-100 items). Each node follows the same schema as POST /nodes. |
on_duplicate | string | No | "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_viewtitlevalue
on_duplicate | Behavior |
|---|---|
skip | Duplicate nodes are silently skipped and returned in the skipped array. |
overwrite | Duplicate 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
}
}
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.
- IDENTITY meaning_level:
node_typemust be one ofEXPERIENCE,BELIEF,ENTITY, orGOAL - DATA / CONTEXT meaning_level:
node_typecan be any string (e.g.document,log,meeting_notes,recipe) - If
meaning_levelis omitted, it defaults toCONTEXT
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:
| Parameter | Type | Description |
|---|---|---|
schema_ids | string | Comma-separated schema UUIDs. Use __freeform__ for nodes with no schema. |
tag_ids | string | Comma-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:
| Parameter | Type | Description |
|---|---|---|
node_id | string | Filter to a specific node's history |
limit | integer | Max 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:
| Field | Type | Required | Description |
|---|---|---|---|
node_ids | array | Yes | Array 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:
| Parameter | Type | Default | Description |
|---|---|---|---|
direction | string | both | outgoing, incoming, or both |
relationship_type | string | - | 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
}
Embeddings & Vector Search
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:
| Field | Type | Default | Description |
|---|---|---|---|
embedding | array | required | Query vector |
limit | integer | 10 | Max results (max 100) |
min_score | float | 0.0 | Minimum similarity score |
graph_view | string | identity | identity 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:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 100 | Number of versions to return |
offset | integer | 0 | Pagination offset |
include_snapshots | boolean | false | Include full node snapshots |
start_date | datetime | - | Filter from date |
end_date | datetime | - | 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):
| Field | Type | Required | Description |
|---|---|---|---|
file | binary | Yes | The file to upload |
tags | JSON string | No | JSON array of tag IDs or tag names |
metadata | JSON string | No | Additional metadata object |
The endpoint auto-classifies:
data_type: text, image, audio, video, document, or otherformat: file extension (pdf, csv, json, etc.)structure_type:structured(json, csv, xml, yaml, tsv, parquet) orunstructured
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 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
- Agent Infrastructure - Multi-agent collaboration
- Node History - Version tracking
- Relationships - Connect nodes
- Tags - Organize nodes