Skip to main content

REST API

Graphmind exposes a REST API on port 8080 (default). Every operation available through the SDKs is also available as an HTTP endpoint.

Base URL

http://localhost:8080

Configure with the --http-port flag or GRAPHMIND_HTTP_PORT environment variable.

Authentication

When authentication is enabled, include a bearer token in the Authorization header:

Authorization: Bearer <token>

Endpoints

POST /api/query

Execute a Cypher query (read or write).

Request body fields:

FieldTypeRequiredDescription
querystringYesThe Cypher query to execute
graphstringNoGraph namespace (default: "default")
paramsobjectNoQuery parameters as key-value pairs

Request:

curl -X POST http://localhost:8080/api/query \
-H 'Content-Type: application/json' \
-d '{"query": "MATCH (p:Person) RETURN p.name, p.age", "graph": "default"}'

Request with parameters:

curl -X POST http://localhost:8080/api/query \
-H 'Content-Type: application/json' \
-d '{
"query": "MATCH (p:Person) WHERE p.name = $name AND p.age > $minAge RETURN p.name, p.age",
"graph": "default",
"params": {
"name": "Alice",
"minAge": 25
}
}'

Response:

{
"columns": ["p.name", "p.age"],
"records": [["Alice", 30], ["Bob", 25]],
"nodes": [
{"id": "1", "labels": ["Person"], "properties": {"name": "Alice", "age": 30}},
{"id": "2", "labels": ["Person"], "properties": {"name": "Bob", "age": 25}}
],
"edges": []
}

The graph field is optional and defaults to "default". The params field is optional and allows you to pass named parameters that are substituted into the query at execution time, preventing injection and improving query plan caching.

POST /api/script

Execute a multi-statement Cypher script. Statements are separated by newlines.

Request:

curl -X POST http://localhost:8080/api/script \
-H 'Content-Type: application/json' \
-d '{
"query": "CREATE (a:Person {name: '\''Alice'\''})\nCREATE (b:Person {name: '\''Bob'\''})"
}'

Response:

{
"status": "ok",
"executed": 2,
"errors": [],
"storage": {"nodes": 2, "edges": 0}
}

POST /api/nlq

Translate a natural-language question to Cypher and execute it. Requires an LLM provider to be configured on the server.

Request:

curl -X POST http://localhost:8080/api/nlq \
-H 'Content-Type: application/json' \
-d '{"query": "Who are Alice'\''s friends?", "graph": "default"}'

Response:

{
"cypher": "MATCH (a:Person {name:'Alice'})-[:KNOWS]-(b) RETURN a, b",
"provider": "OpenAI",
"model": "gpt-4o-mini",
"columns": ["a", "b"],
"records": [["Alice", "Bob"]],
"nodes": [...],
"edges": [...]
}

GET /api/status

Server health, version, and graph statistics.

Request:

curl http://localhost:8080/api/status

Response:

{
"status": "healthy",
"version": "0.6.2",
"storage": {
"nodes": 1042,
"edges": 3891
}
}

GET /api/schema

Introspect node labels, edge types, properties, indexes, and statistics.

Request:

curl http://localhost:8080/api/schema

Response:

{
"node_types": [
{"label": "Person", "count": 42, "properties": {"name": "String", "age": "Integer"}},
{"label": "Company", "count": 5, "properties": {"name": "String"}}
],
"edge_types": [
{"type": "KNOWS", "count": 120, "source_labels": ["Person"], "target_labels": ["Person"],
"properties": {"since": "Integer"}}
],
"indexes": [
{"label": "Person", "property": "name", "type": "btree"}
],
"constraints": [],
"statistics": {
"total_nodes": 47,
"total_edges": 120,
"avg_out_degree": 2.55
}
}

POST /api/sample

Sample a subgraph for visualization. Returns a proportional subset of nodes and edges.

Request:

curl -X POST http://localhost:8080/api/sample \
-H 'Content-Type: application/json' \
-d '{"max_nodes": 200, "labels": ["Person", "Company"], "graph": "default"}'

Response:

{
"nodes": [
{"id": 1, "label": "Person", "name": "Alice", "properties": {"age": 30}},
{"id": 2, "label": "Company", "name": "Acme", "properties": {}}
],
"edges": [
{"id": 100, "source": 1, "target": 2, "type": "WORKS_AT", "properties": {}}
],
"total_nodes": 1042,
"total_edges": 3891,
"sampled_nodes": 200,
"sampled_edges": 312
}

All fields in the request body are optional. max_nodes defaults to 200 (max 1000).

POST /api/import/csv

Import nodes from CSV content.

Request:

curl -X POST http://localhost:8080/api/import/csv \
-H 'Content-Type: application/json' \
-d '{
"csv": "name,age,city\nAlice,30,Paris\nBob,25,London",
"label": "Person",
"id_column": "name",
"delimiter": ","
}'

Response:

{
"status": "ok",
"nodes_created": 2,
"label": "Person",
"columns": ["name", "age", "city"]
}

POST /api/import/json

Import nodes from JSON objects.

Request:

curl -X POST http://localhost:8080/api/import/json \
-H 'Content-Type: application/json' \
-d '{
"label": "Person",
"nodes": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
]
}'

Response:

{
"status": "ok",
"nodes_created": 2,
"label": "Person"
}

POST /api/snapshot/export

Export the entire graph as a portable .sgsnap snapshot.

Request:

curl -X POST http://localhost:8080/api/snapshot/export \
-H 'Content-Type: application/json' \
-d '{"graph": "default"}' \
--output graph.sgsnap

The response body is the binary snapshot file.

POST /api/snapshot/import

Import a previously exported .sgsnap snapshot.

Request:

curl -X POST http://localhost:8080/api/snapshot/import \
-H 'Content-Type: application/octet-stream' \
--data-binary @graph.sgsnap

Response:

{
"status": "ok",
"nodes_imported": 1042,
"edges_imported": 3891
}

GET /api/graphs

List all graph namespaces.

Request:

curl http://localhost:8080/api/graphs

Response:

{
"graphs": ["default", "tenant_acme", "tenant_globex"]
}

DELETE /api/graphs/:name

Delete a graph namespace and all its data.

Request:

curl -X DELETE http://localhost:8080/api/graphs/tenant_acme

Response:

{
"status": "ok",
"deleted": "tenant_acme"
}

GET /metrics

Prometheus-compatible metrics endpoint.

Request:

curl http://localhost:8080/metrics

Response (text/plain):

# HELP graphmind_nodes_total Total number of nodes
# TYPE graphmind_nodes_total gauge
graphmind_nodes_total 1042
# HELP graphmind_edges_total Total number of edges
# TYPE graphmind_edges_total gauge
graphmind_edges_total 3891
# HELP graphmind_query_duration_seconds Query execution time
# TYPE graphmind_query_duration_seconds histogram
...

Error Responses

All endpoints return errors in a consistent format:

{
"error": "Parse error: unexpected token at line 1, column 5"
}

HTTP status codes:

CodeMeaning
200Success
400Bad request (invalid Cypher, missing fields)
401Unauthorized (missing or invalid token)
404Not found (unknown endpoint or graph)
500Internal server error

Examples in Python (requests)

import requests

BASE = "http://localhost:8080"

# Query
resp = requests.post(f"{BASE}/api/query", json={
"query": "MATCH (p:Person) RETURN p.name, p.age",
"graph": "default"
})
data = resp.json()
print(data["records"])

# Status
status = requests.get(f"{BASE}/api/status").json()
print(f"Nodes: {status['storage']['nodes']}")

# Schema
schema = requests.get(f"{BASE}/api/schema").json()
for nt in schema["node_types"]:
print(f"{nt['label']}: {nt['count']} nodes")

# Import CSV
csv_data = "name,age\nAlice,30\nBob,25"
resp = requests.post(f"{BASE}/api/import/csv", json={
"csv": csv_data,
"label": "Person"
})
print(resp.json())

Examples in JavaScript (fetch)

const BASE = "http://localhost:8080";

// Query
const result = await fetch(`${BASE}/api/query`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: "MATCH (p:Person) RETURN p.name, p.age",
graph: "default",
}),
}).then((r) => r.json());

console.log(result.records);

// Status
const status = await fetch(`${BASE}/api/status`).then((r) => r.json());
console.log(`Nodes: ${status.storage.nodes}`);

// Schema
const schema = await fetch(`${BASE}/api/schema`).then((r) => r.json());
schema.node_types.forEach((nt) => console.log(`${nt.label}: ${nt.count}`));

// NLQ
const nlq = await fetch(`${BASE}/api/nlq`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: "Who knows Alice?" }),
}).then((r) => r.json());

console.log("Generated Cypher:", nlq.cypher);