Skip to main content

Overview

Duckling supports multiple isolated database replicas running on a single server instance. Each database gets its own DuckDB file, connection pool, and sync service.

Endpoints

GET /api/databases

List all configured databases. Authentication: Required Request Example:
curl http://localhost:3001/api/databases \
  -H "Authorization: Bearer $DUCKLING_API_KEY"
Response:
{
  "success": true,
  "databases": [
    {
      "id": "lms",
      "name": "LMS Database",
      "duckdbPath": "data/lms.db",
      "createdAt": "2025-11-06T18:58:36.480Z",
      "updatedAt": "2025-11-06T18:58:36.480Z",
      "s3": {
        "enabled": true,
        "bucket": "my-backups",
        "region": "us-east-1",
        "secretAccessKey": "***",
        "encryptionKey": "***"
      }
    },
    {
      "id": "analytics",
      "name": "Analytics Database",
      "duckdbPath": "data/analytics.db",
      "createdAt": "2025-11-07T10:00:00.000Z",
      "updatedAt": "2025-11-07T10:00:00.000Z"
    }
  ]
}
databases
array
Array of database configurations
databases[].id
string
Unique database identifier (used in ?db= query parameter)
databases[].name
string
Human-readable database name
databases[].duckdbPath
string
Path to DuckDB file on disk
MySQL connection strings and S3 credentials are masked in responses for security.

POST /api/databases

Add a new database configuration. Authentication: Required Request Body:
name
string
required
Human-readable database name
mysqlConnectionString
string
required
MySQL connection string in format: mysql://user:pass@host:port/database
Request Example:
curl -X POST http://localhost:3001/api/databases \
  -H "Authorization: Bearer $DUCKLING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "E-Commerce Database",
    "mysqlConnectionString": "mysql://user:password@mysql-host:3306/ecommerce?charset=utf8mb4"
  }'
Response:
{
  "success": true,
  "database": {
    "id": "ecommerce",
    "name": "E-Commerce Database",
    "duckdbPath": "data/ecommerce.db",
    "createdAt": "2025-11-08T14:30:00.000Z",
    "updatedAt": "2025-11-08T14:30:00.000Z"
  }
}
database.id
string
Auto-generated unique ID (sanitized from database name)
database.duckdbPath
string
Auto-generated DuckDB file path: data/{id}.db
After adding a database, run a full sync to populate the DuckDB replica:
curl -X POST 'http://localhost:3001/sync/full?db=ecommerce' \
  -H "Authorization: Bearer $DUCKLING_API_KEY"

PUT /api/databases/:id

Update an existing database configuration. Authentication: Required Path Parameters:
id
string
required
Database ID to update
Request Body:
name
string
Updated database name (optional)
mysqlConnectionString
string
Updated MySQL connection string (optional)
Request Example:
curl -X PUT http://localhost:3001/api/databases/lms \
  -H "Authorization: Bearer $DUCKLING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "LMS Production Database"
  }'
Response:
{
  "success": true,
  "database": {
    "id": "lms",
    "name": "LMS Production Database",
    "duckdbPath": "data/lms.db",
    "createdAt": "2025-11-06T18:58:36.480Z",
    "updatedAt": "2025-11-08T15:00:00.000Z"
  }
}
Only name and mysqlConnectionString fields can be updated. The id and duckdbPath are immutable.

DELETE /api/databases/:id

Remove a database configuration and close its connections. Authentication: Required Path Parameters:
id
string
required
Database ID to delete
Request Example:
curl -X DELETE http://localhost:3001/api/databases/ecommerce \
  -H "Authorization: Bearer $DUCKLING_API_KEY"
Response:
{
  "success": true,
  "message": "Database deleted successfully"
}
Deleting a database configuration does not delete the DuckDB file on disk. You must manually remove the file if needed:
rm data/ecommerce.db

POST /api/databases/:id/test

Test database connections (both MySQL and DuckDB). Authentication: Required Path Parameters:
id
string
required
Database ID to test
Request Example:
curl -X POST http://localhost:3001/api/databases/lms/test \
  -H "Authorization: Bearer $DUCKLING_API_KEY"
Response (Healthy):
{
  "success": true,
  "connections": {
    "mysql": "healthy",
    "duckdb": "healthy"
  }
}
Response (Unhealthy):
{
  "success": false,
  "error": "MySQL connection failed: ECONNREFUSED"
}
Use this endpoint to validate MySQL credentials and network connectivity before adding a database.

Database Configuration File

Database configurations are persisted to data/databases.json. File Location:
  • Host: ./data/databases.json (relative to project root)
  • Container: /app/data/databases.json
Schema:
[
  {
    "id": "lms",
    "name": "LMS Database",
    "mysqlConnectionString": "mysql://user:pass@host:3306/chitti_lms?charset=utf8mb4",
    "duckdbPath": "data/lms.db",
    "createdAt": "2025-11-06T18:58:36.480Z",
    "updatedAt": "2025-11-06T18:58:36.480Z",
    "s3": {
      "enabled": true,
      "bucket": "my-duckling-backups",
      "region": "us-east-1",
      "accessKeyId": "AKIAIOSFODNN7EXAMPLE",
      "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
      "pathPrefix": "lms/",
      "encryption": "client-aes256",
      "encryptionKey": "a3f1c2d4e5b6a7f8..."
    }
  }
]
The databases.json file contains sensitive credentials. Ensure proper file permissions:
chmod 600 data/databases.json

Multi-Database Architecture

Isolation Model

Each database has its own:
  • DuckDB File - Separate .db file: data/{id}.db
  • Connection Pool - Isolated MySQL and DuckDB connections
  • Sync Service - Independent watermark tracking and sync state
  • Automation Service - Separate backup and cleanup schedules

Query Context

All data endpoints accept the ?db={database_id} query parameter:
# Query LMS database
curl -X POST 'http://localhost:3001/api/query?db=lms' \
  -H "Authorization: Bearer $DUCKLING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT COUNT(*) FROM User"}'

# Query Analytics database
curl -X POST 'http://localhost:3001/api/query?db=analytics' \
  -H "Authorization: Bearer $DUCKLING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT COUNT(*) FROM Events"}'

Default Database

If ?db= is omitted, requests default to the default database:
# Uses 'default' database
curl -X POST http://localhost:3001/api/query \
  -H "Authorization: Bearer $DUCKLING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT COUNT(*) FROM User"}'

Example: Adding a New Database

Step 1: Add Database Configuration
curl -X POST http://localhost:3001/api/databases \
  -H "Authorization: Bearer $DUCKLING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production Orders",
    "mysqlConnectionString": "mysql://user:password@prod-mysql:3306/orders"
  }'
Step 2: Test Connection
curl -X POST http://localhost:3001/api/databases/orders/test \
  -H "Authorization: Bearer $DUCKLING_API_KEY"
Step 3: Run Initial Sync
curl -X POST 'http://localhost:3001/sync/full?db=orders' \
  -H "Authorization: Bearer $DUCKLING_API_KEY"
Step 4: Query the New Database
curl -X POST 'http://localhost:3001/api/query?db=orders' \
  -H "Authorization: Bearer $DUCKLING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT COUNT(*) FROM orders"}'

Error Responses

400 Bad Request - Missing Fields:
{
  "success": false,
  "error": "Name and MySQL connection string are required"
}
404 Not Found:
{
  "success": false,
  "error": "Database not found"
}
500 Internal Server Error:
{
  "success": false,
  "error": "Failed to connect to MySQL: ECONNREFUSED"
}

Build docs developers (and LLMs) love