Skip to main content

API Reference

Garak provides a REST API for all database operations. All requests require authentication via access tokens.

note

This documentation is provided primarily for the purpose of allowing developers to create their own clients. You really should use a client instead of directly calling the REST api.

Authentication

All API requests must include an Authorization header:

Authorization: Bearer {node_id}/{token}

The token format is {node_id}/{token} where:

  • node_id: The node ID of the access token record
  • token: The actual token string

Permission Levels

GrantAccess
readRead all documents in all silos
writeRead/write all documents in all silos
read::{silo}Read all documents in shards with specified silo prefix
write::{silo}Read/write all documents in shards with specified silo prefix

Common Request Format

All POST requests require a JSON body with at minimum:

{
"silo": "your-silo-id",
"shard": "your-shard-id",
"store": "your-store-name"
}

Status Endpoint

GET /api/status

Health check endpoint.

Response:

200 OK

Master Endpoints

Using a master token, initilized via the MASTER_TOKEN envvar, new tokens can be created.

The header when using master token follows format: Authorization: master_token_here.

POST /tokens/create

Create a new access token (requires master token).

Authorization: Master token only

Request:

{
"grant": "read" | "write" | "read::silo" | "write::silo"
}

Response:

{
"token": "{node_id}/{token_string}"
}

Read Operations

POST /get

Retrieve a single node by ID.

Request:

{
"silo": "string",
"shard": "string",
"store": "string",
"node_id": number
}

Response:

{
// Your document data
}

Errors:

  • 404 - Node not found
  • 403 - Insufficient permissions

POST /mget

Retrieve multiple nodes by IDs.

Request:

{
"silo": "string",
"shard": "string",
"store": "string",
"node_ids": [number, number, ...]
}

Response:

{
"nodes": [
{
"node_id": number,
"value": {...} | null
},
...
]
}

POST /secondary_key_lookup

Look up a node by secondary key index.

Request:

{
"silo": "string",
"shard": "string",
"store": "string",
"trigger_field": "fieldName",
"key": "value"
}

Response:

{
"node_id": number | null
}

POST /categorization_lookup

Retrieve all nodes in a category.

Request:

{
"silo": "string",
"shard": "string",
"store": "string",
"trigger_field": "fieldName",
"key": "categoryValue"
}

Response:

{
"nodes": [number, number, ...]
}

POST /slice_lookup

Query a slice index for nodes in a range.

Request:

{
"silo": "string",
"shard": "string",
"store": "string",
"slice_name": "string",
"group": "string",
"point": { "Number": number } | { "Absolute": "Top" | "Bottom" },
"select": { "Limit": number } | { "Until": number }
}

Select Options:

  • { "Limit": n } - Return up to n entries
    • Positive n: entries after/at point
    • Negative n: entries before/at point
  • { "Until": n } - Return all entries until reaching key value n

Response:

{
"nodes": [
number, // If fetching just IDs
// OR
{ // If fetching with keys
"key": number,
"value": number // node_id
}
]
}

POST /slice_correction

Internal endpoint for slice index maintenance.

Request:

{
"silo": "string",
"shard": "string",
"store": "string",
"slice_name": "string",
"group": "string",
"key": number,
"node_id": number,
"delete": boolean
}

Write Operations

All write operations require write permissions.

POST /insert

Insert a new node.

Request:

{
"silo": "string",
"shard": "string",
"store": "string",
"data": {
// Your document data
}
}

Response:

{
"node_id": number
}

Errors:

  • 400 - Data validation error or schema violation
  • 403 - Insufficient permissions (read-only token)

POST /set

Set the value of a field in a node.

Request:

{
"silo": "string",
"shard": "string",
"store": "string",
"node_id": number,
"field": "fieldName.nested.path", // Supports dot notation
"value": any
}

Response:

{
// Updated document
}

Errors:

  • 404 - Node not found
  • 400 - Invalid field or value

POST /inc

Increment a numerical field.

Request:

{
"silo": "string",
"shard": "string",
"store": "string",
"node_id": number,
"field": "fieldName",
"value": number // Can be negative for decrement
}

Response:

{
// Updated document
}

Errors:

  • 404 - Node not found
  • 400 - Field is not a number (ETARGETFIELDNOTINT)

POST /add_to_set

Add a value to an array field (if not already present).

Request:

{
"silo": "string",
"shard": "string",
"store": "string",
"node_id": number,
"field": "fieldName",
"value": any
}

Behavior:

  • If field is nullish, initializes as empty array
  • Only adds value if not already present
  • Creates field if it doesn't exist

Response:

{
// Updated document
}

Errors:

  • 404 - Node not found
  • 400 - Field is not an array (ETARGETFIELDNOTARR)

POST /pull

Remove a value from an array field.

Request:

{
"silo": "string",
"shard": "string",
"store": "string",
"node_id": number,
"field": "fieldName",
"value": any
}

Response:

{
// Updated document
}

Errors:

  • 404 - Node not found
  • 400 - Field is not an array

POST /delete

Delete a node.

Request:

{
"silo": "string",
"shard": "string",
"store": "string",
"node_id": number
}

Response:

{
// Success confirmation
}

Note: Deletion removes node from all indexes. Index removal failures are ignored.

Index Management

POST /create_index

Create an index (idempotent).

Request:

{
"index": {
"silo": "string",
"store": "string",
"trigger_field": "fieldName",
"kind": "SecondaryKey" | "Categorization" | "Slice",

// For Slice indexes only:
"group_on": "fieldName" | "$",
"name": "uniqueSliceName",

// Optional for Categorization and Slice:
"includes": {
"field": { "$eq": value }
// JSON condition selector
}
}
}

Index Kinds:

SecondaryKey:

{
"kind": "SecondaryKey",
"trigger_field": "email"
}

Categorization:

{
"kind": "Categorization",
"trigger_field": "status",
"includes": {
"type": { "$eq": "active" }
}
}

Slice:

{
"kind": "Slice",
"trigger_field": "timestamp",
"group_on": "chatId",
"name": "messagesByChat",
"includes": {
"deleted": { "$eq": false }
}
}

Response:

{
// Success confirmation
}

POST /reindex

Rebuild an index by scanning all nodes.

Request:

{
"index": {
"silo": "string",
"store": "string",
"trigger_field": "fieldName",
"kind": "SecondaryKey" | "Categorization" | "Slice",
"group_on": "fieldName", // For Slice only
"name": "sliceName" // For Slice only
},
"shard": "string"
}

Warning: This operation scans all nodes in the store and can be slow for large datasets.

Replication Endpoints

These endpoints are restricted to master token access only.

POST /replicas/cold_sync

Initiate a cold sync (full database snapshot).

Authorization: Master token only

Response:

{
"job_id": "uuid",
"status": "accepted",
"message": "Cold sync job started. Poll /replicas/cold_sync_status/{job_id} for status."
}

Status: 202 Accepted

GET /replicas/cold_sync_status/{job_id}

Check the status of a cold sync job.

Authorization: Master token only

Response:

{
"job_id": "string",
"status": "InProgress" | "Completed" | { "Failed": "error message" },
"created_at": number,
"completed_at": number | null,
"tar_path": "string" | null,
"tar_size": number | null,
"snapshot_time": number | null
}

GET /replicas/cold_sync_download/{job_id}

Download the completed snapshot tar file.

Authorization: Master token only

Response:

  • 200 - Tar file stream with headers:
    • Content-Type: application/x-tar
    • Content-Length: {size}
    • x-garak-locktime: {timestamp}
  • 202 - Job still in progress
  • 404 - Job not found
  • 500 - Job failed or file not accessible

GET /replicas/hot_sync

Retrieve oplog entries for incremental replication.

Authorization: Master token only

Headers:

x-garak-latest: {last_known_timestamp}

Response:

[
{
"Write": {
"silo": "string",
"shard": "string",
"store": "string",
"node_id": number,
"data": {...},
"is_new_node": boolean
}
},
{
"Delete": {
"silo": "string",
"shard": "string",
"store": "string",
"node_id": number
}
}
]

Response Headers:

x-garak-latest: {current_timestamp}

Error Response Format

All errors return JSON with the following structure:

{
"field": "fieldName", // Field that caused error (if applicable)
"code": "ERROR_CODE",
"message": "Human readable error message",
"kind": "KIND_CONSTANT"
}

Error Kinds

KindDescription
KIND_OPERATION_INCOMPATIBLE_WITH_FIELDOperation cannot be performed on field type
KIND_NODE_NOT_FOUNDNode ID does not exist
KIND_SCHEMA_VIOLATIONData violates index constraints
KIND_MALFORMED_INDEXIndex definition or data is corrupted
KIND_OVERSIZE_NODENode data exceeds 25KB limit

Common Error Codes

CodeHTTP StatusDescription
ENOTFOUND404Node not found
ETARGETFIELDNOTARR400Field is not an array
ETARGETFIELDNOTINT400Field is not a number
ERROR_NODE_TOO_LARGE400Node exceeds 25KB
EDUPLICATE400Violates secondary key uniqueness