Skip to content
v0.5.1GitHub

Using

REST API

applies to v0.5.1

LuraDB’s interface is a RESTful API. There is no proprietary protocol and no driver stack to install: every language and standard tool can talk to it, and the API is browsable down to the individual record. This page covers what all endpoints share: where they live, how a client authenticates, what an error looks like, how conditional requests and paging work. The endpoint pages generated from the OpenAPI document of the release say the rest.

The examples in this handbook use a server on http://127.0.0.1:3000, the default address, and the admin key in $KEY. The paths fall into a few families:

Path What it is
/health The heartbeat. It needs no key.
/version The version handshake: the server version and the version of the API contract.
/store-api/domains, /store-api/json/domains, /store-api/rel/domains Create, list and delete domains, one set per engine.
/store-api/kv/{domain}/… The key-value engine, see Key-value.
/store-api/json/{domain}/… The JSON engine, see JSON documents.
/store-api/rel/{domain}/… The relational engine, see LuraSQL.
/store-api/auth/… Users, permissions and keys, see Security.
/store-api/backups, /store-api/restores/{id} Backup and restore, see Backup and restore.
/store-api/metrics, /store-api/config, /store-api/events, /store-api/logs Operation, see Monitoring.
Terminal window
curl -s http://127.0.0.1:3000/version -H "Authorization: Bearer $KEY"
{ "server_version": "0.5.1", "api_version": "0.14.2" }

The api_version is the version of the contract. What a change of it means is on Compatibility.

A request body is JSON, with one exception: the value of a key-value entry is raw bytes. An endpoint that reads JSON wants the header Content-Type: application/json; without it the answer is 415, and a body that is JSON but has the wrong shape is a 422:

Terminal window
curl -si -X POST http://127.0.0.1:3000/store-api/json/domains \
-H "Authorization: Bearer $KEY" -d '{"name": "orders"}'
HTTP/1.1 415 Unsupported Media Type
content-type: text/plain; charset=utf-8
Expected request with `Content-Type: application/json`

A body may be up to 2 MiB; larger ones answer 413, with higher limits for the bulk loads (see Limits and defaults). Names in a path are percent-encoded: a key user/42/profile is written user%2F42%2Fprofile.

The port and the address come from server.port and server.bind_address. The same API is available over HTTPS and a Unix socket, see Security.

With authentication on, which is the packaged default, a client sends its key as a Bearer token. Only GET /health and, while server.hello_enabled is on, GET / work without one:

Terminal window
curl -s http://127.0.0.1:3000/store-api/auth/whoami -H "Authorization: Bearer $KEY"
curl -si http://127.0.0.1:3000/store-api/kv/default/keys/hello -H "Authorization: Bearer lura_0000"
{ "name": "admin", "role": "Admin" }
HTTP/1.1 401 Unauthorized
content-type: text/plain; charset=utf-8
Unauthorized

401 means the key is missing or unknown; 403 means the key is valid but has no right for the request. An admin key passes every check; a user key reaches the domains it has a permission on, with the level read, write or ddl, separately for each engine:

Terminal window
curl -si -X PUT http://127.0.0.1:3000/store-api/kv/sessions/keys/s-1 \
-H "Authorization: Bearer $USER_KEY" -d x
HTTP/1.1 403 Forbidden
content-type: text/plain; charset=utf-8
Forbidden

Here $USER_KEY is the key of a user that may only read sessions. How to create users, grant permissions, rotate keys and turn the setting auth.enabled off for a test on a loopback address is on Security.

An error answers with a plain-text body and the status in the response line. Read the status, not the text: the text is for people and can change between releases. A few statuses ask the client to do something:

Status Meaning What to do
400, 422 The request is malformed or breaks a rule. Fix the request; sending it again does not help.
401, 403 No valid key, or no right. Check the key and its permissions.
404, 410 Not found, or the domain is being deleted.
409 The request collides with what exists: a name, a primary key, a link target.
412, 428 A precondition failed or is required. Read again and retry with the new ETag, see below.
429 The domain used up its budget of reads or writes per second. Wait for Retry-After.
503, 507 A queue is full, or a volume or the memory is short. The request did nothing. Wait for Retry-After and send it again unchanged.

Every status LuraDB uses, with its typical cause and the headers that go with it, is on Status codes.

Terminal window
curl -si http://127.0.0.1:3000/store-api/kv/default/keys/nope -H "Authorization: Bearer $KEY"
HTTP/1.1 404 Not Found
content-type: text/plain; charset=utf-8
content-length: 35
404 Not Found: key 'nope' not found

Every key and every document carries an ETag. A write with If-Match succeeds only if the key is still at the version you read, and a write with If-None-Match: * succeeds only if the key does not exist yet. A failed precondition answers 412 with the current ETag; a domain created with require_conditional_writes answers 428 to a write that carries neither header.

Terminal window
curl -si -X PUT http://127.0.0.1:3000/store-api/kv/sessions/keys/s-4f2a \
-H "Authorization: Bearer $KEY" -H "If-None-Match: *" -d 'replaced'
HTTP/1.1 412 Precondition Failed
content-type: text/plain; charset=utf-8
etag: "1a1a3944e160000"
412 Precondition Failed: the key's current version does not satisfy the write precondition

The rules, the exceptions for bulk operations and what a reader sees are on Consistency and conditional writes.

Every endpoint that lists takes limit and offset. There are no cursors: the order of a list is fixed (by key, or by primary key), so offset walks through it, and a write between two requests can move an entry across a page boundary. The answer carries the numbers that were applied, so a limit above the maximum is visible, and total counts the matches before paging.

List Parameters Default and maximum Answer
GET /store-api/kv/{domain}/keys prefix, contains, limit, offset 1000, 10000 keys, total, offset, limit
GET /store-api/json/{domain}/documents limit, offset, keys_only 50, 1000 documents, keys, total, offset, limit
POST /store-api/json/{domain}/search limit, offset in the body 50, 1000 documents, total, offset, limit
GET /store-api/rel/{domain}/tables/{table}/rows limit, offset, column=value 1000, rel.max_limit rows, row_count, limit, offset, limit_applied
POST /store-api/rel/{domain}/sql LIMIT and OFFSET in the statement 1000 without LIMIT, rel.max_limit rows, row_count, limit_applied

With three keys in the domain default, the second page of size one and a request for more than the maximum:

Terminal window
for i in 1 2 3; do
curl -s -X PUT http://127.0.0.1:3000/store-api/kv/default/keys/item-$i \
-H "Authorization: Bearer $KEY" -d $i
done
curl -s "http://127.0.0.1:3000/store-api/kv/default/keys?prefix=item-&limit=1&offset=1" \
-H "Authorization: Bearer $KEY"
curl -s "http://127.0.0.1:3000/store-api/kv/default/keys?prefix=item-&limit=100000" \
-H "Authorization: Bearer $KEY"
{ "keys": ["item-2"], "total": 3, "offset": 1, "limit": 1 }
{ "keys": ["item-1", "item-2", "item-3"], "total": 3, "offset": 0, "limit": 10000 }

The second request asked for 100000 and got the maximum, 10000. In the relational answers, limit_applied is true when more matching rows existed than were returned.

The endpoint reference is generated from the contract

Section titled “The endpoint reference is generated from the contract”

The group Endpoints in the sidebar, below this page, is generated from api/openapi.json of the release, so it cannot drift from the server. Each endpoint has a page with its parameters, its request body, its answers with the status codes and headers, and the condition for each status. The overview lists every tag, and the sidebar groups the pages by the tags of the contract:

Tag Covers
Key-Value Store Keys, values, metadata, null state, counting, deleting by prefix, watching.
JSON Document Store Documents, search, bulk load, export.
JSON Indexes Index definitions and re-indexing.
Relational Store The /sql endpoint.
Relational Browse, Relational Rows, Relational Import Tables, views, rows and the CSV and TSV import.
Domains, Relational Domains Creating, listing, reading and deleting domains, key-value and relational. The JSON domain endpoints are under JSON Document Store.
Auth Users, permissions, key rotation, whoami.
Backup Backups and restores.
Metrics, Logs, Events Health, metrics, configuration, log files and the event stream.

One operation of each engine shows what such a page holds: put_key writes a key-value entry, put_document writes a JSON document, and execute_sql runs a LuraSQL statement.

The create, list, read and delete operations of domains have the same operation name in all three engines. The generated pages carry one page for each name, that of the relational engine; the paths of the key-value and the JSON variant are in the table at the top of this page and in the contract served by the running server.

The same contract is served by the running server at /api-docs/openapi.json, and as a Swagger UI, when server.swagger_enabled is on, see First five minutes.