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

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 filters and options
14const filteredResponse = 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
25console.log(filteredResponse.data);
26// [{ 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
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 returnRepresentation: true,
11 },
12 {
13 // Filter: update users where status is 'pending' and created more than 30 days ago
14 status: "eq.pending",
15 created_at: "lt.2024-01-01",
16 }
17);
18
19console.log(`Updated ${response.data?.length} rows`);
20
21// Update all rows (requires force=true)
22const allResponse = await client.databases.update(
23 "your-database-asset-id",
24 "users",
25 {
26 data: { last_sync: new Date().toISOString() },
27 },
28 { force: true }
29);

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

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 { id: "eq.invalid" }
8 );
9} catch (error) {
10 if (error instanceof AthenaIntelligenceError) {
11 switch (error.statusCode) {
12 case 400:
13 console.error("Bad request - check your filter syntax");
14 break;
15 case 403:
16 console.error("Access denied to this database");
17 break;
18 case 404:
19 console.error("Database or table not found");
20 break;
21 case 503:
22 console.error("Database is starting up - retry in a moment");
23 break;
24 default:
25 console.error("API error:", error.message);
26 }
27 }
28}

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.