Database API

This guide explains how to use the Database API to perform CRUD operations on your database tables. The API uses PostgREST-style query parameters for filtering, which provides a powerful and flexible way to query data.

Overview

The Database API provides four main operations:

OperationHTTP MethodDescription
SelectGETRead rows from a table with filtering, ordering, and pagination
InsertPOSTInsert one or more rows into a table
UpdatePATCHUpdate rows matching filter conditions
DeleteDELETEDelete rows matching filter conditions

All operations use the same endpoint pattern:

/api/v0/databases/{asset_id}/data/{table_name}

Filter Syntax

Filters are passed as query parameters where:

  • The parameter name is the column name
  • The parameter value is the operator and value in the format operator.value

SDK Filter Usage:

  • Python: Pass filters as keyword arguments directly (e.g., status="eq.active")
  • TypeScript: Pass filters via the queryParams option in the 4th argument (e.g., { queryParams: { status: "eq.active" } })
  • cURL: Pass filters as query string parameters (e.g., ?status=eq.active)

Supported Operators

OperatorDescriptionExampleSQL Equivalent
eqEqual?status=eq.activeWHERE status = 'active'
neqNot equal?status=neq.deletedWHERE status != 'deleted'
gtGreater than?age=gt.21WHERE age > 21
gteGreater than or equal?age=gte.18WHERE age >= 18
ltLess than?price=lt.100WHERE price < 100
lteLess than or equal?price=lte.50WHERE price <= 50
likePattern match (case-sensitive)?name=like.*John*WHERE name LIKE '%John%'
ilikePattern match (case-insensitive)?email=ilike.*@gmail.comWHERE email ILIKE '%@gmail.com'
inIn list?status=in.(active,pending)WHERE status IN ('active', 'pending')
isIS NULL/TRUE/FALSE?deleted_at=is.nullWHERE deleted_at IS NULL

Pattern Matching

For like and ilike operators, use * as a wildcard (it gets converted to % in SQL):

  • *value - Ends with “value”
  • value* - Starts with “value”
  • *value* - Contains “value”
$# Find all users with emails ending in @example.com
$?email=ilike.*@example.com
$
$# Find all products starting with "Premium"
$?name=like.Premium*
$
$# Find all descriptions containing "sale"
$?description=ilike.*sale*

Combining Multiple Filters

Multiple filters are combined with AND logic:

$# Find active users over 21
$?status=eq.active&age=gt.21
$
$# Find products between $10 and $50
$?price=gte.10&price=lte.50
$
$# Find pending orders from today
$?status=eq.pending&created_at=gte.2024-01-15

Select (Read) Rows

Read data from a table with optional filtering, ordering, and pagination.

1import { AthenaIntelligenceClient } from '@athenaintel/sdk';
2
3const client = new AthenaIntelligenceClient({
4 apiKey: process.env.ATHENA_API_KEY,
5});
6
7// Basic select - get all rows
8const response = await client.databases.select(
9 "your-database-asset-id",
10 "users"
11);
12
13// With pagination and ordering options
14const paginatedResponse = await client.databases.select(
15 "your-database-asset-id",
16 "users",
17 {
18 select: "id,name,email", // Columns to return
19 order: "created_at.desc", // Sort by created_at descending
20 limit: 50, // Max 50 rows
21 offset: 0, // Skip 0 rows (for pagination)
22 }
23);
24
25// With filters - pass filter conditions via queryParams
26const filteredResponse = await client.databases.select(
27 "your-database-asset-id",
28 "users",
29 {
30 select: "id,name,email",
31 limit: 50,
32 },
33 {
34 queryParams: {
35 status: "eq.active", // Filter: status = 'active'
36 age: "gt.21", // Filter: age > 21
37 }
38 }
39);
40
41console.log(filteredResponse.data);
42// [{ id: 1, name: "John", email: "john@example.com" }, ...]

Select Query Parameters

ParameterTypeDefaultDescription
selectstring* (all)Comma-separated list of columns to return
orderstring-Order by clause (e.g., created_at.desc, name.asc)
limitinteger100Maximum rows to return (1-1000)
offsetinteger0Number of rows to skip
{column}string-Filter condition (e.g., status=eq.active)

Insert Rows

Insert one or more rows into a table.

1// Insert a single row
2const response = await client.databases.insert(
3 "your-database-asset-id",
4 "users",
5 {
6 data: {
7 name: "John Doe",
8 email: "john@example.com",
9 status: "active",
10 },
11 returnRepresentation: true, // Return the inserted row
12 }
13);
14
15console.log(response.data);
16// [{ id: 1, name: "John Doe", email: "john@example.com", status: "active" }]
17
18// Insert multiple rows
19const bulkResponse = await client.databases.insert(
20 "your-database-asset-id",
21 "users",
22 {
23 data: [
24 { name: "Alice", email: "alice@example.com", status: "active" },
25 { name: "Bob", email: "bob@example.com", status: "pending" },
26 ],
27 returnRepresentation: true,
28 }
29);

Update Rows

Update rows that match filter conditions.

Safety Feature: Filters are required by default to prevent accidental bulk updates. To update all rows intentionally, you must pass force=true.

1// Update rows matching a filter - pass filter conditions via queryParams
2const response = await client.databases.update(
3 "your-database-asset-id",
4 "users",
5 {
6 data: {
7 status: "inactive",
8 updated_at: new Date().toISOString(),
9 },
10 return_representation: true,
11 },
12 {
13 // Filter: update users where status is 'pending' and created more than 30 days ago
14 queryParams: {
15 status: "eq.pending",
16 created_at: "lt.2024-01-01",
17 }
18 }
19);
20
21console.log(`Updated ${response.data?.length} rows`);
22
23// Update all rows (requires force=true)
24const allResponse = await client.databases.update(
25 "your-database-asset-id",
26 "users",
27 {
28 force: true,
29 data: { last_sync: new Date().toISOString() },
30 }
31);

Delete Rows

Delete rows that match filter conditions.

Safety Feature: Filters are required by default to prevent accidental bulk deletes. To delete all rows intentionally, you must pass force=true.

1// Delete rows matching a filter - pass filter conditions via queryParams
2const response = await client.databases.delete(
3 "your-database-asset-id",
4 "users",
5 {
6 body: { return_representation: true }, // Return deleted rows
7 },
8 {
9 // Filter: delete users where status is 'deleted' and older than 90 days
10 queryParams: {
11 status: "eq.deleted",
12 deleted_at: "lt.2024-01-01",
13 }
14 }
15);
16
17console.log(`Deleted ${response.data?.length} rows`);
18
19// Delete a specific row by ID
20const singleDelete = await client.databases.delete(
21 "your-database-asset-id",
22 "users",
23 {},
24 {
25 queryParams: { id: "eq.123" }
26 }
27);
28
29// Delete all rows (requires force=true) - USE WITH CAUTION!
30const allDelete = await client.databases.delete(
31 "your-database-asset-id",
32 "temp_logs",
33 { force: true }
34);

Common Filter Examples

Filter by Status

$# Active users only
$?status=eq.active
$
$# Users that are NOT deleted
$?status=neq.deleted
$
$# Users with pending or review status
$?status=in.(pending,review)

Filter by Date/Time

$# Created after a specific date
$?created_at=gt.2024-01-01
$
$# Created within a date range
$?created_at=gte.2024-01-01&created_at=lt.2024-02-01
$
$# Records with no deletion date (not deleted)
$?deleted_at=is.null

Filter by Numeric Values

$# Products under $100
$?price=lt.100
$
$# Products between $50 and $200
$?price=gte.50&price=lte.200
$
$# Orders with quantity greater than 10
$?quantity=gt.10

Filter by Text (Pattern Matching)

$# Email addresses from a specific domain
$?email=ilike.*@company.com
$
$# Names starting with "John"
$?name=like.John*
$
$# Descriptions containing "premium" (case-insensitive)
$?description=ilike.*premium*

Complex Filters

$# Active premium users created this year
$?status=eq.active&tier=eq.premium&created_at=gte.2024-01-01
$
$# Pending orders over $100 from verified customers
$?order_status=eq.pending&total=gt.100&customer_verified=is.true
$
$# Products in multiple categories with stock
$?category=in.(electronics,accessories)&stock=gt.0

Pagination

Use limit and offset for pagination:

1const pageSize = 20;
2let page = 0;
3let hasMore = true;
4
5while (hasMore) {
6 const response = await client.databases.select(
7 "your-database-asset-id",
8 "users",
9 {
10 limit: pageSize,
11 offset: page * pageSize,
12 order: "id.asc",
13 }
14 );
15
16 console.log(`Page ${page + 1}:`, response.data);
17
18 hasMore = response.data.length === pageSize;
19 page++;
20}

Additional Operations

List Tables

Get a list of all tables in the database:

1const tables = await client.databases.listTables("your-database-asset-id");
2console.log(tables.tables);
3// [{ name: "users", schema_name: "public", row_count: 1000 }, ...]

Get Table Schema

Get column information for a specific table:

1const schema = await client.databases.getTableSchema(
2 "your-database-asset-id",
3 "users"
4);
5console.log(schema.columns);
6// [{ name: "id", data_type: "integer", is_nullable: false }, ...]

Check Database Status

Check if a serverless database is running or suspended:

1const status = await client.databases.getStatus("your-database-asset-id");
2console.log(status.status); // "running", "suspended", "starting", etc.

Error Handling

1import { AthenaIntelligenceError } from '@athenaintel/sdk';
2
3try {
4 const response = await client.databases.select(
5 "your-database-asset-id",
6 "users",
7 {},
8 { queryParams: { id: "eq.invalid" } }
9 );
10} catch (error) {
11 if (error instanceof AthenaIntelligenceError) {
12 switch (error.statusCode) {
13 case 400:
14 console.error("Bad request - check your filter syntax");
15 break;
16 case 403:
17 console.error("Access denied to this database");
18 break;
19 case 404:
20 console.error("Database or table not found");
21 break;
22 case 503:
23 console.error("Database is starting up - retry in a moment");
24 break;
25 default:
26 console.error("API error:", error.message);
27 }
28 }
29}

Best Practices

  1. Always use filters for UPDATE and DELETE - The API requires filters by default to prevent accidental bulk modifications. Only use force=true when you explicitly intend to affect all rows.

  2. Use specific column selection - Instead of selecting all columns (*), specify only the columns you need with the select parameter for better performance.

  3. Implement pagination - For large datasets, use limit and offset to paginate results rather than fetching all rows at once.

  4. Handle 503 errors gracefully - Serverless databases may be suspended. Implement retry logic for 503 errors as the database wakes up.

  5. Use appropriate operators - Choose the right operator for your use case:

    • Use ilike for case-insensitive text searches
    • Use in for checking against multiple values
    • Use is.null instead of eq.null for NULL checks
  6. Validate input - Always validate and sanitize any user input before using it in filter values to prevent injection attacks.