Reference
Clauses
MATCH
Finds patterns in the graph. Variables are bound to matched nodes and relationships.
MATCH (n:Person {name: 'Alice'}) RETURN n MATCH (a:Person)-[:FRIEND]->(b:Person) RETURN a, b
OPTIONAL MATCH
Like MATCH, but returns null for unmatched variables instead of eliminating the row. A WHERE directly after OPTIONAL MATCH filters within the optional pattern; use WITH to bridge if you want a hard post-filter.
MATCH (a:Person) OPTIONAL MATCH (a)-[:FRIEND]->(b) RETURN a.name, b.name // Hard post-filter (drops rows without a matching friend): MATCH (a:Person) OPTIONAL MATCH (a)-[:FRIEND]->(b) WITH a, b WHERE b.age > 30 RETURN a.name, b.name
WHERE
Filters results. Used after MATCH or WITH.
MATCH (n:Person) WHERE n.age > 30 RETURN n MATCH (n:Person) WHERE n.name STARTS WITH 'A' AND n.age >= 25 RETURN n
RETURN
Projects output columns. Supports aliases, DISTINCT, and * for all variables.
MATCH (n:Person) RETURN n.name AS personName MATCH (n:Person) RETURN DISTINCT n.city MATCH (n:Person) RETURN *
WITH
Pipes intermediate results between query parts. Same syntax as RETURN.
MATCH (n:Person) WITH n.name AS name, n.age AS age WHERE age > 30 RETURN name
ORDER BY / SKIP / LIMIT
Sorting and pagination. SKIP and LIMIT accept integers or $parameter refs.
MATCH (n:Person) RETURN n.name ORDER BY n.age DESC MATCH (n:Person) RETURN n SKIP 5 LIMIT 10
CREATE
Creates nodes and relationships.
CREATE (n:Person {name: 'Alice', age: 30}) CREATE (a)-[:FRIEND {since: 2020}]->(b)
MERGE
Match-or-create. Finds the pattern if it exists, creates it if not.
MERGE (p:Person {name: 'Alice'}) ON CREATE SET p.created = timestamp() ON MATCH SET p.lastSeen = timestamp()
SET
Updates properties on nodes or relationships.
MATCH (n:Person {name: 'Alice'}) SET n.age = 31 MATCH (n:Person {name: 'Alice'}) SET n += {city: 'NYC'}
DELETE / DETACH DELETE
Removes nodes and relationships. DETACH DELETE also removes connected edges.
MATCH (n:Person {name: 'Alice'}) DELETE n MATCH (n:Person {name: 'Alice'}) DETACH DELETE n
REMOVE
Removes properties from nodes.
MATCH (n:Person) REMOVE n.age
UNWIND
Expands a list into individual rows.
UNWIND [1, 2, 3] AS x RETURN x UNWIND [{name: 'Alice'}, {name: 'Bob'}] AS props CREATE (p:Person) SET p.name = props.name
FOREACH
Iterates over a list and executes mutation clauses per element.
MATCH (a:Person {name: 'Alice'}) FOREACH (name IN ['Bob', 'Carol'] | CREATE (a)-[:FRIEND]->(:Person {name: name}) )
CALL { subquery }
Runs a subquery for each incoming row.
MATCH (p:Person) CALL { WITH p MATCH (p)-[:FRIEND]->(f) RETURN count(f) AS friendCount } RETURN p.name, friendCount
UNION / UNION ALL
Combines results from multiple queries. UNION deduplicates; UNION ALL keeps all.
MATCH (a:Person) RETURN a.name AS name UNION MATCH (b:Company) RETURN b.name AS name
CREATE INDEX / DROP INDEX
CREATE INDEX ON :Person(name) DROP INDEX ON :Person(name)
Property index over current values. On bitemporal graphs (config.bitemporal: true), CREATE INDEX also builds a temporal property index — a history-aware structure that supports AT and AT VALID / AT RECORDED predicates soundly. Without it, the naive "current index → filter by time" approach silently misses nodes whose past value matched but current value differs.
-- Valid-time query, uses the temporal index MATCH (n:Person {status: 'active'}) AT '2025-03-15' RETURN n -- Full bitemporal query — 2D filter via the temporal index MATCH (n:Policy {status: 'active'}) AT VALID '2025-03-15' AT RECORDED '2025-06-01' RETURN n
The temporal index stores 2D rectangles over (valid-time × recorded-time), is built lazily on first query per (label, prop), persisted to disk on compaction, and evicted on mutations. EXPLAIN shows TemporalIndexSeek when the temporal path is taken, IndexSeek for current-time lookups, and LabelScan with a reason otherwise.
SHOW Commands
SHOW INDEXES -- list all property indexes SHOW LABELS -- list all node labels SHOW RELATIONSHIP TYPES -- list all edge types SHOW CONSTRAINTS -- list unique constraints
EXPLAIN
EXPLAIN MATCH (n:Person) RETURN n -- returns the execution plan without running the query
Built-in Procedures
CALL db.schema() -- graph schema: labels, types CALL db.schema.visualization() -- renderable schema: one node per label, one edge per type CALL db.indexStats() -- per-index entry counts CALL db.rebuildIndex('Person', 'name') -- rebuild an index CALL db.procedures() -- list all available procedures CALL db.index.fulltext.queryNodes('Article', 'title', 'graph database') YIELD node, score RETURN node.title, score -- full-text search with relevance scoring CALL db.index.vector.queryNodes('Doc', 'embedding', [0.1, 0.9], 5) YIELD node, score RETURN node.title, score -- vector similarity (cosine) search, top-K results -- Temporal procedures CALL db.changes('2025-01-01T00:00:00Z', '2025-03-01T00:00:00Z') YIELD timestamp, operation, entityType, id, labels, properties -- audit log: all mutations in a time range CALL db.diff('2025-01-01T00:00:00Z', '2025-03-01T00:00:00Z') YIELD entityId, entityType, changeType, label, property, before, after -- snapshot diff: one row per change (added/removed/changed) between two points MATCH (p:Person {name: 'Alice'}) CALL db.history(id(p)) YIELD version, operation, validFrom, validTo, properties -- full version history of an entity MATCH (p:Policy {id: $id}) CALL db.propertyHistory(id(p), 'premium') YIELD value, validFrom, validTo -- value intervals for a specific property -- Graph embeddings CALL db.materializeEmbeddings('Claim') YIELD label, nodesUpdated, dimensions, embeddingAt -- precompute 128-dim structural embeddings for all nodes of a label MATCH (c:Claim {id: 'claim_123'}) CALL db.computeEmbedding(c) YIELD nodeId, embedding, dimensions -- compute a node's structural fingerprint (does not store it) MATCH (c:Claim {id: 'claim_123'}) CALL db.similar(c, 10) YIELD node, score -- top-K structurally similar nodes (cosine similarity) -- Cosine similarity between two vectors RETURN db.vectorSimilarity(a._embedding, b._embedding) AS similarity -- Graph algorithms (GDS-style analytics) CALL db.pageRank({iterations: 20, dampingFactor: 0.85}) YIELD nodeId, score -- PageRank centrality CALL db.degreeCentrality({direction: 'both'}) YIELD nodeId, score -- degree centrality (in/out/both) CALL db.connectedComponents() YIELD nodeId, componentId, componentSize -- union-find connected components CALL db.labelPropagation({iterations: 10}) YIELD nodeId, communityId, communitySize -- community detection via label propagation CALL db.shortestPath('sourceId', 'targetId', {weightProperty: 'cost'}) YIELD found, path, totalWeight, nodeCount, relationshipCount -- BFS (unweighted) or Dijkstra (weighted) CALL db.linkPrediction.commonNeighbors('id1', 'id2') YIELD score -- shared neighbor count between two nodes CALL db.linkPrediction.adamicAdar('id1', 'id2') YIELD score -- Adamic-Adar index (weighted common neighbors) CALL db.linkPrediction.jaccard('id1', 'id2') YIELD score -- Jaccard index (intersection/union of neighbor sets) CALL db.linkPrediction.preferentialAttachment('id1', 'id2') YIELD score -- preferential attachment (product of degrees) MATCH (c:Claim {id: 'claim_123'}) CALL db.linkPrediction.predict(c, 10, { algorithm: 'adamicAdar', candidateLabel: 'Claim', explain: true }) YIELD node, score, sharedNeighbors, contributions -- top-K link predictions with explainability CALL db.knnGraph({label: 'Claim', k: 5}) YIELD node1, node2, similarity -- kNN graph from embedding cosine similarity CALL db.triangleCount() YIELD nodeId, triangles, coefficient -- triangle count + local clustering coefficient -- Temporal: run any algorithm at a specific time CALL db.pageRank({atTime: '2025-01-01T00:00:00Z'}) YIELD nodeId, score -- PageRank against graph state at that time CALL db.algorithm.diff('pageRank', '2025-01-01T00:00:00Z', '2025-07-01T00:00:00Z') YIELD nodeId, change, t1Value, t2Value, delta -- diff algorithm results between two points in time -- Anomaly scoring CALL db.anomalyScore({labels: ['Claim'], method: 'structural'}) YIELD nodeId, anomalyScore, details -- anomaly detection: degree z-score, community mismatch, or structural composite -- methods: 'degree', 'community', 'structural' (default) -- Derived feature pipelines CALL db.pipeline([ {type: 'materializeEmbeddings', label: 'Claim'}, {type: 'pageRank', labels: ['Claim'], writeProperty: 'pagerank'}, {type: 'knnGraph', label: 'Claim', k: 5, writeRelationshipType: 'SIMILAR_TO'}, {type: 'anomalyScore', labels: ['Claim'], method: 'structural'} ]) YIELD step, ms, rowsProcessed -- run a sequence of algorithm steps; returns execution log
Patterns
Node Patterns
() -- anonymous node (n) -- bound to variable n (n:Person) -- with label (n:Person:Employee) -- multiple labels (n:Person {name: 'Alice'}) -- with property filter
Relationship Patterns
-[r:FRIEND]-> -- outgoing, typed <-[r:FRIEND]- -- incoming, typed -[r:FRIEND]- -- undirected --> -- outgoing shorthand <-- -- incoming shorthand -- -- undirected shorthand -[:FRIEND|COWORKER]-> -- type alternation -[r:FRIEND {since: 2020}]-> -- with property filter
Variable-Length Paths
-[*1..3]-> -- 1 to 3 hops -[*2]-> -- exactly 2 hops -[*]-> -- 1 to 10 hops (default) -[:FRIEND*1..5]-> -- typed variable-length
Maximum depth is capped at 10.
Named Paths
p = (a:Person)-[:FRIEND*1..3]->(b:Person) RETURN p, nodes(p), relationships(p)
The path variable is an array of alternating [node, edge, node, edge, ...].
shortestPath / allShortestPaths
-- Find the shortest path between two nodes MATCH p = shortestPath((a:Person {name:'Alice'})-[*]-(b:Person {name:'Charlie'})) RETURN p, length(p) -- Find all shortest paths (all paths at minimum depth) MATCH p = allShortestPaths((a:Person)-[*]-(b:Person)) RETURN p
Uses BFS to find the shortest path(s). Supports typed relationships and property filters. Maximum depth defaults to 15.
Expressions
Literals
42 -- integer 3.14 -- float 'hello' -- string true / false -- boolean null -- null [1, 2, 3] -- list {name: 'Alice'} -- map
CASE Expression
Generic form:
CASE WHEN n.age > 30 THEN 'senior' WHEN n.age > 20 THEN 'junior' ELSE 'unknown' END
Simple form:
CASE n.status WHEN 'active' THEN 1 WHEN 'inactive' THEN 0 ELSE -1 END
List Indexing & Slicing
list[0] -- first element list[-1] -- last element list[1..3] -- slice (exclusive end)
Out-of-bounds access returns null.
List Comprehension
[x IN [1,2,3,4,5] WHERE x > 2 | x * 10] -- [30, 40, 50] [x IN range(1, 5) | x * x] -- [1, 4, 9, 16, 25]
Pattern Comprehension
-- Inline pattern matching that returns a list MATCH (a:Person) RETURN [(a)-[:FRIEND]->(f) | f.name] AS friends -- With WHERE filter MATCH (a:Person) RETURN [(a)-[:FRIEND]->(f) WHERE f.age > 25 | f.name] AS olderFriends
Evaluates a pattern inline per row and returns a list of projected values.
Map Projection
-- Select specific properties from a node MATCH (n:Person) RETURN n { .name, .age } AS person -- Mix shorthand and computed properties MATCH (n:Person) RETURN n { .name, upperName: toUpper(n.name) } AS person
Creates a map from selected properties. Shorthand .prop reads from the object; explicit key: expr evaluates an expression.
EXISTS Subquery
MATCH (n:Person) WHERE exists { MATCH (n)-[:FRIEND]->() } RETURN n
COUNT Subquery
MATCH (n:Person) RETURN n.name, count { MATCH (n)-[:FRIEND]->() } AS friendCount
Parameters
Query parameters use $name syntax and are resolved from the execution context.
MATCH (n:Person {name: $name}) RETURN n MATCH (n:Person) RETURN n LIMIT $limit
Operators
Comparison
= | Equal |
<> != | Not equal |
< > <= >= | Ordering |
Boolean
AND | Logical and |
OR | Logical or |
XOR | Exclusive or |
NOT | Negation |
Arithmetic
+ - * / % | Standard math |
^ | Power |
+ | String concatenation |
String
STARTS WITH | Prefix match |
ENDS WITH | Suffix match |
CONTAINS | Substring match |
=~ | Regex (full-string, JS syntax) |
Null & List
IS NULL | Null check |
IS NOT NULL | Non-null check |
IN | List membership |
Aggregate Functions
Aggregate functions group rows and reduce them. All support DISTINCT: count(DISTINCT x).
| Function | Description |
|---|---|
count(expr) / count(*) | Count values or rows |
collect(expr) | Collect into a list |
sum(expr) | Sum numeric values |
avg(expr) | Average |
min(expr) / max(expr) | Min / max |
stdev(expr) / stdevp(expr) | Sample / population std dev |
percentileCont(expr, pct) | Continuous percentile |
percentileDisc(expr, pct) | Discrete percentile |
String Functions
| Function | Description |
|---|---|
toLower(str) / toUpper(str) | Case conversion |
trim(str) / ltrim(str) / rtrim(str) | Whitespace trimming |
replace(str, search, repl) | Replace all occurrences |
substring(str, start, len?) | Extract substring |
left(str, n) / right(str, n) | First / last n chars |
split(str, delim) | Split into list |
reverse(str) | Reverse |
toString(val) | Convert to string |
Math Functions
| Function | Description |
|---|---|
abs(x) | Absolute value |
round(x) / floor(x) / ceil(x) | Rounding |
sign(x) | Sign (-1, 0, 1) |
sqrt(x) | Square root |
log(x) / log10(x) | Logarithm |
exp(x) | e^x |
rand() | Random [0, 1) |
| Function | Description |
|---|---|
sin cos tan | Trigonometric |
asin acos atan atan2 | Inverse trig |
degrees(x) / radians(x) | Angle conversion |
pi() / e() | Constants |
List Functions
| Function | Description |
|---|---|
head(list) / last(list) | First / last element |
tail(list) | All except first |
size(list) | Length (also strings) |
range(start, end, step?) | Integer list (inclusive) |
reverse(list) | Reverse |
coalesce(a, b, ...) | First non-null |
Type Functions
| Function | Description |
|---|---|
toInteger(val) / toFloat(val) / toBoolean(val) | Type conversion |
toStringOrNull / toIntegerOrNull / toFloatOrNull / toBooleanOrNull | Null-safe conversion |
valueType(val) | Type name string |
Graph Functions
| Function | Description |
|---|---|
id(entity) | Internal ID |
labels(node) | List of labels |
type(rel) | Relationship type |
properties(entity) / keys(entity) | Property map / keys |
startNode(rel) / endNode(rel) | Source / target node |
nodes(path) / relationships(path) | Extract from path |
length(path) | Path or list length |
timestamp() | Epoch milliseconds |
Examples
Build a social network
CREATE (alice:Person {name: 'Alice', age: 30}) CREATE (bob:Person {name: 'Bob', age: 25}) MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'}) CREATE (a)-[:FRIEND {since: 2020}]->(b)
Aggregation with grouping
MATCH (p:Person)-[:FRIEND]->(f) RETURN p.name, count(f) AS friends ORDER BY friends DESC
Batch upsert
UNWIND [{name: 'Alice'}, {name: 'Bob'}] AS props MERGE (p:Person {name: props.name}) ON CREATE SET p.status = 'new' ON MATCH SET p.status = 'existing'
Variable-length path
MATCH p = (a:Person {name: 'Alice'})-[:FRIEND*1..3]->(b) RETURN b.name, length(relationships(p)) AS distance
List comprehension
WITH [1,2,3,4,5,6,7,8,9,10] AS nums RETURN [x IN nums WHERE x % 2 = 0 | x * x] AS evenSquares
Base URL
https://api.graphiquity.com
All endpoints are relative to this base. Requests and responses use application/json.
Authentication
All authenticated requests require the Authorization header.
API Key Recommended for apps
Create an API key on the API Keys page. Pass it as a Bearer token:
Authorization: Bearer gq_a1b2c3d4e5f6...
API keys grant access to the /query endpoint. Store keys securely — they cannot be retrieved after creation.
JWT Token For web apps
Authenticate via Cognito to get an ID token:
Authorization: Bearer eyJraWQiOiJ...
JWT tokens grant access to all endpoints and expire after 1 hour.
POST /query
Execute a Cypher query against a graph. JWT API Key
Request Body
| Field | Type | Description | |
|---|---|---|---|
graph | string | Required | Graph name |
cypher | string | Required | Cypher query |
parameters | object | Optional | Query parameters ($param syntax) |
atTime | string | Optional | ISO 8601 timestamp for time travel |
Response
{
"status": 200,
"data": [
{ "name": "Alice", "age": 30 },
{ "name": "Bob", "age": 25 }
]
}
Examples
curl -X POST https://api.graphiquity.com/query \ -H "Authorization: Bearer gq_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "graph": "my-graph", "cypher": "MATCH (n:Person) WHERE n.age > $minAge RETURN n.name, n.age", "parameters": { "minAge": 25 } }'
const API_KEY = 'gq_YOUR_API_KEY'; const BASE = 'https://api.graphiquity.com'; async function query(graph, cypher, parameters = {}) { const res = await fetch(`${BASE}/query`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ graph, cypher, parameters }), }); const json = await res.json(); if (json.error) throw new Error(json.error); return json.data; } // Usage const people = await query('my-graph', 'MATCH (n:Person) WHERE n.age > $minAge RETURN n.name, n.age', { minAge: 25 } );
import requests API_KEY = "gq_YOUR_API_KEY" BASE = "https://api.graphiquity.com" def query(graph, cypher, parameters=None, at_time=None): body = {"graph": graph, "cypher": cypher} if parameters: body["parameters"] = parameters if at_time: body["atTime"] = at_time resp = requests.post( f"{BASE}/query", headers={"Authorization": f"Bearer {API_KEY}"}, json=body, ) data = resp.json() if "error" in data: raise Exception(data["error"]) return data["data"] # Usage people = query("my-graph", "MATCH (n:Person) WHERE n.age > $minAge RETURN n.name, n.age", {"minAge": 25} )
package main import ("bytes"; "encoding/json"; "fmt"; "net/http") const apiKey = "gq_YOUR_API_KEY" const baseURL = "https://api.graphiquity.com" func query(graph, cypher string, params map[string]interface{}) (map[string]interface{}, error) { body, _ := json.Marshal(map[string]interface{}{ "graph": graph, "cypher": cypher, "parameters": params, }) req, _ := http.NewRequest("POST", baseURL+"/query", bytes.NewReader(body)) req.Header.Set("Authorization", "Bearer "+apiKey) req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() var result map[string]interface{} json.NewDecoder(resp.Body).Decode(&result) return result, nil }
require 'net/http'; require 'json'; require 'uri' API_KEY = "gq_YOUR_API_KEY" BASE = "https://api.graphiquity.com" def query(graph, cypher, parameters: {}) uri = URI("#{BASE}/query") req = Net::HTTP::Post.new(uri) req["Authorization"] = "Bearer #{API_KEY}" req["Content-Type"] = "application/json" req.body = { graph: graph, cypher: cypher, parameters: parameters }.to_json res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) } JSON.parse(res.body)["data"] end
import java.net.http.*; import java.net.URI; HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://api.graphiquity.com/query")) .header("Authorization", "Bearer " + API_KEY) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(""" {"graph":"my-graph","cypher":"MATCH (n:Person) RETURN n","parameters":{}} """)).build(); HttpResponse<String> res = HttpClient.newHttpClient() .send(req, HttpResponse.BodyHandlers.ofString()); System.out.println(res.body());
using var client = new HttpClient(); client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}"); var body = JsonSerializer.Serialize(new { graph = "my-graph", cypher = "MATCH (n:Person) WHERE n.age > $minAge RETURN n", parameters = new { minAge = 25 } }); var res = await client.PostAsync("https://api.graphiquity.com/query", new StringContent(body, Encoding.UTF8, "application/json")); Console.WriteLine(await res.Content.ReadAsStringAsync());
<?php $ch = curl_init('https://api.graphiquity.com/query'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer gq_YOUR_API_KEY', 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'graph' => 'my-graph', 'cypher' => 'MATCH (n:Person) RETURN n.name', ]), ]); $result = json_decode(curl_exec($ch), true); print_r($result['data']); ?>
Creating Data
Use CREATE and MERGE via the query endpoint.
// Create a node await query('my-graph', `CREATE (p:Person {name: $name, age: $age}) RETURN p`, { name: 'Alice', age: 30 }); // Create a relationship await query('my-graph', ` MATCH (a:Person {name: $from}), (b:Person {name: $to}) CREATE (a)-[:FRIEND {since: $year}]->(b)`, { from: 'Alice', to: 'Bob', year: 2024 }); // Batch upsert await query('my-graph', ` UNWIND $people AS props MERGE (p:Person {name: props.name}) ON CREATE SET p.age = props.age`, { people: [{name:'Alice',age:30}, {name:'Bob',age:25}] });
# Create a node query("my-graph", "CREATE (p:Person {name: $name, age: $age})", {"name": "Alice", "age": 30}) # Batch upsert query("my-graph", """ UNWIND $people AS props MERGE (p:Person {name: props.name}) ON CREATE SET p.age = props.age """, {"people": [{"name":"Alice","age":30}, {"name":"Bob","age":25}]})
curl -X POST https://api.graphiquity.com/query \ -H "Authorization: Bearer gq_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"graph":"my-graph","cypher":"CREATE (:Person {name:$name})", "parameters":{"name":"Alice"}}'
Alternative Query Modes
The same graph data is queryable via four interfaces beyond Cypher. All operate over the same storage layer and respect the same auth + tenant isolation.
POST /graphs/{name}/sql
SQL syntax translated to Cypher. Labels map to tables, properties to columns. Response includes the generated Cypher for transparency.
POST /e/graphs/insurance/sql { "sql": "SELECT name, age FROM Person WHERE age > 30 ORDER BY age DESC LIMIT 10" } // Response includes: { cypher: "MATCH (Person) WHERE ...", data: [...] }
Supports SELECT, WHERE (=, !=, <, >, LIKE, IN, IS NULL), JOIN (mapped to relationships), GROUP BY, ORDER BY, LIMIT, OFFSET.
POST /graphs/{name}/graphql
Auto-generated GraphQL schema from graph metadata. Query nodes and traverse relationships with standard GraphQL syntax.
POST /e/graphs/insurance/graphql { "query": "{ Person(limit: 5) { name age } }" } // Introspect schema: GET /e/graphs/insurance/graphql/schema
Document API (REST)
RESTful CRUD over labels as document collections. No query language needed — just HTTP verbs and query parameters.
GET /e/graphs/insurance/collections // list all labels GET /e/graphs/insurance/collections/Person // all Person nodes GET /e/graphs/insurance/collections/Person?age.gt=30 // filtered GET /e/graphs/insurance/collections/Person/{id} // single node by ID
Filter operators: .gt, .lt, .gte, .lte, .contains, .startsWith, .endsWith. Pagination: limit, skip. Sorting: sort=-age.
Natural Language Query
Ask questions in plain English via the console UI (select “Natural Language” mode). Translates to Cypher using your configured AI model. Requires an AI API key in Settings.
Batch
Execute up to 1,000 queries in a single request against the same graph. Queries run sequentially and count as one rate-limit hit. Ideal for bulk data loading.
Mutation batches (containing CREATE, MERGE, SET, DELETE, etc.) are queued on the writer and processed asynchronously — the response is 202 with a jobId. Poll GET /e/jobs/{jobId}/result to retrieve results when complete. Read-only batches execute synchronously and return 200 with inline results.
| Field | Type | Description |
|---|---|---|
graph | string | Target graph name |
queries | array | Array of query objects or strings (max 1,000) |
queries[].cypher | string | Cypher statement |
queries[].parameters | object | Optional parameters for the query |
const res = await fetch('https://api.graphiquity.com/batch', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ graph: 'my-graph', queries: [ { cypher: 'CREATE (:Person {name: $name})', parameters: { name: 'Alice' } }, { cypher: 'CREATE (:Person {name: $name})', parameters: { name: 'Bob' } }, 'MATCH (a:Person {name:"Alice"}), (b:Person {name:"Bob"}) CREATE (a)-[:FRIEND]->(b)' ] }) }); const { data } = await res.json(); console.log(`${data.succeeded} succeeded, ${data.failed} failed`);
import requests res = requests.post("https://api.graphiquity.com/batch", headers={"Authorization": f"Bearer {api_key}"}, json={ "graph": "my-graph", "queries": [ {"cypher": "CREATE (:Person {name: $name})", "parameters": {"name": "Alice"}}, {"cypher": "CREATE (:Person {name: $name})", "parameters": {"name": "Bob"}}, ] }) data = res.json()["data"] print(f'{data["succeeded"]} succeeded, {data["failed"]} failed')
curl -X POST https://api.graphiquity.com/batch \ -H "Authorization: Bearer gq_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"graph":"my-graph","queries":[ {"cypher":"CREATE (:Person {name:$name})","parameters":{"name":"Alice"}}, {"cypher":"CREATE (:Person {name:$name})","parameters":{"name":"Bob"}} ]}'
Response (read-only batch)
{
"status": 200,
"data": {
"results": [
{ "data": [{ "n.name": "Alice" }] },
{ "data": [{ "n.name": "Bob" }] }
],
"succeeded": 2,
"failed": 0,
"total": 2
}
}
Response (mutation batch — async)
// Initial response (202) { "status": 202, "data": { "jobId": "batch_1710000000000_abc123", "status": "queued", "queryCount": 25, "position": 1 } } // Poll GET /e/jobs/batch_1710000000000_abc123/result for completion
Temporal Queries (Time Travel)
Every mutation is versioned automatically. Query the past, diff snapshots, and audit changes — no configuration needed.
Temporal Functions
temporal.validAt(entity, '2024-01-01T00:00:00Z') -- true if entity existed then temporal.age(entity) -- ms since creation temporal.overlaps(e1, e2) -- true if validity periods overlap
AT Syntax
Use AT directly in Cypher — no API options needed.
MATCH (p:Person) AT '2025-01-01T00:00:00Z' RETURN p MATCH (p:Policy) AT $snapshotTime WHERE p.status = 'Active' RETURN p
Property-Level History
MATCH (p:Policy {id: $id}) CALL db.propertyHistory(id(p), 'premium') YIELD value, validFrom, validTo RETURN value, validFrom, validTo
Point-in-Time API
Pass atTime as an ISO 8601 timestamp via the API.
const res = await fetch(`${BASE}/query`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ graph: 'my-graph', cypher: 'MATCH (n:Person) RETURN n.name, n.age', atTime: '2024-01-01T00:00:00Z', }), });
result = query("my-graph", "MATCH (n:Person) RETURN n", at_time="2024-01-01T00:00:00Z")
curl -X POST https://api.graphiquity.com/query \ -H "Authorization: Bearer gq_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"graph":"my-graph","cypher":"MATCH (n) RETURN n","atTime":"2024-01-01T00:00:00Z"}'
Graph Management JWT only
GET /graphs
List all graphs.
POST /graphs
Create a graph.
| Field | Type | Description | |
|---|---|---|---|
name | string | Required | Letters, numbers, hyphens, underscores. Max 64 chars. |
description | string | Optional | Description |
DELETE /graphs/{name}
Delete a graph.
POST /graphs/{newName}/clone-from/{sourceName}
Clone an existing graph into a new graph. Snapshot-aware: hardlinks .dat files where possible (zero extra disk space). Useful for “try before you commit” — clone before flipping a permanent flag like bitemporal mode.
PUT /graphs/{name}/config
Update per-graph configuration. Audit-tier flags: bitemporal (one-way enable, permanent), worm (reversible write-lock), accessLog (per-query audit log).
Audit Edition Forensics tier
Bitemporal queries, tamper-evident audit chain, schema versioning, WORM mode, access logging, and compliance dashboards. Per-graph opt-in.
Bitemporal Cypher Syntax
Two independent time axes — valid time (when a fact was true) and transaction time (when we recorded it).
MATCH (n) AT '2025-01-01' RETURN n MATCH (n) AT VALID '2025-01-01' RETURN n MATCH (n) AT RECORDED '2025-06-01' RETURN n MATCH (n) AT VALID '2025-01-01' RECORDED '2025-06-01' RETURN n
Or via API body parameters: atTime (valid) and recordedAt (transaction).
Retroactive Backdating Writes
For graphs with bitemporal mode enabled, the validFrom body parameter on POST /query backdates _valid_from on every mutation in the query. _recorded_from is still real now — letting the resolver answer "we now know X was true since Y."
POST /e/query { "graph": "insurance", "cypher": "MATCH (p:Policy {id: 'p1'}) SET p.risk_score = 21", "validFrom": "2024-06-01T00:00:00.000Z" }
Rejected on non-bitemporal graphs with a clear error. Use the existing timestamp body parameter for replay/seed flows that want both axes set to the same past moment.
Audit Procedures
CALL db.verifyAuditChain() YIELD verified, nodesChained, edgesChained, nodesHeadHash RETURN * CALL db.schemaDiff('2025-01-01', '2025-06-01') YIELD changeType, kind, name, property, before, after RETURN * CALL db.history(id(n)) YIELD version, validFrom, recordedFrom, recordedTo RETURN * -- Bitemporal db.diff: pass body recordedAt to fix the knowledge cutoff CALL db.diff('2025-01-01', '2025-03-01') YIELD entityId, changeType RETURN * -- + body { recordedAt: '2025-04-01' } → "what changed Jan→Mar as we knew on Apr 1" -- db.changes/changeRate: optional axis arg ('valid' default | 'recorded') CALL db.changes('2025-06-01', '2025-06-30', null, 'recorded') YIELD entityId, operation RETURN * -- "show me corrections recorded in this window, regardless of valid time"
WORM Mode
Write-Once-Read-Many. Locks a graph against all mutations (returns HTTP 403 with WORM_LOCKED). Reads still work. Reversible — lifting WORM is admin-audited. Toggle from the per-graph Settings tab or via:
PUT /e/graphs/{name}/config { "worm": true } // or false to lift
REST Endpoints
GET /graphs/{name}/schema/definition?at=ISO
Historical schema as it was at the specified time. Without ?at=, returns current schema.
GET /graphs/{name}/schema/history
Full schema timeline.
GET /graphs/{name}/audit/verify
Verify the tamper-evident hash chain over history/nodes.log and history/edges.log. Returns chain status, head hashes, and any tamper detection.
GET /graphs/{name}/audit/access?from=&to=&limit=
Read the per-query access log. Tenant-scoped, paginated. Most-recent first.
GET /graphs/{name}/compliance/summary
One-shot read-only aggregation of chain status, tenant boundary, audit-tier config, schema timeline, recent access, backup history, and inventory. Powers the compliance dashboard.
POST /graphs/{name}/compliance/export
Generate a tar.gz compliance bundle and upload to S3. Body: {"from": "ISO", "to": "ISO"} (both optional). Returns S3 key, manifest, and SHA-256 hashes.
API Key Management JWT only
GET /apikeys
List keys (prefixes only).
POST /apikeys
Create a key. The full key is only shown once.
// Response { "status": 201, "data": { "key": "gq_a1b2c3...", "prefix": "gq_a1b2c3d", "name": "My Key" } }
DELETE /apikeys/{prefix}
Revoke a key by prefix.
Graph Schema
GET /graphs/{name}/schema
Returns the labels and edge types present in the graph. Useful for autocomplete and introspection.
// Response { "status": 200, "data": { "labels": ["Person", "Company"], "edgeTypes": ["FRIEND", "WORKS_AT"] } }
Contracts (Schema Validation)
Contracts enforce per-label and per-edge-type schemas — type checking, required fields, unique constraints, and regex patterns. Each contract has an enforcement mode:
| Mode | Behavior |
|---|---|
OFF | No validation (default) |
WARN | Writes succeed but response includes warnings array |
STRICT | Invalid writes are rejected with HTTP 400 |
GET /graphs/{name}/contracts
List all contracts defined for the graph.
// Response { "status": 200, "data": [ { "kind": "node", "label": "Person", "mode": "STRICT", "properties": { "name": { "type": "string", "required": true, "maxLength": 200 }, "email": { "type": "string", "unique": true }, "age": { "type": "integer" } } } ] }
PUT /graphs/{name}/contracts
Create or replace a contract. The request body is the contract object.
| Field | Type | Description | |
|---|---|---|---|
kind | string | Required | "node" or "edge" |
label | string | Node label (required when kind=node) | |
relType | string | Edge type (required when kind=edge) | |
mode | string | OFF | WARN | STRICT | |
properties | object | Map of property name → definition |
Property definition fields: type (string|integer|float|boolean|timestamp|json|vector), required, unique, maxLength, pattern, enum, dims.
// JavaScript await fetch("https://api.graphiquity.com/graphs/myGraph/contracts", { method: "PUT", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, body: JSON.stringify({ kind: "node", label: "Person", mode: "STRICT", properties: { name: { type: "string", required: true, maxLength: 200 }, email: { type: "string", unique: true, pattern: "^[^@]+@[^@]+$" }, age: { type: "integer" } } }) });
# Python import requests requests.put( "https://api.graphiquity.com/graphs/myGraph/contracts", headers={"Authorization": f"Bearer {token}"}, json={ "kind": "node", "label": "Person", "mode": "STRICT", "properties": { "name": {"type": "string", "required": True, "maxLength": 200}, "email": {"type": "string", "unique": True}, "age": {"type": "integer"} } } )
DELETE /graphs/{name}/contracts
Remove a contract. Pass {"label": "Person"} for node contracts or {"relType": "FRIEND"} for edge contracts.
Cypher Constraint Syntax
You can also create and drop unique constraints via Cypher:
// Create a unique constraint CREATE CONSTRAINT ON (p:Person) ASSERT p.email IS UNIQUE // Drop a unique constraint DROP CONSTRAINT ON (p:Person) ASSERT p.email IS UNIQUE
Usage Analytics
GET /usage
Returns per-day operation counts for the tenant. Data is retained for 90 days.
// Response { "status": 200, "data": [ { "date": "2026-03-02", "operation": "query", "count": 142 }, { "date": "2026-03-02", "operation": "createGraph", "count": 1 } ] }
User Management JWT only
GET /users
List all users in the tenant.
POST /users/invite
Invite a user. Requires owner role.
| Field | Type | Description | |
|---|---|---|---|
email | string | Required | Email address |
role | string | Optional | owner or member (default) |
DELETE /users/{userId}
Remove a user. Requires owner role.
Errors
{ "status": 400, "error": "graph and cypher are required" }
| Status | Meaning |
|---|---|
200 | Success |
201 | Created |
400 | Bad request (missing fields, invalid Cypher) |
401 | Unauthorized |
403 | Forbidden (insufficient role) |
404 | Not found |
409 | Conflict (duplicate resource) |
429 | Rate limit exceeded (200 req/min free tier) |
500 | Internal error |
Large Results
Responses that exceed 6 MB are automatically offloaded to S3. Instead of inline data, the response includes a resultUrl field containing a presigned URL (valid for 15 minutes). Fetch from this URL to retrieve the full result. The console handles this transparently.
{
"status": 200,
"resultUrl": "https://graphiquity-results.s3.amazonaws.com/...",
"queryStatus": "completed",
"resultCount": 2500
}
Quick Start
1. Get an API key
Go to API Keys and create one. Copy it immediately.
2. Create a graph
Use the Dashboard or the POST /graphs endpoint.
3. Query away
const gq = (cypher, p) => fetch('https://api.graphiquity.com/query', { method: 'POST', headers: { Authorization: 'Bearer gq_YOUR_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ graph: 'my-graph', cypher, parameters: p }), }).then(r => r.json()).then(r => r.data); await gq("CREATE (:Person {name:'Alice'})-[:FRIEND]->(:Person {name:'Bob'})"); const friends = await gq("MATCH (a)-[:FRIEND]->(b) RETURN a.name, b.name"); // [{ "a.name": "Alice", "b.name": "Bob" }]
import requests def gq(cypher, p={}): return requests.post("https://api.graphiquity.com/query", headers={"Authorization": "Bearer gq_YOUR_KEY"}, json={"graph": "my-graph", "cypher": cypher, "parameters": p}).json()["data"] gq("CREATE (:Person {name:'Alice'})-[:FRIEND]->(:Person {name:'Bob'})") friends = gq("MATCH (a)-[:FRIEND]->(b) RETURN a.name, b.name") # [{"a.name": "Alice", "b.name": "Bob"}]
Graph Visualization
Interactive overlays for the graph console. Run a query in graph mode, then use these features to analyze the visible result set. Each overlay has a "Clear" button to restore the normal view.
Shortest Path Highlight
Trace the shortest path between two nodes in the graph view.
Steps
- Right-click a node → "Find Path From Here" (green ring)
- Right-click another node → "Find Path To Here"
- Path highlighted in blue, start in green, end in red
- Non-path elements dim to 15% opacity
Procedure
CALL db.shortestPath('startId', 'endId') YIELD path, distance
Temporal Diff Overlay
Compare the graph at two points in time. Highlights additions, removals, and property changes.
Steps
- Click "Temporal Diff" in the console toolbar
- Set T1 and T2 timestamps, click "Compare"
- Green = added, red dashed = removed, yellow = changed, dimmed = unchanged
- Click changed nodes to see property diffs (old → new)
Procedure
CALL db.diff('2025-01-01', '2025-06-01') YIELD entityId, entityType, changeType, label, property, before, after
Vector Similarity Clusters
Color nodes by vector similarity to a reference node. Requires a vector index on the node's label.
Steps
- Right-click a node → "Show Similar Nodes"
- Reference node becomes a purple diamond
- Similar nodes colored: red (high) → yellow (medium) → blue (low)
- Hover for similarity score tooltip
Procedures
CALL db.similar('nodeId', 20) YIELD node, score CALL db.vectorSearch('indexName', embedding, 20) YIELD node, score