Skip to content

Overview

RPC protocol

The RPC protocol allows for network protocol agnostic communication with SurrealDB. It is used internally by our client SDKs, and is served over WebSocket, HTTP and, from 3.3.0, gRPC. Combined with the CBOR protocol specification, the RPC protocol provides a fully type-safe and efficient way to interact with SurrealDB over the network.

SurrealDB's session variables hold session-specific data. They act as temporary storage tied to a user's active connection, for tasks such as maintaining application state, storing user preferences, or holding temporary data relevant only to the current session.

Session variables are strictly confined to the individual connection, so one user's session data remains private and does not interfere with other connections.

You can interact with session variables in the following ways:

  1. Explicit Session-Wide Management:

    • Use the let method to define a new variable or update an existing one within the current session. This variable will persist for the duration of the connection.

    • Use the unset method to remove a previously defined variable from the session.

    • The reset method, in addition to its other functions, clears all currently defined session variables, restoring the session's variable state.

  2. Implicit Request-Scoped Management:

    • Methods query, select, insert, create, upsert, update, relate, and delete, accept an optional vars parameter. This parameter is an object containing key-value pairs, where each key represents the variable name (without the leading $) and the value is the data to be assigned.

    • Variables passed via this parameter are defined only for the execution context of that specific method call. They temporarily override any session-wide variable with the same name for that request but do not permanently alter the session state. These variables are automatically discarded once the method execution completes.

To use a session variable within a query or method, prefix its name with a dollar sign ($), for example, $user_id.

The namespace and database can be part of the connection URL, so that a client sends no initial use:

ws://localhost:8000/ns/main/db/main/rpc
http://localhost:8000/ns/main/db/main/rpc
Note

SurrealDB 4.x introduces a new, streamlined URL structure, which gives smoother and more predictable URLs and is the basis for upcoming functionality. Connections to /rpc keep working as before.

The namespace and database in the URL are where the connection starts, in the same way as the surreal-ns and surreal-db headers. The server checks them when it authenticates the connection, and the credentials must belong to that namespace and database. After that, a use, a SurrealQL USE, or a signin or signup can select another namespace or database, and reset unsets both. The full rules are on the HTTP page under Scoped URLs.

You can use the RPC protocol to perform the following actions:

Function

Description

authenticate [ token ]

Authenticate a user against SurrealDB with a token

create [ thing, data ]

Create a record with a random or specified ID

delete [ thing ]

Delete either all records in a table or a single record

info

Returns the record of an authenticated record user

insert [ thing, data ]

Insert one or multiple records in a table

insert_relation [ table, data ]

Insert a new relation record into a specified table or infer the table from the data

invalidate

Invalidate a user's session for the current connection

kill [ queryUuid ]

Kill an active live query

let [ name, value ]

Define a variable on the current connection

live [ table, diff ]

Initiate a live query

merge [ thing, data ]

Merge specified data into either all records in a table or a single record

mysql [ statements, vars?, options? ]

Run MySQL statements against SurrealDB tables

patch [ thing, patches, diff ]

Patch either all records in a table or a single record with specified patches

pgsql [ statements, vars?, options? ]

Run PostgreSQL statements against SurrealDB tables

ping

Sends a ping to the database

query [ sql, vars?, options? ]

Execute a custom query with optional variables

query_cancel [ id ]

Stop a streaming query that is running on the same connection

query_stream [ sql, vars?, options? ]

Execute a custom query and receive its results as frames while it runs

relate [ in, relation, out, data? ]

Create graph relationships between created records

reset

Resets all attributes for the current connection

run [ func_name, version, args ]

Execute built-in functions or custom functions with optional arguments.

select [ thing ]

Select either all records in a table or a single record

signin [NS, DB, AC, ... ]

Signin a root, NS, DB or record user against SurrealDB

signup [ NS, DB, AC, ... ]

Signup a user using the SIGNUP query defined in a record access method

unset [ name ]

Remove a variable from the current connection

update [ thing, data ]

Modify either all records in a table or a single record with specified data if the record already exists

upsert [ thing, data ]

Replace either all records in a table or a single record with specified data

use [ ns, db ]

Specifies or unsets the namespace and/or database for the current connection

version

Returns version information about the database/server


How much a single RPC call may carry depends on the transport it arrives over.

TransportDefault limitEnvironment variable
WebSocket /rpc128 MiB per messageSURREAL_WEBSOCKET_MAX_MESSAGE_SIZE
HTTP POST /rpc4 MiB per request bodySURREAL_HTTP_MAX_RPC_BODY_SIZE
gRPC The HTTP /rpc limit, so 4 MiB per message unless that is raised, in each directionSURREAL_GRPC_MAX_MESSAGE_SIZE

The WebSocket ceiling is roughly thirty times the HTTP one, which is why the SDKs default to the WebSocket engine. If a payload is rejected over HTTP, moving the same call onto a WebSocket connection is usually enough.

The values above are what the server accepts. An SDK may also enforce its own limit on outgoing messages, and that limit is applied before anything reaches the network. The effective ceiling is therefore whichever of the two is lower.

The Rust SDK defaults to 64 MiB per message. Sending more than that produces a Message too long error from the client itself, not a rejection from the server. The limit is configurable:

use surrealdb::engine::remote::ws::Ws;
use surrealdb::opt::{Config, WebsocketConfig};
use surrealdb::Surreal;

let websocket = WebsocketConfig::new().max_message_size(128 << 20); // 128 MiB
let config = Config::new().websocket(websocket)?;
let db = Surreal::new::<Ws>(("127.0.0.1:8000", config)).await?;

Raising a client limit above the server's ceiling does not help, as the server still rejects the message. Raise both in this case, or keep messages under the lower of the two.

For request bodies on the other HTTP endpoints, see Request size limits.


The server also serves the RPC protocol over gRPC, as the surrealdb.protocol.rpc.v1.SurrealDBService service defined in the surrealdb-protocol repository. gRPC shares the listener, port and TLS configuration of the HTTP and WebSocket endpoints: it runs over HTTP/2, which the server negotiates by the connection preface on a plaintext port and by ALPN on a TLS one, so no second port is configured. gRPC calls pass through the same middleware as the other endpoints, including the startup gate and the handling of the Authorization header. Denying the rpc HTTP route closes gRPC along with both /rpc endpoints, and the RPC method names restrict gRPC calls as they do WebSocket ones.

Clients connect with a grpc:// address, or grpcs:// for TLS. The Rust SDK supports both through its protocol-grpc feature, and surreal db sql --endpoint accepts them too.

A few details:

  • SURREAL_GRPC_MAX_MESSAGE_SIZE limits every message the server receives and every message it sends. It defaults to the value of SURREAL_HTTP_MAX_RPC_BODY_SIZE, and the server reports it to clients as Limits.max_message_bytes in the GetCapabilities handshake, so a client knows the limit before it sends anything. The server splits query results across as many messages as they need, so the limit bounds a single request, record or live query notification rather than a whole result. A client can also set a figure of its own, which bounds the responses it accepts, as described for the Rust SDK.

  • Compression is negotiated per client. The server accepts requests compressed with zstd or gzip, answers in whichever of the two the client lists in grpc-accept-encoding, and sends uncompressed messages to a client that lists neither.

  • SURREAL_GRPC_MAX_ATTACHED_SESSIONS and SURREAL_GRPC_NOTIFICATION_BUFFER bound the sessions attached across all gRPC connections and the live query notifications buffered for a slow subscriber. All three variables are listed under HTTP server config.

  • The service's Health method is the gRPC health check, and the health HTTP route allows or denies it together with GET /health. The server does not implement the standard grpc.health.v1.Health service, so a probe that calls that service, such as a Kubernetes grpc probe, fails with Unimplemented. Probe GET /health or GET /ready over HTTP instead.

  • The server does not offer gRPC server reflection, so a tool such as grpcurl needs the .proto files from the surrealdb-protocol repository.


Shaped results are a result format for clients that read many records of the same shape. A shaped result describes the fields of a row once, with their names and types, and then sends each row as its values only, with no field names and no type tags. Everything else, such as errors, scalar results and the response envelope, is CBOR, so a client needs a decoder for the row blocks in addition to the CBOR decoder it already has. JSON, CBOR and the other formats are unchanged, and a client that does not ask for shaped results never receives them.

A client opts in for each connection or request:

TransportHow to ask for shaped results
WebSocketOffer the shaped subprotocol in Sec-WebSocket-Protocol. Requests and responses are then CBOR, in binary messages.
HTTP /rpcSend a CBOR body with Content-Type: application/cbor and Accept: application/vnd.surrealdb.shaped.
HTTP /sqlSend Accept: application/vnd.surrealdb.shaped.

A statement whose result is a list of rows returns a shaped block in place of the array, in a query response, in the rows frames of query_stream, and in live query notifications. A block is a CBOR byte string under tag 57.


This method allows you to authenticate a user against SurrealDB with a token.

Method Syntax
authenticate [ token ]

Parameter

Description

token

The token that authenticates the user

Request
{
    "id": 1,
    "method": "authenticate",
    "params": [ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJTdXJyZWFsREIiLCJpYXQiOjE1MTYyMzkwMjIsIm5iZiI6MTUxNjIzOTAyMiwiZXhwIjoxODM2NDM5MDIyLCJOUyI6InRlc3QiLCJEQiI6InRlc3QiLCJTQyI6InVzZXIiLCJJRCI6InVzZXI6dG9iaWUifQ.N22Gp9ze0rdR06McGj1G-h2vu6a6n9IVqUbMFJlOxxA" ]
}
Response
{
    "id": 1,
    "result": null
}


This method opens an interactive transaction on the connection and returns its id, a UUID. Requests that carry that id in a top-level txn field run inside the transaction, and the changes stay invisible to other requests on the connection until the transaction is committed.

Method syntax
begin

A connection can hold 64 open transactions by default, which SURREAL_MAX_TRANSACTIONS_PER_CONNECTION changes. A further begin is refused with Too many open transactions.

Request
{
    "id": 1,
    "method": "begin"
}
Response
{
    "id": 1,
    "result": "01a119b9-ae48-7821-bcb0-7d0ee0aad775"
}

To run a request inside the transaction, add the txn field next to method and params. This works for query and for the other data methods such as create, select and update.

Request
{
    "id": 2,
    "method": "query",
    "txn": "01a119b9-ae48-7821-bcb0-7d0ee0aad775",
    "params": ["CREATE person:1 SET name = 'a'"]
}

A request without txn runs outside the transaction. In the example above, SELECT * FROM person without txn returns an empty array until the commit, while the same query with txn returns person:1.

This method commits a transaction opened with begin. The transaction id is the only parameter, and it goes in params, not in the txn field.

Method syntax
commit [ txn ]
Request
{
    "id": 3,
    "method": "commit",
    "params": ["01a119b9-ae48-7821-bcb0-7d0ee0aad775"]
}
Response
{
    "id": 3,
    "result": null
}

A transaction id that is unknown or already finished returns Transaction not found.

This method discards a transaction opened with begin, along with every change made inside it. The transaction id goes in params.

Method syntax
cancel [ txn ]
Request
{
    "id": 4,
    "method": "cancel",
    "params": ["01a119b9-b558-7780-9a29-e350a7a492a3"]
}
Response
{
    "id": 4,
    "result": null
}

The begin, commit and cancel methods are not available over HTTP, because HTTP requests carry no connection to hold the transaction between them.

This method creates a record either with a random or specified ID.

Method Syntax
create [ thing, data ]

Parameter

Description

thing

The thing (Table or Record ID) to create. Passing just a table will result in a randomly generated ID

data

The content of the record

Request
{
    "id": 1,
    "method": "create",
    "params": [
        "person",
        {
            "name": "Mary Doe"
        }
    ]
}
Response
{
    "id": 1,
    "result": [
        {
            "id": "person:s5fa6qp4p8ey9k5j0m9z",
            "name": "Mary Doe"
        }
    ]
}


This method deletes either all records in a table or a single record.

Method Syntax
delete [ thing ]

Parameter

Description

record_id

The record_id (Table or Record ID) to delete

Request
{
    "id": 1,
    "method": "delete",
    "params": [ "person:8s0j0bbm3ngrd5c9bx53" ]
}

Notice how the deleted record is returned. This differs from a DELETE statement via the CLI or SurrealDB Studio which returns nothing unless the RETURN BEFORE clause is used.

Response
{
    "id": 1,
    "result": {
        "active": true,
        "id": "person:8s0j0bbm3ngrd5c9bx53",
        "last_updated": "2023-06-16T08:34:25Z",
        "name": "John Doe"
    }
}


This method returns the record of an authenticated record user.

Method Syntax
info
Request
{
    "id": 1,
    "method": "info"
}

The result property of the response is likely different depending on your schema and the authenticated user. However, it does represent the overall structure of the responding message.

Response
{
    "id": 1,
    "result": {
        "id": "user:john",
        "name": "John Doe"
    }
}


This method creates a record either with a random or specified ID.

Method Syntax
insert [ thing, data ]

Parameter

Description

thing

The table to insert in to

data

One or multiple record(s)

Request
{
    "id": 1,
    "method": "insert",
    "params": [
        "person",
        {
            "name": "Mary Doe"
        }
    ]
}
Response
{
    "id": 1,
    "result": [
        {
            "id": "person:s5fa6qp4p8ey9k5j0m9z",
            "name": "Mary Doe"
        }
    ]
}
Request
{
    "id": 1,
    "method": "insert",
    "params": [
        "person",
        [
            {
                "name": "Mary Doe"
            },
            {
                "name": "John Doe"
            }
        ]
    ]
}
Response
{
    "id": 1,
    "result": [
        {
            "id": "person:s5fa6qp4p8ey9k5j0m9z",
            "name": "Mary Doe"
        },
        {
            "id": "person:xtbbojcm82a97vus9x0j",
            "name": "John Doe"
        }
    ]
}


This method inserts a new relation record into the database. You can specify the relation table to insert into and provide the data for the new relation.

Method Syntax
insert_relation [ table, data ]

Parameter

Description

table

The name of the relation table to insert into. If null or none, the table is determined from the id field in the data.

data

An object containing the data for the new relation record, including in, out, and any additional fields.

Inserting a Relation into a Specified Table

Request
{
    "id": 1,
    "method": "insert_relation",
    "params": [
        "likes",                   // (relation table)
        {                          // data
            "in": "user:alice",
            "out": "post:123",
            "since": "2024-09-15T12:34:56Z"
        }
    ]
}
Response
{
    "id": 1,
    "result": {
        "id": "likes:user:alice:post:123",
        "in": "user:alice",
        "out": "post:123",
        "since": "2024-09-15T12:34:56Z"
    }
}

Inserting a Relation Without Specifying the Table

If you do not specify the table parameter (i.e., set it to null or none), the relation table is inferred from the id field within the data.

Request
{
    "id": 2,
    "method": "insert_relation",
    "params": [
        null,                      // relation table is null
        {                          // data
            "id": "follows:user:alice:user:bob",
            "in": "user:alice",
            "out": "user:bob",
            "since": "2024-09-15T12:34:56Z"
        }
    ]
}
Response
{
    "id": 2,
    "result": {
        "id": "follows:user:alice:user:bob",
        "in": "user:alice",
        "out": "user:bob",
        "since": "2024-09-15T12:34:56Z"
    }
}
  • table parameter:

    • Specifies the relation table into which the new relation record will be inserted.

    • If table is null or none, the method expects the data to contain an id from which it can infer the relation table.

  • data parameter:

    • Must include at least the in and out fields, representing the starting and ending points of the relation.

    • Can include additional fields to store more information within the relation.

  • Relation IDs:

    • If an id is provided in the data, it will be used as the identifier for the new relation record.

    • If no id is provided, the system may generate one based on the table, in, and out fields.

  • Single vs. multiple inserts:

    • The method primarily handles single relation inserts.

    • The one variable in the code determines if the table parameter refers to a single item.

  • Invalid parameters:

    • If you provide fewer than two parameters or incorrect parameter types, you will receive an InvalidParams error.

    • The method expects exactly two parameters: table and data.

Example of invalid parameters:

Request with missing parameters
{
    "id": 3,
    "method": "insert_relation",
    "params": [
        "likes"  // Missing the data parameter
    ]
}
Response
{
    "id": 3,
    "error": {
        "cause": null,
        "code": -32603,
        "details": { "kind": "InvalidParams" },
        "kind": "Validation",
        "message": "Expected (what, data)"
    }
}

See Errors for the full error shape, the list of kinds, and the wire codes.

  • Include in and out Fields:

    • Always provide the in and out fields in your data to define the relation endpoints.

  • Specifying the Relation Table:

    • If possible, specify the table parameter to clearly indicate the relation table.

    • If not specified, ensure that the id in data correctly reflects the desired relation table.

  • Providing an id in data:

    • If you want to control the id of the relation, include it in the data.

    • This is especially important when table is null or none.

Inserting a Relation with Auto-Generated ID

Request
{
    "id": 4,
    "method": "insert_relation",
    "params": [
        "friendship",              // table (relation table)
        {                          // data
            "in": "user:alice",
            "out": "user:bob",
            "since": "2024-09-15"
        }
    ]
}
Response
{
    "id": 4,
    "result": {
        "id": "friendship:user:alice:user:bob",
        "in": "user:alice",
        "out": "user:bob",
        "since": "2024-09-15"
    }
}

Notes:

  • The id is generated based on the table, in, and out fields.

  • The relation is inserted into the friendship table.


This method will invalidate the user's session for the current connection.

Method Syntax
invalidate
Request
{
    "id": 1,
    "method": "invalidate"
}
Response
{
    "id": 1,
    "result": null
}


This method stores a variable on the current connection.

Method Syntax
let [ name, value ]

Parameter

Description

name

The name for the variable without a prefixed $ character

value

The value for the variable

Request
{
    "id": 1,
    "method": "let",
    "params": [ "website", "https://surrealdb.com/" ]
}
Response
{
    "id": 1,
    "result": null
}


This method initiates a live query for a specified table name.

Method Syntax
live[ table ]
Important

For more advanced live queries where filters are needed, use the Query method to initiate a custom live query.

Parameter

Description

table

The table to initiate a live query for

diff

If set to true, live notifications will contain an array of JSON Patches instead of the entire record

Request
{
    "id": 1,
    "method": "live",
    "params": [ "person" ]
}
Response
{
    "id": 1,
    "result": "0189d6e3-8eac-703a-9a48-d9faa78b44b9"
}

For every creation, update or deletion on the specified table, a live notification will be sent. Live notifications do not have an ID attached, but rather include the Live Query's UUID in the result object.

{
    "result": {
        "action": "CREATE",
        "id": "0189d6e3-8eac-703a-9a48-d9faa78b44b9",
        "result": {
            "id": "person:8s0j0bbm3ngrd5c9bx53",
            "name": "John"
        }
    }
}


This method kills an active live query, stopping any further notifications for it.

Method Syntax
kill [ queryUuid ]

Parameter

Description

queryUuid

The UUID of the live query to kill, as returned by the live method

Request
{
    "id": 1,
    "method": "kill",
    "params": [ "0189d6e3-8eac-703a-9a48-d9faa78b44b9" ]
}
Response
{
    "id": 1,
    "result": null
}

This method merges specified data into either all records in a table or a single record.

Method Syntax
merge [ thing, data ]
Note

This function merges the current document / record data with the specified data. If no merge data is passed it will simply trigger an update.

Parameter

Description

thing

The thing (Table or Record ID) to merge into

data

The content of the record

Request
{
    "id": 1,
    "method": "merge",
    "params": [
        "person",
        {
            "active": true
        }
    ]
}
Response
{
  "id": 1,
  "result": [
      {
          "active": true,
          "id": "person:8s0j0bbm3ngrd5c9bx53",
          "name": "John Doe"
      },
      {
          "active": true,
          "id": "person:s5fa6qp4p8ey9k5j0m9z",
          "name": "Mary Doe"
      }
  ]
}


This method runs MySQL statements on the RPC session, in the MySQL dialect that the MySQL wire protocol listener uses. It takes the same parameters and options, and answers in the same form, as pgsql, with these differences:

  • A positional ? binds to the variable of its position, so the first ? reads _1.

  • A column's type is { "name", "mysql_type" }, where mysql_type is the MySQL protocol type code.

  • command.tag is the kind of statement, such as SELECT, INSERT, REPLACE or CREATE TABLE.

  • An ERR entry carries MySQL's SQLSTATE and its error number in errno, such as 1064 for a parse failure, 1146 for a missing table and 1062 for a duplicate key.

  • An entry whose whole text is USE name switches the session database within the session namespace, as the use method does.

  • START TRANSACTION takes READ ONLY, READ WRITE and WITH CONSISTENT SNAPSHOT. A failing statement leaves the transaction "active", because MySQL has no failed state.

  • SET statements are accepted and have no effect. User variables (@a) and SQL PREPARE statements, which the MySQL listener supports, are not available.

  • A column without an alias is named as in the PostgreSQL dialect, such as count for COUNT(*), not by its expression text as the MySQL listener names it.

The method is gated by the mysql RPC method name, the arbitrary query capability and the mysql HTTP route name.

Request
{
    "id": 1,
    "method": "mysql",
    "params": [
        "SELECT name FROM person WHERE name = ?",
        { "_1": "Tobie" },
        { "rowMode": "array" }
    ]
}


This method patches either all records in a table or a single record with specified patches.

Method Syntax
patch [ thing, patches, diff ]
Note

This function patches the current document / record data with the specified JSON Patch data.

Parameter

Description

thing

The thing (Table or Record ID) to patch

patches

An array of patches following the JSON Patch specification

diff

A boolean representing if just a diff should be returned.

Request
{
    "id": 1,
    "method": "patch",
    "params": [
        "person",
        [
            { "op": "replace", "path": "/last_updated", "value": "2023-06-16T08:34:25Z" }
        ]
    ]
}
Response
{
    "id": 1,
    "result": [
        [
            {
                "op": "add",
                "path": "/last_updated",
                "value": "2023-06-16T08:34:25Z"
            }
        ],
        [
            {
                "op": "add",
                "path": "/last_updated",
                "value": "2023-06-16T08:34:25Z"
            }
        ]
    ]
}


This method runs PostgreSQL statements on the RPC session, as query runs SurrealQL. The statements are read in the PostgreSQL dialect that the Postgres wire protocol listener uses, and run against SurrealDB tables. The method answers with the column metadata, command tags, error codes and transaction status that a SQL client library needs, so a client modelled on node-postgres can run over WebSocket or HTTP instead of a TCP connection. mysql is the same method for the MySQL dialect.

Method syntax
pgsql [ statements, vars?, options? ]

Parameter

Description

statements

A string, which can hold several statements separated by

;

, or a batch: an array of strings and

{ "text": string, "vars"?: object }

objects, run in order

vars

An object of variables layered over the session's. The positional parameter

$1

binds to the variable

_1

,

$2

to

_2

, and so on. In a batch, an entry's own

vars

take precedence over these

options

An object of the options in the table below

OptionValuesMeaning
describebooleanDescribe the last statement of each entry instead of running it.
rowMode"object" (default) or "array"Send each row as an object keyed by column name, or as an array in column order.
valueMode"native" (default) or "text"Send values as SurrealDB values, or every value as its text form, with null for SQL NULL.
atomicboolean, default falseRun every statement of the call in one transaction, which commits only if all of them succeed.
isolation"snapshot", "serializable" or a SQL isolation levelThe isolation level of an atomic call's transaction.
readOnlyboolean, default falseMake an atomic call's transaction read-only, so that a write in it fails.

An unknown option or value fails the call with invalid_params. The session's namespace, database, authentication and variables apply, as they do for query, so a call runs with the permissions of the signed-in user.

A call that reaches the engine answers with one object. statements has one entry for each SQL statement, params is present only for a describe call, and transaction.status is "idle", "active" or "failed". An entry that ran has a command with the PostgreSQL command tag (SELECT 1, INSERT 0 3, CREATE TABLE) and the number of rows it read or wrote, a columns list with each column's name, PostgreSQL type and source table and column, and its rows. A record ID in a row is sent as its key, so person:1 is sent as 1.

An INSERT, UPDATE or DELETE answers with a count and no rows, unless it has a RETURNING clause. Within one string, the first statement that fails stops the statements after it, which answer with an ERR entry. A failing batch entry does not stop the entries after it, so independent statements go in separate entries.

A statement that fails answers with an ERR entry, while the call itself succeeds. Its error holds the message and the PostgreSQL sqlstate, such as 42601 for a parse failure, 42P01 for a missing table, 23505 for a duplicate key and 40001 for a transaction conflict that can be retried. A failure that is not about the statement text, such as a denied method or malformed parameters, fails the call as an RPC error.

A parameter bound to a column is read as the column's type when it is sent as text, so a client that sends every parameter as text can write to integer, float, numeric, boolean, timestamp, uuid and json columns. Text that does not read as the column's type fails with 42804. SQL PREPARE and EXECUTE statements are refused, so bind parameters with vars instead.

Over WebSocket, an entry whose whole text is BEGIN or START TRANSACTION, with optional ISOLATION LEVEL and READ ONLY modes, opens a transaction on the session. Later calls run inside it until an entry that is COMMIT or ROLLBACK. REPEATABLE READ, READ COMMITTED and READ UNCOMMITTED run at snapshot isolation, and SERIALIZABLE opens a serializable transaction. After a failing statement, the status is "failed" and later statements fail with 25P02 until the transaction ends. SET TRANSACTION and SET SESSION CHARACTERISTICS AS TRANSACTION choose the modes of the next transaction, or of every later one.

An HTTP request holds no transaction between requests, so an entry that opens one fails. Use atomic: true instead, which works over both transports. In an atomic call, the statements after the first failure are not run, and the response reports committed: false.

SET, SHOW and RESET act on session settings, which last for the session over WebSocket and for the request over HTTP. A setting has no effect on the engine. A running call over WebSocket is cancelled with query_cancel, given the call's request id.

The method is gated by the pgsql RPC method name, by the arbitrary query capability as query is, and by the postgres HTTP route name. A call that a capability denies fails with a NotAllowed error.

Request
{
    "id": 1,
    "method": "pgsql",
    "params": [
        "SELECT id, name FROM person WHERE name = $1",
        { "_1": "Tobie" }
    ]
}
Response
{
    "id": 1,
    "result": {
        "statements": [
            {
                "status": "OK",
                "time": "412µs",
                "command": {
                    "tag": "SELECT 1",
                    "rows_affected": 1,
                    "last_insert_id": null,
                    "warnings": 0
                },
                "columns": [
                    {
                        "name": "id",
                        "type": { "name": "integer", "oid": 23 },
                        "nullable": null,
                        "table": "person",
                        "column": "id"
                    },
                    {
                        "name": "name",
                        "type": { "name": "text", "oid": 25 },
                        "nullable": null,
                        "table": "person",
                        "column": "name"
                    }
                ],
                "rows": [ { "id": 1, "name": "Tobie" } ]
            }
        ],
        "transaction": { "status": "idle" }
    }
}


Method Syntax
ping
Request
{
    "id": 1,
    "method": "ping",
}
Response
{
  "id": 1,
  "result": null
}


This method sends a custom SurrealQL query.

Method Syntax
query [ sql, vars?, options? ]

Parameter

Description

sql

The query to execute against SurrealDB

vars

An object that maps variable names to the values the query uses

options

An object of the options in the table below

OptionValuesMeaning
read_onlytrue, or an object with max_staleness and strictRun the statements as one read-only block, as BEGIN READ ONLY does. max_staleness is a duration and defaults to 0s, the current state.
tracean array of trace kindsReport the graph the statements walked, as described under Tracing the graph a query walked.

An unknown option is refused, so a misspelt option is never ignored.

Request
{
    "id": 1,
    "method": "query",
    "params": [
        "CREATE person SET name = 'John'; SELECT * FROM type::table($tb);",
        {
            "tb": "person"
        }
    ]
}
Response
{
  "id": 1,
  "result": [
      {
          "status": "OK",
          "time": "152.5µs",
          "result": [
              {
                  "id": "person:8s0j0bbm3ngrd5c9bx53",
                  "name": "John"
              }
          ]
      },
      {
          "status": "OK",
          "time": "32.375µs",
          "result": [
              {
                  "id": "person:8s0j0bbm3ngrd5c9bx53",
                  "name": "John"
              }
          ]
      }
  ]
}

A query or query_stream call can report the graph edges, references and record links that its statements read to produce their results, along with the parameters that a LET bound and the live queries that a LIVE SELECT started. A client such as a graph view uses this to draw the relationships a query followed, instead of guessing them from the record ids in the results. The trace option lists the kinds to report.

In the example below, person:a is related to person:b through the edge knows:ab, and the call asks for relations and variables:

Request
{
    "id": 1,
    "method": "query",
    "params": [
        "LET $x = 1; SELECT ->knows->person AS friends FROM person:a;",
        {},
        { "trace": ["relations", "variables"] }
    ]
}

Each result carries the trace of its statement under a trace field. A statement with nothing to report has no trace field.

Response
{
  "id": 1,
  "result": [
      {
          "result": null,
          "status": "OK",
          "time": "189.625µs",
          "trace": { "variables": { "x": 1 } },
          "type": null
      },
      {
          "result": [{ "friends": ["person:b"] }],
          "status": "OK",
          "time": "2.2515ms",
          "trace": { "relations": [["person:a", "knows:ab", "person:b"]] },
          "type": null
      }
  ]
}

The trace option takes these kinds:

KindEntryReported for
relations[in, edge, out]Each graph edge that a -> or <- lookup in the result read. An end that the session cannot read is NONE, which is null in JSON.
references[referrer, referenced]Each reference that a <~ lookup in the result read, with the record that holds the field first.
links[record, field, target]Each record link that the result reads through, such as author in author.name.
variablesan object of names and valuesThe parameter that a top-level LET bound, on the result of that LET.
subscriptionsan array of live query idsThe live query that a LIVE SELECT started.

A trace reports only what leads to the result:

  • A lookup is reported only where its value is part of the result, and only for the edges that lead to the records it returned. An edge that a filter in the path removed, or a path that reaches no record, is not reported. A recursive lookup is reported at every depth.

  • A lookup or link in a condition, such as a WHERE clause, is not reported. A link that the result returns without reading through it, as in SELECT author FROM post, is not reported either.

  • A lookup or link that reaches the result through a function, an operator or an aggregate, such as count(->knows), is not reported. The trace carries "partial": true instead. A LIMIT over a graph path in FROM also sets partial.

  • Edges that a RELATE writes are not reported, because its result already holds them, and nor are links that FETCH resolves.

A trace holds only what the session can read. A record that its table permissions hide, an edge of a table that the session cannot select, and a link in a field that the session cannot read are not reported.

Asking for a trace does not change the results. A kind that is not asked for is not recorded, and a call without the trace option records nothing. A statement that fails, or that a transaction rolls back, has no trace. The same entry can appear more than once, so a client keys entries by their record ids.

A trace has size limits. A LET whose value is too large is reported by name with the value NONE, and a buffered query keeps at most about 1 MiB of relation, reference and link entries across all its statements. A trace that reached a limit lists the kinds it cut short in a truncated array.

A kind that the server does not know is refused, so a client never reads an unsupported kind as an empty one:

Response
{
    "id": 1,
    "error": {
        "cause": null,
        "code": -32603,
        "details": { "kind": "InvalidParams" },
        "kind": "Validation",
        "message": "Unknown trace kind `records`"
    }
}

The trace option is not available over gRPC. A buffered query with a trace sends its results as ordinary values, not as shaped results.

This method stops a query_stream request that is still running on the same connection.

Method syntax
query_cancel [ id ]

Parameter

Description

id

The request

id

of the streaming query to stop, with the same value and type the

query_stream

request used

The method returns null once the stream has been told to stop. The stream then ends with an end frame whose error is The streaming query was stopped before it completed, which retracts every statement that had not finished. A connection can stop only its own streams, and an id with no stream in progress, including one that has just finished, returns the error No streaming query with this request id is in progress.

Request
{
    "id": 2,
    "method": "query_cancel",
    "params": [1]
}
Response
{
    "id": 2,
    "result": null
}

This method runs a query in the same way as query, but answers with a sequence of frames sent while the query runs instead of one response at the end, so the first records reach the client before the last statement has finished. Only the WebSocket /rpc endpoint serves it, and HTTP POST /rpc reports Method not found. Neither the Rust nor the JavaScript SDK calls this method over WebSocket. The Rust SDK's .stream_items() streams incrementally on the embedded engines and over gRPC, and the JavaScript SDK's streaming query responses do so on the embedded Node.js and browser engines, which produce the same frames locally.

Method syntax
query_stream [ sql, vars?, options? ]

Parameter

Description

sql

The query to execute against SurrealDB

vars

An object that maps variable names to the values the query uses

options

An object with the same options as

query

,

read_only

and

trace

The request needs an id, because every frame carries it, and one without an id is refused. A second query_stream that reuses the id of a stream still running on the connection is refused with A streaming query with this request id is already in progress. A request that carries a txn runs inside that client transaction, and the client waits for the end frame before it sends commit, because a commit that arrives while the stream is running ends the transaction the stream is still using. A failure before execution starts, such as a parse error or a denied capability, is answered with an ordinary error response and no frames.

Each frame arrives as an ordinary response carrying the request's id, and its result is an object whose stream field names the frame type:

streamFieldsMeaning
beginstatements, versionThe stream is open. statements is an upper bound, because control flow such as a RETURN can skip the rest of a block, so the number of statements is best counted from finished frames. version is the revision of the frame protocol, currently 1.
rowsindex, valuesRecords produced by statement index, with more possibly to follow. A statement's first rows frame carries up to 16 records, and each later one up to twice as many as the one before, to a limit of 256.
valueindex, valueThe single value of a statement that does not return a list, such as RETURN 1 + 2. A statement sends rows frames or one value frame, never both.
finishedindex, time, type, single, errorStatement index is final. single is true when its value is one value rather than a list, type is "live" or "kill" for those statements and null for any other, and error is present only when the statement failed.
endresults, time, errorThe stream is complete, and nothing follows. results is the number of finished frames delivered, and error is present only when the stream did not complete normally.

Records are provisional until their statement's finished frame arrives:

  • A finished frame with an error retracts every record already sent for that statement.

  • An end frame with an error retracts every statement that has no finished frame, while the statements that finished stand. This covers a timeout, a query_cancel, a transaction that could not commit, and a stream the server stopped because its frames could not be delivered, which reports The streaming query was stopped before it completed.

  • An end error can also name LIVE SELECT subscriptions that were discarded because their session ended, since their statements had already finished and cannot be retracted.

New frame types and fields can be added without changing version, so a client ignores a stream value or a field it does not recognise. Every finished frame carries single, because a statement that sent no rows or value frame has no other way to say whether its value is an empty list or a single value.

A request with the trace option, described under Tracing the graph a query walked, receives the trace of each statement inside the frames that the stream already sends, under a trace field. A trace never takes a frame of its own. It goes on the statement's next rows or value frame, or on its finished frame when no other frame follows.

  • A trace belongs to its statement, not to the records of the frame it arrives on, so a client collects it for each index across that statement's frames.

  • A trace is provisional in the same way as records. A finished frame with an error retracts every trace already sent for that statement.

  • The same entry can arrive in more than one frame.

Request
{
    "id": 1,
    "method": "query_stream",
    "params": [
        "SELECT title, author.name, ->tagged->tag AS tags FROM post;",
        {},
        { "trace": ["relations", "links"] }
    ]
}
Response
{ "id": 1, "result": { "statements": 1, "stream": "begin", "version": 1 } }
{
    "id": 1,
    "result": {
        "index": 0,
        "stream": "rows",
        "trace": {
            "links": [["post:1", "author", "person:tobie"]],
            "relations": [["post:1", "tagged:a", "tag:rust"]]
        },
        "values": [{ "author": { "name": "Tobie" }, "tags": ["tag:rust"], "title": "Hello" }]
    }
}
{ "id": 1, "result": { "index": 0, "single": false, "stream": "finished", "time": "188.25µs", "type": null } }
{ "id": 1, "result": { "results": 1, "stream": "end", "time": "301.5µs" } }
  • SURREAL_WEBSOCKET_MAX_CONCURRENT_STREAMS, 32 by default, limits how many streams one connection runs at once. A request over the limit is refused with Too many concurrent streaming queries rather than queued.

  • SURREAL_WEBSOCKET_STREAM_SEND_TIMEOUT, 30 seconds by default, stops a stream that has waited that long to send one frame, which is what happens when the client stops reading. It applies whether or not --query-timeout is set.

  • --query-timeout, when set, limits the whole stream, including the time spent waiting to send frames.

  • query_stream is an RPC method capability of its own, and the query runs through the query method's gate as well, so denying either method denies streaming.

Both environment variables are listed under WebSocket config.

Request
{
    "id": 1,
    "method": "query_stream",
    "params": [
        "SELECT * FROM person; RETURN 1 + 2;"
    ]
}

The server sends these frames in order, one message each:

Response
{ "id": 1, "result": { "statements": 2, "stream": "begin", "version": 1 } }
{
    "id": 1,
    "result": {
        "index": 0,
        "stream": "rows",
        "values": [
            { "id": "person:jane", "name": "Jane" },
            { "id": "person:john", "name": "John" }
        ]
    }
}
{ "id": 1, "result": { "index": 0, "single": false, "stream": "finished", "time": "215.042µs", "type": null } }
{ "id": 1, "result": { "index": 1, "stream": "value", "value": 3 } }
{ "id": 1, "result": { "index": 1, "single": true, "stream": "finished", "time": "18.5µs", "type": null } }
{ "id": 1, "result": { "results": 2, "stream": "end", "time": "402.917µs" } }

This method relates two records with a specified relation.

Method Syntax
relate [ in, relation, out, data? ]

Parameter

Description

in

The record to relate to

relation

The relation table

out

The record to relate from

data

The content of the record

Request
{
    "id": 1,
    "method": "relate",
    "params": [
        "person:12s0j0bbm3ngrd5c9bx53",
        "knows",
        "person:8s0j0bbm3ngrd5c9bx53"
    ]
}
Response
{
    "id": 1,
    "result": {
        "id": "knows:12s0j0bbm3ngrd5c9bx53:8s0j0bbm3ngrd5c9bx53",
        "in": "person:12s0j0bbm3ngrd5c9bx53",
        "out": "person:8s0j0bbm3ngrd5c9bx53"
    }
}
Request
{
    "id": 2,
    "method": "relate",
    "params": [
        "person:john_doe",          // in
        "knows",                    // relation
        "person:jane_smith",        // out
        { "since": "2020-01-01" }   // data
    ]
}
Response
{
    "id": 2,
    "result": {
        "id": "knows:person:john_doe:person:jane_smith",
        "in": "person:jane_smith",
        "out": "person:john_doe",
        "since": "2020-01-01"
    }
}


This method will reset all attributes for the current connection. It clears authentication (much like invalidate), unsets the selected NS/DB, unsets any defined connection params, and aborts any active live queries. On WebSocket connections it also cancels open client-managed transactions for the session being reset and frees their slots under SURREAL_MAX_TRANSACTIONS_PER_CONNECTION / SURREAL_MAX_TRANSACTIONS_PER_SESSION.

Method Syntax
reset
Request
{
    "id": 1,
    "method": "reset"
}
Response
{
    "id": 1,
    "result": null
}


This method allows you to execute built-in functions or custom functions with optional arguments. SurrealDB 4.0 removed SurrealML, so a call to an ml:: model fails.

Method Syntax
run [ func_name, version?, args? ]

Parameter

Description

func_name

The name of the function to execute. Prefix with fn:: for custom functions.

version

The version of the function to execute.

args

The arguments to pass to the function or model.

Request
{
    "id": 1,
    "method": "run",
    "params": [ "time::now" ]
}
Response
{
    "id": 1,
    "result": "2024-09-15T12:34:56Z"
}
Request
{
    "id": 1,
    "method": "run",
    "params": [ "fn::calculate_discount", null, [ 100, 15 ] ]
}
Response
{
    "id": 1,
    "result": 85
}


This method selects either all records in a table or a single record.

Method Syntax
select [ thing ]

Parameter

Description

thing

The thing (Table or Record ID) to select

Request
{
    "id": 1,
    "method": "select",
    "params": [ "person" ]
}
Response
{
    "id": 1,
    "result": [
        {
            "id": "person:8s0j0bbm3ngrd5c9bx53",
            "name": "John"
        }
    ]
}


This method allows you to sign in as a root, namespace, or database user, or with a record access method.

Signing in also selects the namespace and database it names, so the connection needs no separate use call afterwards. A database user or a record access method selects both NS and DB, and a namespace user selects NS. A root user selects neither. Once signed in, a user can only switch to a namespace or database that it has access to.

The result is the authentication token as a string. When a record access method is defined WITH REFRESH, the result is instead an object holding the token as access and a refresh token as refresh.

Method Syntax
signin [ NS, DB, AC, ... ]

Parameter

Description

NS

The namespace to sign in to. Only required for DB & RECORD authentication

DB

The database to sign in to. Only required for RECORD authentication

AC

Specifies the access method. Only required for RECORD authentication

user

The username of the database user. Only required for ROOT, NS & DB authentication

pass

The password of the database user. Only required for ROOT, NS & DB authentication

...

Specifies any variables to pass to the SIGNIN query. Only relevant for RECORD authentication

Request
{
    "id": 1,
    "method": "signin",
    "params": [
        {
            "user": "tobie",
            "pass": "3xtr3m3ly-s3cur3-p@ssw0rd"
        }
    ]
}
Response
{
    "id": 1,
    "result": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3OTAyMjQ0NjIsIm5iZiI6MTc5MDIyNDQ2MiwiZXhwIjoxNzkwMjI4MDYyLCJpc3MiOiJTdXJyZWFsREIiLCJqdGkiOiJmZWM2MDlmNS0zY2VhLTQwYTItOTc0MC00ZjliMWNmNGFhOWUiLCJJRCI6InJvb3QifQ.wJD-0zmqmGc1qE4XHt1zI6HBvA_YnOsHuxYwYBHqreAqxTgG04B_lb1-SfTLtHX_f319T15xaHaNhkeOUEmggg"
}
Request
{
    "id": 1,
    "method": "signin",
    "params": [
        {
            "NS": "surrealdb",
            "DB": "docs",
            "AC": "commenter",

            "username": "johndoe",
            "password": "SuperStrongPassword!"
        }
    ]
}
Response
{
    "id": 1,
    "result": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJTdXJyZWFsREIiLCJpYXQiOjE1MTYyMzkwMjIsIm5iZiI6MTUxNjIzOTAyMiwiZXhwIjoxODM2NDM5MDIyLCJOUyI6InRlc3QiLCJEQiI6InRlc3QiLCJTQyI6InVzZXIiLCJJRCI6InVzZXI6dG9iaWUifQ.N22Gp9ze0rdR06McGj1G-h2vu6a6n9IVqUbMFJlOxxA"
}


This method allows you to sign a user up using the SIGNUP query defined in a record access method.

The result is the authentication token as a string. When the record access method is defined WITH REFRESH, the result is instead an object holding the token as access and a refresh token as refresh.

Method Syntax
signup [ NS, DB, AC, ... ]

Parameter

Description

NS

Specifies the namespace of the record access method

DB

Specifies the database of the record access method

AC

Specifies the access method

...

Specifies any variables used by the SIGNUP query of the record access method

Request
{
    "id": 1,
    "method": "signup",
    "params": [
        {
            "NS": "surrealdb",
            "DB": "docs",
            "AC": "commenter",

            "username": "johndoe",
            "password": "SuperStrongPassword!"
        }
    ]
}
Response
{
  "id": 1,
  "result": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJTdXJyZWFsREIiLCJpYXQiOjE1MTYyMzkwMjIsIm5iZiI6MTUxNjIzOTAyMiwiZXhwIjoxODM2NDM5MDIyLCJOUyI6InRlc3QiLCJEQiI6InRlc3QiLCJTQyI6InVzZXIiLCJJRCI6InVzZXI6dG9iaWUifQ.N22Gp9ze0rdR06McGj1G-h2vu6a6n9IVqUbMFJlOxxA"
}


This method removes a variable from the current connection.

Method Syntax
unset [ name ]

Parameter

Description

name

The name of the variable without a prefixed $ character

Request
{
    "id": 1,
    "method": "unset",
    "params": [ "website" ]
}
Response
{
    "id": 1,
    "result": null
}


This method replaces either all records in a table or a single record with specified data.

Method Syntax
update [ thing, data ]
Note

This function replaces the current document / record data with the specified data if that document / record has already been created. If no document has been created this will return an empty array. Also, if no replacement data is passed it will simply trigger an update.

Parameter

Description

thing

The thing (Table or Record ID) to update

data

The content of the record

Request
{
    "id": 1,
    "method": "update",
    "params": [
        "person:8s0j0bbm3ngrd5c9bx53",
        {
            "name": "John Doe"
        }
    ]
}
Response
{
    "id": 1,
    "result": {
        "id": "person:8s0j0bbm3ngrd5c9bx53",
        "name": "John Doe"
    }
}


Method Syntax
upsert [ thing, data ]

Parameter

Description

thing

The thing (Table or Record ID) to upsert

data

The content of the record

Request
{
    "id": 1,
    "method": "upsert",
    "params": [
        "person:12s0j0bbm3ngrd5c9bx53",
        {
            "name": "John Doe",
            "job": "Software developer",
        }
    ]
}
Response
{
    "id": 1,
    "result": {
        "id": "person:12s0j0bbm3ngrd5c9bx53",
        "name": "John Doe",
        "job": "Software developer"
    }
}


This method specifies or unsets the namespace and/or database for the current connection.

Method Syntax
use [ ns, db ]

Parameter

Description

NS

Sets the selected Namespace for queries

DB

Sets the selected Database for queries

For either the namespace or database, a string will change the value, null will unset the value, and none will cause the value to not be affected.

On a scoped connection, use can select a namespace or database other than the ones in the URL.

Request
{
    "id": 1,
    "method": "use",
    "params": [ "surrealdb", "docs" ]
}
Response
{
    "id": 1,
    "result": null
}
Example Combinations
[none, none]     -- Won't change ns or db
["test", none]   -- Change ns to test
[none, "test"]   -- Change db to test
["test", "test"] -- Change ns and db to test

[none, null]     -- Will only unset the database
[null, none]     -- Will throw an error, you cannot unset only the database
[null, null]     -- Will unset both ns and db
["test", null]   -- Change ns to test and unset db


This method returns version information about the database/server.

Method Syntax
version

This method does not accept any parameters.

Request
{
    "id": 1,
    "method": "version"
}
Response
{
    "id": 1,
    "result": {
        "version": "3.2.0",
        "build": "abc123",
        "timestamp": "2024-09-15T12:34:56Z"
    }
}
  • Parameters: Providing any parameters will result in an InvalidParams error.

  • Result Fields:

    • version: The version number of the database/server.

    • build: The build identifier.

    • timestamp: The timestamp when the version was built or released.

Note

The actual values in the response will depend on your specific database/server instance.


Was this page helpful?