Skip to content
Graphs Reference API Keys Team Settings

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

ANDLogical and
ORLogical or
XORExclusive or
NOTNegation

Arithmetic

+ - * / %Standard math
^Power
+String concatenation

String

STARTS WITHPrefix match
ENDS WITHSuffix match
CONTAINSSubstring match
=~Regex (full-string, JS syntax)

Null & List

IS NULLNull check
IS NOT NULLNon-null check
INList membership

Aggregate Functions

Aggregate functions group rows and reduce them. All support DISTINCT: count(DISTINCT x).

FunctionDescription
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

FunctionDescription
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

FunctionDescription
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)
FunctionDescription
sin cos tanTrigonometric
asin acos atan atan2Inverse trig
degrees(x) / radians(x)Angle conversion
pi() / e()Constants

List Functions

FunctionDescription
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

FunctionDescription
toInteger(val) / toFloat(val) / toBoolean(val)Type conversion
toStringOrNull / toIntegerOrNull / toFloatOrNull / toBooleanOrNullNull-safe conversion
valueType(val)Type name string

Graph Functions

FunctionDescription
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

FieldTypeDescription
graphstringRequiredGraph name
cypherstringRequiredCypher query
parametersobjectOptionalQuery parameters ($param syntax)
atTimestringOptionalISO 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.

FieldTypeDescription
graphstringTarget graph name
queriesarrayArray of query objects or strings (max 1,000)
queries[].cypherstringCypher statement
queries[].parametersobjectOptional 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.

FieldTypeDescription
namestringRequiredLetters, numbers, hyphens, underscores. Max 64 chars.
descriptionstringOptionalDescription

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:

ModeBehavior
OFFNo validation (default)
WARNWrites succeed but response includes warnings array
STRICTInvalid 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.

FieldTypeDescription
kindstringRequired"node" or "edge"
labelstringNode label (required when kind=node)
relTypestringEdge type (required when kind=edge)
modestringOFF | WARN | STRICT
propertiesobjectMap 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.

FieldTypeDescription
emailstringRequiredEmail address
rolestringOptionalowner or member (default)

DELETE /users/{userId}

Remove a user. Requires owner role.

Errors

{ "status": 400, "error": "graph and cypher are required" }
StatusMeaning
200Success
201Created
400Bad request (missing fields, invalid Cypher)
401Unauthorized
403Forbidden (insufficient role)
404Not found
409Conflict (duplicate resource)
429Rate limit exceeded (200 req/min free tier)
500Internal 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

  1. Right-click a node → "Find Path From Here" (green ring)
  2. Right-click another node → "Find Path To Here"
  3. Path highlighted in blue, start in green, end in red
  4. 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

  1. Click "Temporal Diff" in the console toolbar
  2. Set T1 and T2 timestamps, click "Compare"
  3. Green = added, red dashed = removed, yellow = changed, dimmed = unchanged
  4. 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

  1. Right-click a node → "Show Similar Nodes"
  2. Reference node becomes a purple diamond
  3. Similar nodes colored: red (high) → yellow (medium) → blue (low)
  4. Hover for similarity score tooltip

Procedures

CALL db.similar('nodeId', 20) YIELD node, score
CALL db.vectorSearch('indexName', embedding, 20) YIELD node, score