gRPC API
ArcadeDB exposes a gRPC interface for high-performance, strongly-typed access from any language with gRPC support (Java, Python, Go, C++, Node.js, Rust, C#, and more). The gRPC API provides two services with 67 RPCs covering queries, record operations, transactions, graph bulk loading, vector and full-text search, time series, and server administration.
Proto definition: arcadedb-server.proto
Enabling the gRPC server
The gRPC module is bundled in the full distribution, but the server is implemented as a plugin (com.arcadedb.server.grpc.GrpcServerPlugin) that is not started by default. If the server is not listening on any gRPC port, it is because the plugin has not been registered yet.
Enable it by adding the plugin to the arcadedb.server.plugins setting, then start the server:
-Darcadedb.server.plugins=gRPC:com.arcadedb.server.grpc.GrpcServerPlugin
The format is <pluginName>:<pluginFullClass> (comma-separated when registering several plugins). Once enabled, the server listens on port 50051 by default. Confirm it started by looking for a gRPC server started line in the server log, or with grpcurl -plaintext localhost:50051 list.
Host, port, TLS, message size and the other gRPC options are the arcadedb.grpc. settings - see gRPC settings for the full list. Since v26.10.2 every one of them is a registered server setting, read like arcadedb.server.plugins from the server configuration file, from environment variables and from JVM system properties (-Darcadedb.grpc.). In v26.10.1 and earlier only arcadedb.grpc.port was; the others were read as JVM system properties only, so a grpc.tls.enabled written in the configuration file was ignored.
(Since v26.10.2.) arcadedb.grpc.host binds the standard gRPC server to every local address the name resolves to (in v26.10.1 and earlier
it was read but never applied, so the server listened on every interface); the xDS server (grpc.mode xds or
both) has no host setting and still listens on every interface.
Services
ArcadeDbService
The main service for database operations: querying, record CRUD, bulk inserts, transactions, graph bulk loading, search, and time series.
| RPC | Request | Response | Description |
|---|---|---|---|
|
|
stream |
Execute a query and stream results back row by row |
|
|
|
Execute a DDL/DML command (CREATE, INSERT, UPDATE, DELETE) |
|
|
|
Execute a query and return all results at once |
|
|
|
Create a new record (vertex, edge, or document) |
|
|
|
Update an existing record by RID |
|
|
|
Look up a record by its Record ID |
|
|
|
Delete a record by RID |
|
|
|
Insert multiple records in a single request |
|
stream |
|
Client-streaming insert for large datasets |
|
stream |
stream |
Bidirectional streaming for insert with per-record feedback |
|
|
|
Begin an explicit transaction |
|
|
|
Commit the current transaction |
|
|
|
Roll back the current transaction |
|
stream |
|
Client-streaming bulk load of vertices and edges (see below) |
|
|
|
Since 26.10.1. k-nearest-neighbor search over a dense or sparse vector index |
|
|
|
Since 26.10.1. Fused vector, full-text, and graph-expansion search |
|
|
|
Since 26.10.1. Lucene-syntax search over a |
|
|
|
Since 26.10.1. Write a batch of time-series points |
|
stream |
|
Since 26.10.1. Client-streaming time-series ingest |
|
|
stream |
Since 26.10.1. Range query, raw or aggregated into buckets, streamed back |
|
|
|
Since 26.10.1. Latest sample of a time-series type, optionally filtered by tags |
A record that a BeforeRecordCreateListener or a BeforeRecordUpdateListener vetoed was refused on purpose, and the replies say so
instead of reporting it as written. The bulk inserts (BulkInsert, InsertStream, InsertBidirectional) count it as ignored, and a
vetoed update under CONFLICT_UPDATE does not insert a second record for the key. UpdateRecord answers success = false and
updated = false, which is what the drivers return to the caller as "the record was updated". CreateRecord has to answer a RID, so
it fails with INVALID_ARGUMENT naming the refusal.
Before v26.11.1 UpdateRecord answered success = true and updated = true for an update a listener vetoed, and CreateRecord
of a vetoed edge failed with INTERNAL carrying the text of a NullPointerException.
|
Graph batch load
GraphBatchLoad streams GraphBatchChunk messages, each carrying GraphBatchRecord entries of kind VERTEX or
EDGE. The first chunk must name the database and carry the GraphBatchOptions (batch size, light edges, WAL,
commit frequency, vertex_batch_size, commit retries, and return_id_mapping); options on later chunks are ignored.
Vertices declare a temp_id, and edges reference their endpoints by temp_id or by an existing RID (#bucket:pos)
in from_ref / to_ref. All VERTEX records must precede every EDGE record across the whole stream: interleaving
is an error. On a replicated cluster the load must run on the leader; a follower refuses it.
GraphBatchResult reports vertices_created, edges_created, elapsed_ms and, depending on return_id_mapping,
the temp_id to RID map in id_mapping (when the map is not returned, id_mapping_omitted is set and
id_mapping_size still reports its size). The load commits incrementally, so a failure is not a rollback: the counts
already durable at the point of failure travel in the arcadedb-graph-batch-result-bin trailer of the failed call,
with partial_commit set.
Vector, hybrid, and full-text search
Since 26.10.1. These three unary RPCs run the same server-side implementation as the HTTP /api/v1/vector/*
routes and the MCP search tools, so a request that is legal on one protocol is legal on all of them and is rejected
with the same message. Every hit is a SearchHit with rid, record, and either distance (dense index, lower is
better) or score (sparse, full-text, or fused, higher is better); the response’s scoring field says which.
| RPC | Key request fields |
|---|---|
|
|
|
The vector fields above ( |
|
|
All three accept an optional transaction: when it names a live transaction the search reads exactly what that
transaction reads, including its uncommitted changes. A transaction id the server no longer knows is refused with
FAILED_PRECONDITION. See also Full-text index.
Time series
Since 26.10.1. The time-series RPCs read and write TIMESERIES types (see Time series).
-
TimeSeriesWrite/TimeSeriesWriteStream: eachTimeSeriesPointcarriestype(the measurement, defaulting to the request or chunktype),timestamp, andtags/fieldsmaps ofGrpcValue.precisiondefaults toTS_PRECISION_MILLISECONDS(unlike the HTTP line-protocol endpoint, which defaults to nanoseconds). The streaming variant needsdatabaseon the first chunk only and applies back-pressure, pulling one chunk at a time. A write is not atomic: each measurement’s batch commits as it is appended.TimeSeriesWriteSummaryreportsreceived,written, anddropped(written + dropped == received), and lists dropped points' types underunknown_types,non_time_series_types, orunavailable_types. (Since v26.10.2.)undeclared_keys(field 8) lists, as<measurement>.<key> (tag|field), the tag or field keys a measurement’sTIMESERIEStype does not declare in that role: the points carrying them are dropped unless the database setsarcadedb.timeSeriesUndeclaredKeys=ignore.unreadable_values(field 9) lists, as<measurement>.<column> (tag|field): <reason>, values a column cannot store. Both lists are capped at 100 entries, whiledroppedstill counts every point. The HTTP line-protocol endpoint reports the same lists asundeclaredKeysandunreadableValuesin its400partial-write body, described by theTimeSeriesWriteErrorschema of the OpenAPI definition. -
TimeSeriesQuery:type, inclusivefrom_timestamp/to_timestamp(unset means unbounded),fieldsprojection (an unknown name is refused withINVALID_ARGUMENT),tags(TimeSeriesTagFilter, a conjunction of equality predicates on TAG columns; a non-TAG name is refused withINVALID_ARGUMENT),limit,batch_size(rows per streamed message, default 1000), and optionalaggregation(bucket_interval_ms> 0, at least oneTimeSeriesAggregationRequestofTS_AGG_SUM,AVG,MIN,MAXorCOUNT, and an optionalbucket_origin_ms). The answer streams asTimeSeriesQueryResultmessages carryingrows(raw) orbuckets(aggregated); the last message haslastset, andtruncatedwhen the limit cut the answer short. The server-side ceilingarcadedb.server.grpcTimeSeriesMaxResultRows(default 1000000) cannot be raised by the client: alimitabove it is refused withRESOURCE_EXHAUSTEDbefore the first message. -
TimeSeriesLatest:typeand optionaltags.TimeSeriesLatestResponsereturnscolumns,found, and thelatestrow (unset whenfoundisfalse).
TimeSeriesQuery and TimeSeriesLatest accept an optional transaction; when set, the read sees that
transaction’s uncommitted points. An unresolvable transaction id is refused with FAILED_PRECONDITION (the HTTP
time-series read routes instead fall back to reading outside the transaction).
ArcadeDbAdminService
Administration service for server and database management. Every admin RPC runs the same implementation as the HTTP control plane, so the two protocols behave the same way.
Authentication and authorization. Every request is authenticated from its credentials field, except Health
and Ready, which are unauthenticated like GET /api/v1/health and GET /api/v1/ready. Ping, GetServerInfo,
ListDatabases, ExistsDatabase, and GetDatabaseInfo need any valid account; GetProgress needs an account
granted the named database; every other RPC requires the root user and otherwise fails with PERMISSION_DENIED.
Leader routing. On a replicated cluster, CreateDatabase, DropDatabase, CreateUser, UpdateUser,
DeleteUser, SaveGroup, DeleteGroup, CreateApiToken, DeleteApiToken, RestoreBackup, RestoreDatabase,
and ImportDatabase are refused on a follower with FAILED_PRECONDITION and the leader’s address in the
arcadedb-leader- trailers. gRPC has no request proxy, so the client redirects itself. (Since v26.10.2.) The
streaming inserts of ArcadeDbService (InsertStream and InsertBidirectional, and the rows of a BulkInsert)
report a write a follower refused differently: the refused row or commit comes back as an InsertError with code
NOT_LEADER and the leader’s address in leader_address, rather than through the arcadedb-leader- trailers (see
Insert operations).
Server info and databases
| RPC | Request | Response | Description |
|---|---|---|---|
|
|
|
Health check |
|
|
|
Server version, uptime, and configuration |
|
|
|
List all databases |
|
|
|
Check if a database exists |
|
|
|
Create a new database (see below) |
|
|
|
Drop an existing database (see below) |
|
|
|
Since 26.10.1. Open the named database |
|
|
|
Since 26.10.1. Close the named database’s open instance; the files stay, and a later request reopens it |
|
|
|
Since 26.10.1. Run |
|
|
|
Schema, types, and database statistics |
|
|
|
Since 26.10.1. Long-running operations (CHECK DATABASE, REBUILD INDEX, backup, import, …) running on the database, as |
GetDatabaseInfoResponse.records is the sum of the record counts of the database’s types, read from the bucket counters
rather than by a scan. (Since v26.11.1) a type whose records cannot be counted - a time series type whose storage engine
did not open, for one - is named in uncounted_types and left out of the sum, so a non-empty list says the total is short
by an unknown amount rather than that the database holds fewer records; and a schema that cannot be read fails the call
instead of answering zero classes (issue #10877).
CreateDatabase and DropDatabase answer the no-op case the way the HTTP create database and drop database
commands do: a name already taken is refused with ALREADY_EXISTS, a name that does not exist with NOT_FOUND.
Set if_not_exists on CreateDatabaseRequest, or if_exists on DropDatabaseRequest, to make the call idempotent
instead. Either way the response says what happened: CreateDatabaseResponse.created is true only when this call
created the database, and DropDatabaseResponse.dropped only when it dropped one, so a provisioning tool can tell
"created for me" from "already there" rather than reading an empty OK as ownership. Database names are exact and
never case-folded. The Java client exposes the two forms as createDatabase / createDatabaseIfMissing and
dropDatabase / dropDatabaseIfExists; the IfMissing / IfExists variants return the boolean.
Users, groups, and API tokens
| RPC | Request | Response | Description |
|---|---|---|---|
|
|
|
Create a new user. A name already taken is |
|
|
|
Since 26.10.1. Update a user’s |
|
|
|
Delete a user |
|
|
|
Since 26.10.1. List users with their per-database groups. Password hashes are never included |
|
|
|
Since 26.10.1. The whole group document, as JSON in |
|
|
|
Since 26.10.1. Create or replace one group of a database ( |
|
|
|
Since 26.10.1. Delete one group of a database |
|
|
|
Since 26.10.1. List API tokens as |
|
|
|
Since 26.10.1. Mint an API token. The plaintext |
|
|
|
Since 26.10.1. Revoke a token by its |
Settings
| RPC | Request | Response | Description |
|---|---|---|---|
|
|
|
Since 26.10.1. Set a server setting. |
|
|
|
Since 26.10.1. Set a setting of one database |
Backup, restore, and import
| RPC | Request | Response | Description |
|---|---|---|---|
|
|
|
Since 26.10.1. Auto-backup configuration as JSON, and whether the auto-backup plugin is running |
|
|
|
Since 26.10.1. Save the auto-backup configuration from |
|
|
|
Since 26.10.1. Backup archives of a database, with retention totals |
|
|
|
Since 26.10.1. Back up a database now; returns the archive path |
|
|
|
Since 26.10.1. Delete one archive by plain file name (path separators and traversal are refused) |
|
|
stream |
Since 26.10.1. Restore a database’s backup archive into |
|
|
stream |
Since 26.10.1. Create a new database from an archive at a |
|
|
stream |
Since 26.10.1. Create a new database and import the source at a |
The three restore and import RPCs are server-streaming: they send progress messages while the operation runs, then
one final message with completed = true (ImportProgress.result_json carries the importer’s final report). A
failure ends the stream with an error status instead. Cancelling the call stops the progress messages, not the
operation. Unless arcadedb.server.restoreImportAllowLocalUrls is enabled, only http/https URLs to non-private
hosts are accepted. See also Backup.
Query profiler
| RPC | Request | Response | Description |
|---|---|---|---|
|
|
|
Since 26.10.1. Start recording. |
|
|
|
Since 26.10.1. Stop recording and return the run as JSON |
|
|
|
Since 26.10.1. Reset the profiler |
|
|
|
Since 26.10.1. Current profiler results as JSON, without stopping |
|
|
|
Since 26.10.1. Saved profiler runs on disk, newest first |
|
|
|
Since 26.10.1. Load a saved run by file name |
Server lifecycle and cluster
| RPC | Request | Response | Description |
|---|---|---|---|
|
|
|
Since 26.10.1. Server events as a JSON array, from the current event file or a named one, plus the list of event files |
|
|
|
Since 26.10.1. Shut down this server (empty |
|
|
|
Since 26.10.1. Disconnect this server from the cluster. |
|
|
|
Since 26.10.2. Root only. Lifts the quarantine, and its read floor, standing on |
|
|
|
Since 26.10.2. Root only. Lifts the node-wide stale-snapshot read floor of a node that is the only voter of its cluster, accepting its databases as they are without a resync. The response carries |
|
|
|
Since 26.10.1. Join the server at |
|
|
|
Since 26.10.1. Open HTTP authentication sessions, including their bearer tokens. Enable TLS before calling it across an untrusted network |
Health probes
| RPC | Request | Response | Description |
|---|---|---|---|
|
|
|
Since 26.10.1. Unauthenticated liveness probe; |
|
|
|
Since 26.10.1. Unauthenticated readiness probe; a not-ready node answers |
Error statuses
A refused admin call answers the status of the refusal whatever the server wrapped it in on its way out: a name
already taken is ALREADY_EXISTS, a user, group or token that does not exist NOT_FOUND, a security policy
refusing an argument PERMISSION_DENIED, failed authentication UNAUTHENTICATED, a backup, restore or import
already running on the database ABORTED, a malformed argument INVALID_ARGUMENT, an operation this server’s
configuration does not support FAILED_PRECONDITION, and a transient failure such as a Raft quorum not reached
UNAVAILABLE, which a client retries. The description is the refusal’s own sentence, also in production mode, except
for a generic argument, authentication or retry failure raised underneath another error: that one keeps its status
but its text is concealed in production mode, because it may come from a library and carry internal details. A
statement stopped on request is CANCELLED, as on the data-plane RPCs (see Cancellation).
Only an unexpected server fault is INTERNAL, and in production mode its description is concealed.
On the data-plane RPCs, the arcadedb-exception-class trailer names the exception that decided the status rather
than the one wrapped around it, so the Java client rebuilds the engine’s own type: a SchemaException for a missing
type (not the RecordNotFoundException that NOT_FOUND alone reads as), or a DuplicatedKeyException carrying its
index name.
(Since v26.11.1) A command that creates, or renames onto, a schema object name already taken (a type, property, index, bucket, view,
…) answers ALREADY_EXISTS, with the arcadedb-already-exists-kind trailer naming the kind. The Java client rebuilds it as
AlreadyExistsException, a SchemaException. Before, it was INTERNAL, or NOT_FOUND for some of them. The admin RPCs answer
a taken user, token or database name as before, and the Java client raises RemoteAlreadyExistsException for it on every admin
call, ImportDatabase included.
|
In v26.10.2 and earlier, only the leader-redirect refusal was recognised under a wrapper. Any other refusal the
server raised under one (for example inside a commit or a backup) answered |
Cancellation and running statements
Since 26.10.2. The ArcadeDbService RPCs (such as ExecuteQuery, ExecuteCommand, StreamQuery, BulkInsert,
the search RPCs and TimeSeriesQuery, but not BeginTransaction and RollbackTransaction) are listed, while they
run, by the HTTP list queries server command and by Cypher
SHOW TRANSACTIONS, with protocol grpc, and can be stopped by terminate query,
TERMINATE TRANSACTIONS or Studio. A BulkInsert stops between two rows.
The client-streaming loads (InsertStream, InsertBidirectional, GraphBatchLoad and TimeSeriesWriteStream) are
listed from the first chunk, which names the database, to the end of the stream, including while the stream is idle
between two chunks, and terminate query stops them there too. A terminated load ends with status CANCELLED, and
what the load already wrote depends on its transaction mode:
-
PER_STREAMandPER_REQUEST: the open transaction is rolled back, so nothing the stream sent is committed. -
PER_BATCH: the open batch is rolled back; batches committed before the termination stay committed. -
PER_ROW: the rows committed before the termination stay, the rest are not applied. -
A load that writes into a client transaction (
BeginTransaction) rolls that transaction back and ends it. -
GraphBatchLoadcommits incrementally: what is already durable is reported in thearcadedb-graph-batch-result-bintrailer withpartial_commitset.TimeSeriesWriteStreamstops appending; batches already appended stay.
In v26.10.1 and earlier the client-streaming loads were not listed and could not be terminated.
A client that cancels a call, or whose deadline expires, or whose connection drops, terminates the server-side work too: the server stops it at its next check instead of running it to the end for an answer nobody reads.
A terminated call ends with status CANCELLED and the trailer arcadedb-exception-class set to
com.arcadedb.exception.QueryTerminatedException; the gRPC Java client raises that exception, which it does not retry.
A statement terminated inside a client-managed transaction (BeginTransaction) ends that transaction: it is rolled
back, and a later CommitTransaction answers committed = false.
Message Types
Connection & Authentication
|
Database name, username, and password for per-request authentication |
Value Types
The GrpcValue message uses a oneof to represent any ArcadeDB value:
| Field | ArcadeDB Type |
|---|---|
|
Boolean |
|
Integer |
|
Long |
|
Float |
|
Double |
|
String |
|
Binary / byte array |
|
Datetime (google.protobuf.Timestamp) |
|
List / array |
|
Map / embedded document |
|
RID reference (bucket:position) |
|
Decimal (string-encoded) |
|
Null |
A GrpcValue can also carry a logical_type tag that says which ArcadeDB type it stands for. A client that ignores the
tag still reads the plain string or timestamp.
logical_type |
Carried as |
|---|---|
|
|
|
|
|
|
|
|
Text tagged with one of these types that cannot be parsed is returned as a plain string.
GrpcRecord wraps a record with its RID, type name, and a map of property name to GrpcValue.
Enums
| Enum | Values |
|---|---|
|
|
|
|
|
|
|
|
|
|
BeginTransaction accepts READ_COMMITTED and REPEATABLE_READ, the two levels the engine provides, and
READ_UNCOMMITTED, which is the proto default and runs at the stronger READ_COMMITTED. The level the transaction really
runs at is returned in BeginTransactionResponse.effective_isolation. SERIALIZABLE is not supported: it is refused with
INVALID_ARGUMENT, as is a value the server does not know, instead of starting the transaction at a weaker level than the
one asked for (since v26.11.1). HTTP, SQL, openCypher and PostgreSQL refuse it the same way.
|
PAGED retrieval keeps the ORDER BY your query specifies. When the query specifies none, rows are ordered by
@rid, because cutting pages needs a stable order or a row could be skipped or repeated across pages. PAGED also
reserves the parameter names _skip and _limit, and applies to SQL only: a query in another language falls back to
CURSOR. If you order by a column with duplicate values, rows that tie can still move between pages, since every page
re-runs the query; add a unique tie-break such as ORDER BY createdAt DESC, @rid when pages must be reproducible.
|
Insert Operations
|
Batch of records with type name, properties, and insert options |
|
A chunk of records for client-streaming inserts |
|
Per-record request/response for bidirectional streaming |
|
Result summary: |
|
One failed row or commit, see below |
|
Transaction mode, conflict handling, and batch size configuration |
A row that cannot be applied does not fail the call: it is counted in failed and reported as an InsertError, and the
rest of the batch still goes in.
| Field | Number | Meaning |
|---|---|---|
|
1 |
The engine’s error text. In |
|
2 |
Index of the row within the chunk, or |
|
3 |
Machine-readable code, see below |
|
4 |
The offending field, when known |
|
5 |
Since 26.10.2. Set only when |
| Code | Meaning |
|---|---|
|
The row’s key is already taken ( |
|
Any other failure of one row or chunk |
|
Since 26.10.2. The node is a follower and refused the write; |
|
A transaction commit failed for a reason other than a duplicated key or a leader refusal |
|
The stream broke the protocol, for example its options changed mid-stream |
|
The chunk named a transaction id the server does not know or has expired |
|
|
|
An edge row has no |
Command Results & Write Statistics
ExecuteCommand returns an ExecuteCommandResponse:
| Field | Meaning |
|---|---|
|
Whether the command succeeded, and an optional message |
|
Server-computed count of affected elements and numeric scalars |
|
Server-side execution time |
|
Optional row payload, returned when |
|
A |
QueryUpdateStats carries the same write counters the HTTP stats object and the Bolt result summary expose, including counts aggregated across OpenCypher UNION branches and CALL { … } subqueries:
| Field | Meaning |
|---|---|
|
Vertices created / deleted |
|
Edges created / deleted |
|
Properties assigned a value |
|
Type/label memberships added / removed |
|
Indexes created / dropped |
|
Constraints added / removed |
|
|
The equivalent HTTP response object is documented at Write statistics.
Connecting from Python
import grpc
import arcadedb_server_pb2 as pb
import arcadedb_server_pb2_grpc as rpc
channel = grpc.insecure_channel('localhost:50051')
stub = rpc.ArcadeDbServiceStub(channel)
# Execute a query
request = pb.ExecuteQueryRequest(
credentials=pb.DatabaseCredentials(
database='mydb', username='root', password='arcadedb'
),
language='sql',
command='SELECT FROM V LIMIT 10',
)
response = stub.ExecuteQuery(request)
for record in response.records:
print(record)
Connecting from Node.js
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const packageDef = protoLoader.loadSync('arcadedb-server.proto');
const proto = grpc.loadPackageDefinition(packageDef).arcadedb;
const client = new proto.ArcadeDbService(
'localhost:50051', grpc.credentials.createInsecure()
);
client.ExecuteQuery({
credentials: { database: 'mydb', username: 'root', password: 'arcadedb' },
language: 'sql',
command: 'SELECT FROM V LIMIT 10',
}, (err, response) => {
console.log(response.records);
});
Further Reading
-
HTTP/JSON API — Alternative REST-based access
-
PostgreSQL Protocol — Alternative wire protocol