Skip to content

REST API Reference ​

Release: Purple8 Hyper Graph v0.72.0 (pip install purple8-hyper-graph)
Base URL: http://localhost:8100
OpenAPI schema: GET /openapi.json
Interactive UI: GET /docs

OpenAPI info.version reports the installed package's release version (0.72.0). It is not a separate API release number. Restarting an old Docker image does not update its installed package; rebuild or pull the intended image first.

Only the Journey Engine (/journeys, /hitl) and webhooks (/webhooks) routes are versioned under /v1/ — unversioned requests to those three prefixes receive a 301 redirect to the /v1/ equivalent. Every other router (/nodes, /edges, /search, /traverse, /query, /rag, /schema, /admin, etc.) is served unversioned — do not prefix them with /v1/.

For AI agents, Purple8 also exposes 125 MCP tools across 16 dot-namespaced namespaces via the built-in MCP Server (/mcp/sse).

Authentication ​

All business endpoints require Authorization: Bearer <token>.

Login ​

http
POST /auth/login
Content-Type: application/json

{ "email": "admin@example.com", "password": "changeme" }
json
{
  "access_token": "eyJ...",
  "refresh_token": "eyJ...",
  "token_type": "bearer",
  "expires_in": 3600
}

First-run setup

If no users exist yet, GET /auth/setup-required returns true and POST /auth/setup creates the first admin user (bootstrapping — no auth required for this one call). POST /auth/signup registers additional users afterward (subject to P8G_ALLOW_SIGNUP / instance policy).

Refresh token ​

http
POST /auth/refresh
Content-Type: application/json

{ "refresh_token": "eyJ..." }

Logout ​

http
POST /auth/logout
Authorization: Bearer <token>

Current user ​

http
GET /auth/me
Authorization: Bearer <token>

Change password ​

http
POST /auth/change-password
Authorization: Bearer <token>
Content-Type: application/json

{ "current_password": "old", "new_password": "new" }

Forgot / reset password ​

http
POST /auth/forgot-password
Content-Type: application/json

{ "email": "admin@example.com" }
http
POST /auth/reset-password
Content-Type: application/json

{ "token": "...", "new_password": "new" }

Nodes ​

Create node ​

http
POST /nodes
Content-Type: application/json

{
  "node_id": "person_001",
  "node_type": "Person",
  "properties": { "name": "Alice", "age": 30 },
  "embedding": [0.1, 0.2, ...]
}

Response 200

Creation never replaces an existing node: duplicate editable IDs return 409. Use PATCH for updates. IDs must be 1-512 characters, nonblank, without leading/trailing whitespace, path separators, control or Unicode format characters. Properties support at most 64 nested levels. Invalid values return field-level 422 errors without echoing submitted values.

The primary embedding must match the authenticated tenant's default index dimension. Named vectors in embeddings have independent dimensions established when each slot's index is first created; subsequent writes must match that slot. These rules apply consistently to create, patch, and bulk requests. Vectors must be non-empty, one-dimensional, and finite in float32; violations return 422 before mutation. Bulk validation includes dimension consistency between items introducing the same new slot.

embeddings["default"] aliases the primary embedding, so conflicting values in the two fields return 422. Slot names follow the path-segment and control-character restrictions of node IDs and are limited to 255 UTF-8 bytes for filesystem persistence. Dimensions are not restricted to one embedding model.

json
{
  "node_id": "person_001",
  "labels": ["Person"],
  "properties": { "name": "Alice", "age": 30 },
  "created_at": "2026-03-05T10:00:00Z"
}

Get node ​

http
GET /nodes/{node_id}

Update node ​

http
PATCH /nodes/{node_id}
Content-Type: application/json

{
  "properties": { "age": 31 },
  "labels": ["Person", "Employee"]
}

Delete node ​

http
DELETE /nodes/{node_id}?mode=soft

Deletion tombstones the record by default. mode=hard permanently deletes it and requires an administrator. Both modes reject nodes with active edges; delete or soft-delete those edges first.

List nodes ​

http
GET /nodes?label=Person&limit=100&offset=0

Response 200

json
{
  "nodes": [...],
  "pagination": { "total": 42, "limit": 100, "offset": 0, "has_more": false }
}

Bulk create nodes ​

http
POST /nodes/bulk
Content-Type: application/json

[
  { "node_id": "p1", "node_type": "Person", "properties": { "name": "Bob" } },
  { "node_id": "p2", "node_type": "Person", "properties": { "name": "Carol" } }
]

Response 200

json
{
  "created": [...],
  "transaction_id": "...",
  "summary": { "total": 2, "succeeded": 2, "failed": 0 }
}

Edges ​

Create edge ​

http
POST /edges
Content-Type: application/json

{
  "edge_id": "knows_001",
  "edge_type": "KNOWS",
  "source_id": "person_001",
  "target_id": "person_002",
  "properties": { "since": "2024-01-01" }
}

Get edge ​

http
GET /edges/{edge_id}

Update edge ​

http
PATCH /edges/{edge_id}
Content-Type: application/json

{ "properties": { "weight": 0.9 } }

Delete edge ​

http
DELETE /edges/{edge_id}?mode=soft

As with nodes, mode=hard requires an administrator.

Batch existence check ​

http
POST /edges/exists
Content-Type: application/json

{
  "pairs": [
    { "source_id": "p1", "edge_type": "KNOWS", "target_id": "p2" }
  ]
}

Python HTTP SDK: nodes and edges ​

Purple8Client and Purple8AsyncClient expose the same node and edge operations: create, get, update, delete, list, count, and bulk_create. Both also expose edges.exists_batch.

python
from purple8_graph.client import Purple8Client

with Purple8Client("http://localhost:8100", api_key=api_key) as client:
    node = client.nodes.create(
        "person:alice",
        node_type="Person",
        labels=["Employee"],
        properties={"name": "Alice"},
        embeddings={"title": title_vector},
    )
    client.nodes.update("person:alice", embeddings={"title": updated_title_vector})
    page = client.nodes.list(label="Person", limit=20, offset=0)
    total = client.nodes.count(label="Person")
    client.nodes.bulk_create([
        {"node_id": "person:bob", "node_type": "Person"},
        {"node_id": "person:carol", "labels": ["Person"]},
    ])
    client.edges.create("knows:alice:bob", "KNOWS", "person:alice", "person:bob")
    client.edges.update("knows:alice:bob", weight=2.0)
    matches = client.edges.exists_batch([("person:alice", "KNOWS", "person:bob")])
    client.edges.delete("knows:alice:bob")  # Soft delete.

For async usage, use async with Purple8AsyncClient(...) and await each operation. Creation requires node_type or at least one label. For compatibility, the SDK infers node_type from the first label when omitted, including in bulk node dictionaries. It sends bulk requests as raw JSON arrays, not wrapper objects. Responses preserve the REST shapes: list pagination is under pagination, and bulk results contain created, transaction_id, and summary.

The authenticated credentials select the tenant. Per-call tenant_id overrides, including those inside bulk dictionaries, raise ValueError before a request. Path IDs are validated and percent-encoded, so valid IDs containing spaces, Unicode, ?, #, or literal percent sequences round-trip without changing routes. Deletes default to mode="soft"; pass mode="hard" explicitly for administrator permanent deletion. Node dependency checks still apply.

HTTP failures raise SDK exceptions with status_code and detail, including AuthError (401), ForbiddenError (403), NotFoundError (404), and ConflictError (409). Invalid successful CRUD response shapes raise Purple8Error rather than silently returning incompatible values.


http
POST /search/vector
Content-Type: application/json

{
  "embedding": [0.1, 0.2, ...],
  "k": 10,
  "labels": ["Person"]
}

Response 200

json
{
  "results": [
    { "node": { "node_id": "p1", ... }, "score": 0.98, "distance": 0.02 }
  ]
}

Hybrid search (vector + graph) ​

http
POST /search/hybrid
Content-Type: application/json

{
  "embedding": [0.1, 0.2, ...],
  "edge_types": ["KNOWS"],
  "expand_hops": 2,
  "k": 10
}

The vector, named-slot vector (POST /search/vector/{slot}), and hybrid endpoints accept exactly one of embedding or query_text. For natural-language search, send "query_text": "your question" instead of embedding; the server uses the authenticated tenant's configured RAG embedding provider and model. labels filters by node labels, and filters accepts property predicates. Tenant, label, and property restrictions apply before ANN top-k selection. Supported property operators are $eq, $ne, $lt, $lte, $gt, $gte, $in, $nin, and $exists. Unknown operators, scalar membership operands, and non-boolean $exists values return 422, including on empty indexes. Embedding dimensions must match the index (the selected slot's dimension for an existing named slot); invalid inputs return 422. For named-slot query_text, the configured provider must return vectors matching that slot; selecting a slot does not automatically change providers or their configured output dimensions. Provider failures return an explicit non-2xx response, not empty successful results. No credentials or tenant selector belong in the search body.

Hybrid edge_types filters each expansion hop, including when more than one type is supplied. It constrains relationships, not just the final result nodes. Graph and hybrid RAG endpoints also return non-2xx responses for failed storage reads rather than an empty context or a partial successful answer.

Hybrid hits contain node, vector_score, graph_score, combined_score, and hop_distance. The Query Builder's Hybrid tab uses this endpoint, while its Vector tab uses vector similarity search.


Graph traversal ​

http
POST /traverse
Content-Type: application/json

{
  "start_node_id": "person_001",
  "edge_type": "KNOWS",
  "depth": 3,
  "direction": "outgoing"
}

Response 200

json
{
  "nodes": [...],
  "edges": [...],
  "paths": [["person_001", "person_002"]]
}

Cypher query ​

http
POST /query
Content-Type: application/json

{
  "cypher": "MATCH (n:Person)-[:KNOWS]->(m) WHERE id(n) = $id RETURN m",
  "parameters": { "id": "person_001" }
}

Response 200

json
{ "results": [...], "count": 3 }

Parameters are bound as values, not interpolated into query text. Missing parameters return 400 before any query clause executes. The same request shape is accepted by POST /query/explain, which does not execute the query.


Journeys ​

Start a journey ​

http
POST /v1/journeys
Content-Type: application/json

{
  "journey_type": "onboarding",
  "entity_id": "user_42",
  "metadata": { "referrer": "signup-page" }
}

Get journey instance ​

http
GET /v1/journeys/{instance_id}

Advance journey stage ​

http
POST /v1/journeys/{instance_id}/advance
Content-Type: application/json

{ "to_stage": "email_verified", "actor": "system", "notes": "OTP confirmed" }

Cancel journey ​

http
POST /v1/journeys/{instance_id}/cancel
Content-Type: application/json

{ "actor": "admin", "reason": "user request" }

Journey audit trail ​

http
GET /v1/journeys/{instance_id}/audit

List journeys ​

http
GET /v1/journeys?journey_type=onboarding&status=active&limit=50&offset=0

RAG Studio ​

Requires an LLM provider API key to be configured.

Get RAG configuration ​

http
GET /rag/config
Authorization: Bearer <token>

Response 200

json
{
  "provider": "openai",
  "model": "gpt-5.4-mini",
  "embedding_provider": "openai",
  "embedding_model": "text-embedding-3-small",
  "chunk_strategy": "recursive",
  "chunk_size": 512,
  "chunk_overlap_percent": 10,
  "reranker": null,
  "top_k": 10,
  "temperature": 0.1
}

Update RAG configuration ​

http
PUT /rag/config
Authorization: Bearer <token>
Content-Type: application/json

{
  "provider": "anthropic",
  "model": "claude-sonnet-4-20250514",
  "chunk_strategy": "semantic",
  "chunk_size": 1024,
  "chunk_overlap_percent": 15
}

Only the fields you send are updated — omitted fields keep their current values.

RAG query ​

http
POST /rag/query
Authorization: Bearer <token>
Content-Type: application/json

{
  "query": "What are the fire safety requirements for Building 7?",
  "top_k": 5,
  "filters": { "label": "Document", "category": "safety" }
}

Response 200

json
{
  "answer": "Building 7 requires ...",
  "sources": [
    { "node_id": "doc_42", "chunk": "...", "score": 0.95 }
  ],
  "model": "claude-sonnet-4-20250514",
  "tokens_used": { "prompt": 1842, "completion": 312 }
}

Evaluate RAG pipeline ​

http
POST /rag/evaluate
Authorization: Bearer <token>
Content-Type: application/json

{
  "test_queries": [
    { "query": "What is the load capacity?", "expected_answer": "500 kN per column" }
  ]
}

Response 200

json
{
  "results": [
    {
      "query": "What is the load capacity?",
      "answer": "The load capacity is 500 kN per column ...",
      "scores": { "relevance": 0.94, "faithfulness": 0.91, "answer_similarity": 0.88 }
    }
  ],
  "aggregate": { "avg_relevance": 0.94, "avg_faithfulness": 0.91 }
}

List available models ​

http
GET /rag/models
Authorization: Bearer <token>

Response 200

json
{
  "language_models": [
    { "provider": "openai", "model": "gpt-5.4-mini", "context_window": 400000, "max_output": 128000 },
    { "provider": "anthropic", "model": "claude-sonnet-4-20250514", "context_window": 200000, "max_output": 64000 }
  ],
  "embedding_models": [
    { "provider": "openai", "model": "text-embedding-3-small", "dimensions": 1536 },
    { "provider": "cohere", "model": "embed-v4.0", "dimensions": 1536 }
  ],
  "reranker_models": [
    { "provider": "cohere", "model": "rerank-v4-pro" }
  ],
  "chunking_strategies": ["fixed", "recursive", "semantic", "sentence_window", "parent_child", "markdown_header"]
}

KMS & Storage Integrity Admin ​

Requires ADMIN / ADMIN_SETTINGS permission.

MethodPathDescription
GET/admin/kms/statusActive provider, sealed/initialized, key count
GET/admin/kms/keysList all key IDs
GET/admin/kms/keys/{key_id}Describe a key
POST/admin/kms/keys/{key_id}/rotateTrigger key rotation
POST/admin/kms/keys/{key_id}/disableDisable key
POST/admin/kms/keys/{key_id}/enableRe-enable key
POST/admin/storage/scrubRun an online, non-destructive bit-rot and CRC32 integrity verification across BrickWAL frames and cold BrickCoreStorage slabs

Online storage integrity scrub (POST /admin/storage/scrub) ​

Runs engine.scrub_integrity() online without blocking reads or requiring downtime. Verifies checksums and frame headers across active BrickWAL segments and sealed BrickCoreStorage slabs:

http
POST /admin/storage/scrub
Authorization: Bearer <admin_access_token>

Response 200

json
{
  "status": "ok",
  "corrupted_wal_frames": 0,
  "corrupted_cold_slabs": 0,
  "checked_wal_frames": 1420,
  "checked_cold_slabs": 18
}

Health & metrics ​

http
GET /health/live    # liveness probe (always 200 if process is up)
GET /health/ready   # readiness probe (200 after DB init completes)
GET /metrics        # Prometheus text/plain metrics

Error responses ​

All errors return:

json
{ "detail": "Human-readable error message" }
StatusMeaning
400Bad request / validation error
401Missing or invalid token
403Insufficient permissions
404Resource not found
409Conflict (e.g. duplicate node ID)
429Rate limit exceeded — Retry-After header included
500Internal server error

Purple8 Hyper Graph v0.72.0 · AI-Native Execution Infrastructure · NVIDIA Inception Member · Red Hat Technology Partner.