Endpoints

Booster database

Read and write the records of a booster panel's database from your own server with an API key.

View as Markdown

A booster panel is a small web application built in AYETO Booster Studio. A panel can have its own database, and the endpoints on this page let your server read and change that database with an API key: import data into a panel, sync it with another system, or process what the panel's users entered.

All endpoints are under /api/v3/booster/database/private/. Panels themselves reach their database through a separate access path that is not covered here.

Concepts

Panels and their database

Each panel has at most one database. It exists when the panel's Database access setting (panel details in the app) is Public database or Private database; with No database every endpoint on this page answers 403. Both public and private mode can be used through this API. The database is created on first use and is deleted together with the panel.

Every request names the panel in the panel-id header. The panel's ID is shown, with a copy button, at the top of the panel details in the app.

Collections and records

A database holds collections, and a collection holds records. Collections do not need to be created: a collection exists as soon as it has a record. Collection names may contain only letters, digits and underscores (orders, contact_requests).

A record has exactly two fields:

Field Type Description
key UUID Identifier of the record, unique across the databases of all panels (not only within its collection).
value any The data you stored. Any JSON value is accepted; use an object if you want to filter or sort on its fields.

Records carry no other metadata: responses do not include the collection name, timestamps or the author. If you need any of these, store them in value.

Endpoints that work with one record (get, update, delete and their batch variants) address it by key alone; the collection is taken from the stored record. Endpoints that work with a whole collection (create, find, count, locks) take the collection name in the body.

json
{
  "key": "8f14e45f-ceea-467a-9b1c-4e1f2a6b3c70",
  "value": {
    "customer": {"name": "Jana Nováková", "email": "jana@example.com"},
    "status": "open",
    "priority": 3,
    "created_at": "2026-10-01T09:15:00Z"
  }
}

Who can access a panel's data

Requests act as the user who owns the API key. That user must:

  • own the panel (be its creator, or a member of the group that owns it, for example the administrators of an organization that received the panel from a partner), or
  • have the panel shared with them, directly or through a group they belong to.

Owners and users the panel is shared with (read or write sharing) can read and write the database, in both database modes: writing to the database counts as using the panel, not editing it (changing the panel itself still needs write sharing). Membership in the panel's organization does not by itself give access, and neither do AYETO administrator accounts. The API key must also carry the matching scope:

Scope Endpoints
ayeto.booster.database get, find, count
ayeto.booster.database.write create, create-many, update, update-many, delete, delete-many, lock/acquire, lock/release

The write scope does not include the read scope; a key that reads and writes needs both.

Writes made through this API are writes to the panel's database like any other: they start workflows with a booster record trigger on the panel and run the panel's webhook and e-mail notification modules.

Collection rules

A panel can define collection rules that decide what the panel's anonymous visitors may do. Rules apply only in public database mode; a panel in private mode ignores them, and every endpoint on this page works on all its collections.

For a panel in public mode, only one part of the rules matters for requests on this page:

  • An operation set to none for a collection (or for the * rule that covers it) is disabled for everyone, including this API. The request fails with 403.
  • Every other level (public, secret, private) allows API requests.
  • A collection with no matching rule is fully accessible to the API.

The write constraints of a rule (validation schema, maximum value size, maximum number of records) are not applied to writes made through this API. Validate the data on your side before you write it.

Limits

Limit Value
Records returned by one find up to 1000 when paging (limit_to is at most 1000, limit_from lower than 1000); all matching records when no limits are sent
Filter conditions in one query 1000, including conditions inside AND / OR
Nesting of AND / OR 100 levels
Values in one in condition 100
String filter value 1000 characters
Lock time to live more than 0.1 s, default 3600 s

The batch endpoints have no fixed maximum number of items, but each item is written separately (see Create many records); keep batches to a few hundred items so that a request finishes well within your HTTP timeout.

Querying records

find and count take an optional params object. Its fields follow the general query conventions, with the differences listed here.

Field Type Required Description
filters array no Conditions, all of which must match. See Filters and below.
sort_key string no Field to sort by: key or value.<field>.
sort_order integer no 0 ascending (default when sort_key is set), 1 descending.
limit_from integer no Index of the first record to return, from 0, lower than 1000.
limit_to integer no Index after the last record to return (not a count), at most 1000. When only limit_from is sent, it is limit_from + 100 (at most 1000).

Fields you can filter and sort on

Only two kinds of field names are accepted:

  • key, the record key (compare it with a UUID string).
  • value.<path>, a field inside the record value, with dots for nested objects: value.status, value.customer.email.

Any other name fails with 422. Records whose value lacks the field (or is not an object) simply do not match.

A condition is [field, operator, value] with the operators ==, !=, <, >, <=, >=, regex (always case-insensitive) and in (the value is a list, any of which may match). Conditions can be combined with {"AND": [...]} and {"OR": [...]}; each of these needs at least two conditions and can be nested.

json
{
  "collection": "tickets",
  "params": {
    "filters": [
      ["value.status", "in", ["open", "waiting"]],
      {"OR": [
        ["value.priority", ">=", 3],
        ["value.customer.email", "regex", "@example\\.com$"]
      ]}
    ],
    "sort_key": "value.created_at",
    "sort_order": 1,
    "limit_from": 0,
    "limit_to": 50
  }
}

Things to keep in mind when filtering on values:

  • Types must match. ["value.priority", ">=", 3] does not match a stored "priority": "3". Store numbers as numbers and dates as ISO 8601 strings or as numbers, so that < and > compare them correctly.
  • UUID-like strings stay strings in value.* conditions, so a UUID you stored as text is found as text.
  • The characters <, >, " and ' in a string filter value are escaped before the comparison, so a condition on text that contains them does not match. A string filter value must also not start with $.

Sorting and paging

Without sort_key, records come newest first. With sort_key, they are sorted by that field.

limit_from and limit_to are positions in the result, so the second page of 50 is "limit_from": 50, "limit_to": 100. Because limit_to cannot exceed 1000, paging with limits reaches only the first 1000 matching records. To go further, page by value: sort on a field and filter on the last value you received, for example ["value.created_at", "<", "2026-09-30T12:00:00Z"] with "sort_key": "value.created_at", "sort_order": 1.

If you send neither limit_from nor limit_to, find returns all matching records. Use that only for collections you know are small.

count applies the filters and ignores sorting and paging: it returns the total number of matching records.

Common request headers and errors

Every endpoint on this page needs these headers:

Header Description
uni-api-key Your API key. See Authentication.
panel-id ID of the panel whose database you access.
Content-Type application/json

Errors are JSON of the form {"detail": "..."} (see Errors). These can come from every endpoint:

Status detail Cause
401 API key not provided / API key is invalid The key is empty, unknown, disabled or lacks the endpoint's scope.
403 permission denied You neither own the panel nor have it shared with you.
403 database access is not allowed for this panel The panel's database access is set to No database.
403 operation '<operation>' is disabled for collection '<collection>' A collection rule of a panel in public mode sets this operation to none. The operation is create, read, update, delete, count or lock.
404 panel not found No panel with this ID, or the panel is disabled.
422 validation error The panel-id header is missing or not a UUID, or the body does not match the request model.
422 Collection name can only contain alphanumeric characters and underscores Invalid collection name (Collection cannot be empty for an empty one).
429 rate limit message Too many requests; wait for the time in the Retry-After header. See Rate limits.
500 server error Unexpected failure; retry later.

Find records

Returns the records of a collection that match the query.

Endpoint POST /api/v3/booster/database/private/find
Scope ayeto.booster.database
Rate limit default

Request

Field Type Required Description
collection string yes Collection name.
params object no Filters, sorting and paging; see Querying records. Without it, all records of the collection are returned, newest first.

Response

An array of records (key, value). An unknown or empty collection returns [].

Errors

Status detail Cause
422 Field '<field>' is not allowed for filtering or sorting A filter or sort_key names something other than key or value.<path>.
422 Each filter condition must be a list of [field, operator, value] Malformed condition.
422 Operator '<operator>' is not allowed Unknown operator.
422 '<AND/OR>' operator requires a list of at least 2 conditions The value of AND / OR is not a list of at least two conditions.
422 Logical filter must have exactly one key ('AND' or 'OR') Malformed logical condition, e.g. {} or an object with several keys.
422 limit_from must be lower than 1000 limit_from of 1000 or more (paging reaches only the first 1000 records).
422 Maximum number of filter conditions is 1000 (including conditions inside AND/OR) Too many conditions.
422 sort_order must be 0 (ASC) or 1 (DESC) Invalid sort_order.
422 limit_to cannot exceed 1000 limit_to above 1000.
422 limit_from cannot be greater than limit_to Inverted range.

Examples

bash
curl -X POST "https://ayeto.ai/api/v3/booster/database/private/find" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
  -H "Content-Type: application/json" \
  -d '{
    "collection": "tickets",
    "params": {
      "filters": [["value.status", "==", "open"]],
      "sort_key": "value.priority",
      "sort_order": 1,
      "limit_from": 0,
      "limit_to": 20
    }
  }'
json
[
  {
    "key": "8f14e45f-ceea-467a-9b1c-4e1f2a6b3c70",
    "value": {
      "customer": {"name": "Jana Nováková", "email": "jana@example.com"},
      "status": "open",
      "priority": 3,
      "created_at": "2026-10-01T09:15:00Z"
    }
  },
  {
    "key": "c9f0f895-fb98-4b91-8f2a-6d3e5c1b0a47",
    "value": {
      "customer": {"name": "Petr Svoboda", "email": "petr@example.org"},
      "status": "open",
      "priority": 1,
      "created_at": "2026-10-01T11:40:00Z"
    }
  }
]

Count records

Returns the number of records of a collection that match the filters.

Endpoint POST /api/v3/booster/database/private/count
Scope ayeto.booster.database
Rate limit default

Request

Field Type Required Description
collection string yes Collection name.
params object no Same object as for Find records. Only filters affect the result; sorting and paging are validated but ignored.

Response

An integer.

Errors

The query errors of Find records.

Examples

bash
curl -X POST "https://ayeto.ai/api/v3/booster/database/private/count" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
  -H "Content-Type: application/json" \
  -d '{"collection": "tickets", "params": {"filters": [["value.status", "==", "open"]]}}'
json
42

Get a record

Returns one record by its key.

Endpoint POST /api/v3/booster/database/private/get
Scope ayeto.booster.database
Rate limit default

Request

Field Type Required Description
key UUID yes Key of the record.

Response

The record.

Errors

Status detail Cause
404 Database record not found This panel's database has no record with the key.

Examples

bash
curl -X POST "https://ayeto.ai/api/v3/booster/database/private/get" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
  -H "Content-Type: application/json" \
  -d '{"key": "8f14e45f-ceea-467a-9b1c-4e1f2a6b3c70"}'
json
{
  "key": "8f14e45f-ceea-467a-9b1c-4e1f2a6b3c70",
  "value": {
    "customer": {"name": "Jana Nováková", "email": "jana@example.com"},
    "status": "open",
    "priority": 3,
    "created_at": "2026-10-01T09:15:00Z"
  }
}

Create a record

Adds a record to a collection.

Endpoint POST /api/v3/booster/database/private/create
Scope ayeto.booster.database.write
Rate limit default

Request

Field Type Required Description
collection string yes Collection name (letters, digits, underscores). The collection is created with its first record.
value any no The data to store. Omitted means null.
key UUID no Key of the new record. Generated when omitted; it must not be used by any existing record of any panel (otherwise 422).

Response

The created record, including its key.

Errors

Status detail Cause
422 a record with key '<key>' already exists The key you sent is used by an existing record (of this or another panel).
500 error creating database record The record could not be stored (rarely, two requests created the same key at the same moment).

Examples

bash
curl -X POST "https://ayeto.ai/api/v3/booster/database/private/create" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
  -H "Content-Type: application/json" \
  -d '{
    "collection": "tickets",
    "value": {
      "customer": {"name": "Jana Nováková", "email": "jana@example.com"},
      "status": "open",
      "priority": 3,
      "created_at": "2026-10-01T09:15:00Z"
    }
  }'
json
{
  "key": "8f14e45f-ceea-467a-9b1c-4e1f2a6b3c70",
  "value": {
    "customer": {"name": "Jana Nováková", "email": "jana@example.com"},
    "status": "open",
    "priority": 3,
    "created_at": "2026-10-01T09:15:00Z"
  }
}

Create many records

Adds several records, possibly to different collections.

Endpoint POST /api/v3/booster/database/private/create-many
Scope ayeto.booster.database.write
Rate limit default

Request

The body is a JSON array of objects of the same shape as for Create a record (collection, value, optional key).

The items are written one by one, in order. The batch is not atomic: if an item fails, the request returns the error, the records before it stay created and the items after it are not written. An item whose key already exists stops the batch with 422. Before retrying a failed batch, check what was written, or send your own keys so that you can tell which items exist.

Response

An array of the created records, in request order.

Errors

The errors of Create a record, for the first item that failed.

Examples

bash
curl -X POST "https://ayeto.ai/api/v3/booster/database/private/create-many" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
  -H "Content-Type: application/json" \
  -d '[
    {"collection": "products", "key": "0b6e8c1a-5d2f-4e3b-9a7c-1f4d6e8a2b30", "value": {"sku": "MUG-01", "name": "Mug", "quantity": 12}},
    {"collection": "products", "key": "5a9d3f2e-8c1b-4a6e-b7d0-3e2f1c9a8b54", "value": {"sku": "TEE-M", "name": "T-shirt M", "quantity": 40}}
  ]'
json
[
  {"key": "0b6e8c1a-5d2f-4e3b-9a7c-1f4d6e8a2b30", "value": {"sku": "MUG-01", "name": "Mug", "quantity": 12}},
  {"key": "5a9d3f2e-8c1b-4a6e-b7d0-3e2f1c9a8b54", "value": {"sku": "TEE-M", "name": "T-shirt M", "quantity": 40}}
]

Update a record

Replaces the value of a record.

Endpoint POST /api/v3/booster/database/private/update
Scope ayeto.booster.database.write
Rate limit default

Request

Field Type Required Description
key UUID yes Key of the record.
value any no The new value. It replaces the stored value completely; fields you leave out are removed. Omitted means null.

To change a single field, read the record, change the field and send the whole value back. If other clients may write the same record at the same time, do this under a lock.

Response

The updated record.

Errors

Status detail Cause
404 Database record not found This panel's database has no record with the key.

Examples

bash
curl -X POST "https://ayeto.ai/api/v3/booster/database/private/update" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "8f14e45f-ceea-467a-9b1c-4e1f2a6b3c70",
    "value": {
      "customer": {"name": "Jana Nováková", "email": "jana@example.com"},
      "status": "closed",
      "priority": 3,
      "created_at": "2026-10-01T09:15:00Z"
    }
  }'
json
{
  "key": "8f14e45f-ceea-467a-9b1c-4e1f2a6b3c70",
  "value": {
    "customer": {"name": "Jana Nováková", "email": "jana@example.com"},
    "status": "closed",
    "priority": 3,
    "created_at": "2026-10-01T09:15:00Z"
  }
}

Update many records

Replaces the values of several records.

Endpoint POST /api/v3/booster/database/private/update-many
Scope ayeto.booster.database.write
Rate limit default

Request

The body is a JSON array of objects of the same shape as for Update a record (key, value). Items are written one by one, in order, and the batch is not atomic: a failing item stops the request, the items before it stay updated.

Response

An array of the updated records, in request order.

Errors

The errors of Update a record, for the first item that failed.

Examples

bash
curl -X POST "https://ayeto.ai/api/v3/booster/database/private/update-many" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
  -H "Content-Type: application/json" \
  -d '[
    {"key": "0b6e8c1a-5d2f-4e3b-9a7c-1f4d6e8a2b30", "value": {"sku": "MUG-01", "name": "Mug", "quantity": 10}},
    {"key": "5a9d3f2e-8c1b-4a6e-b7d0-3e2f1c9a8b54", "value": {"sku": "TEE-M", "name": "T-shirt M", "quantity": 37}}
  ]'
json
[
  {"key": "0b6e8c1a-5d2f-4e3b-9a7c-1f4d6e8a2b30", "value": {"sku": "MUG-01", "name": "Mug", "quantity": 10}},
  {"key": "5a9d3f2e-8c1b-4a6e-b7d0-3e2f1c9a8b54", "value": {"sku": "TEE-M", "name": "T-shirt M", "quantity": 37}}
]

Delete a record

Deletes one record.

Endpoint POST /api/v3/booster/database/private/delete
Scope ayeto.booster.database.write
Rate limit default

Request

Field Type Required Description
key UUID yes Key of the record.

Response

null.

Errors

Status detail Cause
404 Database record not found This panel's database has no record with the key (also when it was already deleted).

Examples

bash
curl -X POST "https://ayeto.ai/api/v3/booster/database/private/delete" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
  -H "Content-Type: application/json" \
  -d '{"key": "8f14e45f-ceea-467a-9b1c-4e1f2a6b3c70"}'

Delete many records

Deletes several records.

Endpoint POST /api/v3/booster/database/private/delete-many
Scope ayeto.booster.database.write
Rate limit default

Request

The body is a JSON array of objects with a key field, as for Delete a record. Records are deleted one by one, in order; a failing item (for example a key that does not exist) stops the request, and the records before it stay deleted.

Response

null.

Errors

The errors of Delete a record, for the first item that failed.

Examples

bash
curl -X POST "https://ayeto.ai/api/v3/booster/database/private/delete-many" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
  -H "Content-Type: application/json" \
  -d '[
    {"key": "0b6e8c1a-5d2f-4e3b-9a7c-1f4d6e8a2b30"},
    {"key": "5a9d3f2e-8c1b-4a6e-b7d0-3e2f1c9a8b54"}
  ]'

Locks

Locks let several clients (your servers, the panel itself) take turns on the same data. A lock has a time to live and is one of two kinds:

  • Record lock: collection and key. Use the record's actual collection; a lock taken under another collection name does not protect the record.
  • Collection lock: collection without key.

Locks are advisory: the server does not check them. get, find, count, create, update, delete and their batch variants go through whether a record or collection is locked or not. A lock only makes other lock/acquire calls for the same lock wait, so it protects data only when every client that writes it takes the lock first; coordinating that is up to the panel and your code.

  • Record and collection locks are independent. Holding a collection lock does not stop anyone from taking a record lock in it, and the other way round.
  • Anyone may release a lock. release frees the lock whoever took it, and a second acquire of a lock you already hold waits like anyone else's. Release a lock only from the code that acquired it.
  • Locks expire. Every lock is freed automatically after its ttl, even if it was never released. Choose a TTL longer than your work takes, but short enough that a crashed client does not block others for long.

Locking is the lock operation of collection rules; both endpoints need the write scope and the same access to the panel as writing.

Acquire a lock

Takes a record or collection lock, waiting until it is free.

Endpoint POST /api/v3/booster/database/private/lock/acquire
Scope ayeto.booster.database.write
Rate limit default

Request

Field Type Required Description
collection string yes Collection name.
key UUID no Record key. Omit it to take the collection lock.
acquire_timeout number no Seconds to wait for the lock. 0 fails at once when the lock is taken. When omitted, the request waits as long as it takes, up to the remaining TTL of the current holder; always send a value shorter than your HTTP timeout.
ttl number no Seconds after which the lock is released automatically. Must be greater than 0.1. Default 3600.

Response

null, once the lock is yours.

Errors

Status detail Cause
423 Failed to acquire lock for collection '<collection>' and key '<key>' The lock stayed taken for acquire_timeout seconds (<key> is None for a collection lock). Retry later.
422 validation error ttl is 0.1 or less.

Examples

bash
curl -X POST "https://ayeto.ai/api/v3/booster/database/private/lock/acquire" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
  -H "Content-Type: application/json" \
  -d '{"collection": "products", "key": "0b6e8c1a-5d2f-4e3b-9a7c-1f4d6e8a2b30", "acquire_timeout": 10, "ttl": 30}'

Release a lock

Frees a record or collection lock, whoever holds it.

Endpoint POST /api/v3/booster/database/private/lock/release
Scope ayeto.booster.database.write
Rate limit default

Request

Field Type Required Description
collection string yes Collection name, as sent to lock/acquire.
key UUID no Record key, as sent to lock/acquire. Omit it for the collection lock.

Response

null. Releasing a lock that is not taken (or has expired) also succeeds.

Examples

bash
curl -X POST "https://ayeto.ai/api/v3/booster/database/private/lock/release" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
  -H "Content-Type: application/json" \
  -d '{"collection": "products", "key": "0b6e8c1a-5d2f-4e3b-9a7c-1f4d6e8a2b30"}'

Read-modify-write under a lock

update replaces the whole value, so two clients that each read a record, change it and write it back can overwrite each other. Take the record lock around the whole sequence and release it in a finally block; every client writing the record must do the same:

python
import os
import time

import requests

BASE_URL = "https://ayeto.ai/api/v3/booster/database/private"
HEADERS = {
    "uni-api-key": os.environ["AYETO_API_KEY"],
    "panel-id": "3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90",
}


def call(path: str, body):
    response = requests.post(f"{BASE_URL}/{path}", json=body, headers=HEADERS, timeout=30)
    response.raise_for_status()
    return response.json()


def change_stock(key: str, delta: int, attempts: int = 3) -> dict:
    """Add `delta` to the quantity of a product record without losing concurrent changes."""
    lock = {"collection": "products", "key": key}
    for attempt in range(attempts):
        try:
            # wait at most 10 s; the lock frees itself after 30 s if this process dies
            call("lock/acquire", {**lock, "acquire_timeout": 10, "ttl": 30})
            break
        except requests.HTTPError as e:
            if e.response.status_code != 423 or attempt == attempts - 1:
                raise
            time.sleep(2)

    try:
        record = call("get", {"key": key})
        value = record["value"]
        value["quantity"] = value.get("quantity", 0) + delta
        return call("update", {"key": key, "value": value})
    finally:
        call("lock/release", lock)


print(change_stock("0b6e8c1a-5d2f-4e3b-9a7c-1f4d6e8a2b30", -2))