- Databases group your collections. A BYOG database appears in your dashboard and in the standard database APIs like any other HydraDB database.
- Collections are independent graphs inside a database. Queries run against exactly one collection. Collections never see each other’s data.
- Full Cypher support: reads and writes alike (
CREATE,MERGE,MATCH,SET,DELETE), plus the graph-native surface: multi-hop and variable-length traversal, relationship expansion, and shortest-path finding. Your query is sent verbatim; HydraDB never rewrites it. - Isolation is structural: Each collection is a completely separate graph
owned by your organization. There is no cross-tenant data to reach:
MATCH (n) RETURN nreturns your nodes and nothing else.
Quickstart
Authentication
Every request needs your HydraDB API key:401. Databases are scoped to the
organization that owns the API key. Another organization’s database names
are invisible to you (they behave exactly like names that don’t exist).
Endpoints
POST /byog/databases: create a database
{"database": "crm", "status": "ready"}: there is
no provisioning wait. Creating a name that already exists returns 409.
The database also shows up everywhere your other HydraDB databases do:
GET /databases lists it, GET /databases/status reports it ready,
GET /databases/collections includes its collections, and it is visible on
the dashboard. Deleting it through the standard DELETE /databases flow
removes its graph collections as well.
A BYOG database holds only Cypher graphs. Sending it to /context/ingest or
/query returns 400 with the error code BYOG_DATABASE.
POST /byog/query: run Cypher
- Collections auto-create: there is no create-collection call. The first write brings the collection into existence; reading a collection you never wrote to simply returns zero rows.
- Collection names must match
^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$. Database names have no charset restriction. - Always pass user data through
paramsrather than string-building it into the query. Parameters are bound safely and keep query plans cacheable. - Request bodies are capped at 256 KiB (
413beyond that). For bulk loads, send batches of rows withUNWIND $rows AS row CREATE ....
GET /byog/collections?database=crm: list collections
Returns the collection names that exist in the database. An unknown database
returns 404.
DELETE /byog/collections: drop one collection
DELETE /byog/databases: drop a database
POST /byog/databases) removes the database itself. The response
lists what was removed:
deleted is
false. Manage the database itself through the standard DELETE /databases.
Supported Cypher
Two kinds of work are supported: Modelling and CRUD, the day-to-day read/write surface: pattern matching, aggregation,UNWIND, WITH pipelines, MERGE, indexes
(CREATE INDEX FOR (n:Label) ON (n.prop)), and CALL { ... } subqueries.
Graph traversal and exploration: walk relationships, expand a node’s
neighborhood, follow chains of edges, and find paths between nodes:
- Multi-hop patterns: chain relationships across as many hops as you need
in one
MATCH:MATCH (a:Person)-[:KNOWS]->(b)-[:WORKS_AT]->(c:Company) RETURN c.name. - Variable-length traversal: follow a relationship an unbounded or bounded
number of hops with
*:MATCH (a:Person {name:$n})-[:KNOWS*1..4]->(reach) RETURN DISTINCT reach.namereturns everyone within four degrees. - Neighborhood expansion: pull a node’s edges and neighbors in a single
query, in any direction:
MATCH (p:Person {name:$n})-[r]-(nbr) RETURN type(r) AS rel, nbr.name AS neighbor. - Path finding:
shortestPathreturns the actual path: nodes and edges in traversal order, not just the two endpoints. See Relationships and paths. - Directed, typed, filtered traversal: restrict to outgoing (
->), incoming (<-), or either (-) edges, filter by relationship type ([:KNOWS]), and constrain node or edge properties anywhere along the walk.
400 and an
explanation. No partial writes occur; change the query before retrying.
A few dialect notes:
- Existence checks: write them as bare pattern predicates:
MATCH (p:Person) WHERE (p)-[:KNOWS]->() RETURN p.name AS name. TheEXISTS { ... }block form and theexists()function are not accepted. shortestPath: goes in aRETURNorWITHclause (notMATCH p = …) and the traversal must be directed (see the paths example above).
Traversal examples
Expand a node’s relationships, follow chains of edges, and find the shortest path between two nodes, all in plain Cypher:Expand a node's neighborhood
Multi-hop relationship expansion
Shortest path between two nodes
Filtered, typed traversal
Response format
Successful calls return the standard HydraDB envelope:POST /byog/query, data is always a JSON array of row objects, one
per result row, keyed by your RETURN column names. Unaliased expressions use
the expression text as the key: alias everything you plan to parse
(RETURN n.name AS name). A pure write with no RETURN yields data: [].
How values are rendered
Example (
RETURN n where n is a node):
- The
id/labels/relation/source_node_id/target_node_idkeys are added by the renderer. If you store a property with one of those names, it will be shadowed in the response (the stored value is unaffected). Avoid those property names or alias explicitly (RETURN n.id AS my_id). idvalues are internal and stable only within the life of a collection: they can be reused after deletions and do not survive an export/re-import. Key your application on a property you own.
Using results in your code
The examples below show how to consume results in Python and TypeScript.A minimal client
Success puts rows indata; errors put the code and message in error,
mirrored in detail, which the examples read.
Prefer scalars with aliases: they parse themselves
The easiest results to consume are the ones you shape in the query. Instead of returning whole nodes and digging properties out client-side, project exactly the fields you need, aliased:When you do return nodes
A returned node is one flat object: its properties merged withid and
labels. There is no nesting to unwrap:
Relationships and paths
A relationship row carries its endpoints assource_node_id /
target_node_id, which match the id of nodes returned in the same
query. Return both sides and join client-side:
nodes and edges in traversal order.
Remember the shortestPath rules above (in RETURN or
WITH, and directed):
Pagination
Result sets past the deployment cap are silently truncated, so any read that could be large should page. A stableORDER BY makes pages consistent:
Bulk loading
Chunk rows to stay inside the 256 KiB body cap and the 30 s write budget;MERGE on your own key makes the load re-runnable after a failure:
Handling failures
400: the message tells you what to fix, either your Cypher (compiler feedback is passed through) or a query that needsLIMIT/an index (budget timeout). Retrying unchanged will fail identically.429/500: transient; retry with backoff. Writes built onMERGE(as above) are safe to retry; bareCREATEbatches are not idempotent, so a retried chunk can duplicate nodes (one more reason to key on your own id).- A write with no
RETURNsucceeds withdata: []. Don’t treat empty as failure.
Errors
Errors use the standard HydraDB error envelope, with the code and message mirrored in adetail object:
Limits & timeouts
A query counts as a write (and gets the larger budget) when it contains
any write clause:
CREATE, MERGE, SET, DELETE, REMOVE, FOREACH.
Paginate large reads with a stable ORDER BY (see Pagination); without one, rows dropped at the result-set cap are arbitrary. Create indexes for properties you filter on (CREATE INDEX FOR (n:Person) ON (n.name)): long-running reads are usually missing one.
Migrating from Neo4j
Most application Cypher ports directly. The differences you are most likely to notice:CALL db.*/CALL apoc.*procedures are not available. The equivalents are either plain Cypher or not part of the supported surface.LOAD CSVis not available. Batch data in throughparams.- Internal node ids are not portable (true in Neo4j as well). Migrate using
your own key properties, e.g.
UNWIND $rows AS row MERGE (n:Person {ext_id: row.ext_id}) SET n += row.
