Database
This section requires authentication.
Please refer to the Authentication section for more details.
Overview
The Database API lets mobile SDK consumers execute SQL queries against a SQLite-compatible database linked to their app via the AppAmbit dashboard. Each app can have one linked database at a time; a single database can be shared across multiple apps belonging to the same team.
Authentication
All endpoints require a consumer Bearer token obtained from GET /api/consumer/token. The token is scoped to an app via the app_key claim.
| Header | Value |
|---|---|
| Authorization | Bearer {YOUR_AUTH_KEY} |
| Content-Type | application/json |
| Accept | application/json |
Base URL
/api/db
Execute Single Query
Execute a single SQL statement against the app's linked database.
POST/api/db/query
Body Parameters
Required Parameters
| Field | Type | Description |
|---|---|---|
sql |
string |
The SQL statement to execute |
Optional Parameters
| Field | Type | Description |
|---|---|---|
params |
array |
Positional or named parameters for the statement |
Example Request
{
"sql": "SELECT id, name FROM users WHERE active = ?",
"params": [1]
}
Example Response
{
"results": [
{
"columns": ["id", "name"],
"rows": [[1, "Alice"], [2, "Bob"]],
"rows_read": 2,
"rows_written": 0
}
],
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
Response Fields
Top-level:
| Field | Type | Description |
|---|---|---|
results |
array |
Array with one result object per executed statement |
request_id |
string (UUID) |
Unique identifier for this request, useful for tracing |
Result object:
| Field | Type | Description |
|---|---|---|
columns |
string[] |
Column names in result order |
rows |
array[] |
Array of row arrays; values are typed (integer, float, string, or null) |
rows_read |
integer |
Number of rows read from storage |
rows_written |
integer |
Number of rows written to storage |
error |
string |
Present only when the statement produced a database-level error; HTTP status remains 200 |
Execute Batch Statements
Execute multiple SQL statements in a single request. Optionally wraps them in a transaction.
POST/api/db/batch
Body Parameters
Required Parameters
| Field | Type | Description |
|---|---|---|
statements |
array |
Array of statement objects (minimum 1) |
statements[].sql |
string |
The SQL statement |
Optional Parameters
| Field | Type | Description |
|---|---|---|
statements[].params |
array |
Positional or named parameters for this statement |
transaction |
boolean |
When true, wraps all statements in BEGIN / COMMIT. Default: false |
Example Request
{
"statements": [
{
"sql": "INSERT INTO users (name, email) VALUES (?, ?)",
"params": ["Alice", "alice@example.com"]
},
{
"sql": "UPDATE stats SET user_count = user_count + 1"
}
],
"transaction": true
}
Example Response
{
"results": [
{
"columns": [],
"rows": [],
"rows_read": 0,
"rows_written": 1
},
{
"columns": [],
"rows": [],
"rows_read": 1,
"rows_written": 1
}
],
"request_id": "550e8400-e29b-41d4-a716-446655440001"
}
Using transaction: true
BEGIN and COMMIT are injected server-side — do not include them manually in statements. If any statement contains a forbidden query, the entire batch is rejected before any statement reaches the database.
Error Responses
All errors return a JSON object with error (machine-readable code) and message (human-readable description).
403 Forbidden — Database Not Linked
The app has no database linked. Link one from the AppAmbit dashboard.
{
"error": "database_not_linked",
"message": "This app isn't linked to a database. Link it in your AppAmbit dashboard."
}
503 Service Unavailable — Database Unavailable
The linked database exists but is not yet active (e.g., still provisioning).
{
"error": "database_unavailable",
"message": "The database is not yet available. Please try again shortly."
}
403 Forbidden — Forbidden SQL
The SQL statement contains a blocked keyword or does not start with an allowed prefix.
{
"error": "forbidden_query",
"message": "Forbidden SQL statement: ATTACH DATABASE \"evil.db\" AS evil"
}
500 Internal Server Error — Query Execution Error
An unexpected error occurred when communicating with the database backend.
{
"error": "query_error",
"message": "<error detail from the database>"
}
422 Unprocessable Entity — Validation Error
Missing required fields. Standard Laravel validation error format.
{
"message": "The sql field is required.",
"errors": {
"sql": ["The sql field is required."]
}
}
SQL Restrictions
The API enforces a server-side allowlist and blocklist before forwarding SQL to the database.
Allowed Statement Prefixes
SELECT, INSERT, UPDATE, DELETE,
CREATE TABLE, CREATE INDEX,
ALTER TABLE,
DROP TABLE, DROP INDEX,
BEGIN, COMMIT, ROLLBACK
Blocked Keywords
The following keywords are blocked anywhere in the statement:
ATTACH, DETACH, PRAGMA, VACUUM,
LOAD_EXTENSION, SAVEPOINT, RELEASE
Statements not starting with an allowed prefix, or containing a blocked keyword, return 403 forbidden_query without touching the database.
Database Lifecycle
A database linked to an app transitions through the following statuses:
| Status | Queryable | Description |
|---|---|---|
pending |
No | Provisioning requested, not yet started |
provisioning |
No | Being created in the backend |
active |
Yes | Ready to accept queries |
suspended |
No | Temporarily suspended |
deleting |
No | Deletion in progress |
deleted |
No | Permanently removed |
Only active databases accept queries. All other statuses return 503 database_unavailable.
Per-Statement Errors
Database-level errors (e.g., syntax errors, constraint violations) are surfaced inside the results array with HTTP 200. Always inspect results[n].error on every result when doing error-sensitive work.
{
"results": [
{
"columns": [],
"rows": [],
"rows_read": 0,
"rows_written": 0,
"error": "syntax error near \"SELECT\""
}
],
"request_id": "550e8400-e29b-41d4-a716-446655440002"
}
Notes
- The database uses SQLite-compatible syntax. Follow the SQLite SQL dialect when writing queries.
- One database can be shared across multiple apps belonging to the same team; each app can have only one linked database at a time.
- Row values are type-coerced: integers and floats become JSON numbers,
nullbecomes JSONnull, and all other values become strings.