Skip to content

API Reference

Faucet exposes a REST API at /api/v1 with two main sections:

  • System API (/api/v1/system/) -- manage Faucet configuration (services, roles, API keys, admins)
  • Service API (/api/v1/{serviceName}/) -- query and modify data in connected databases

Authentication

All API endpoints (except login, health checks, and OpenAPI spec) require authentication. Faucet supports two authentication methods:

API Key (X-API-Key header)

API keys are bound to roles that define what operations are allowed. Use the X-API-Key header:

bash
curl http://localhost:8080/api/v1/mydb/_table/users \
  -H "X-API-Key: faucet_a1b2c3d4e5f6..."

JWT Bearer Token (Authorization header)

Admin users authenticate with email/password and receive a JWT token. Use the Authorization: Bearer header:

bash
curl http://localhost:8080/api/v1/mydb/_table/users \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Authentication Flow

Request arrives
  |
  +-- X-API-Key header present?
  |     YES -> Validate key hash against stored keys -> Principal{type:"api_key", role_id:N}
  |     NO  -> Continue
  |
  +-- Authorization: Bearer <token> present?
  |     YES -> Validate JWT signature + claims -> Principal{type:"admin", admin_id:N}
  |     NO  -> 401 Unauthorized

Obtaining a JWT Token

bash
curl -X POST http://localhost:8080/api/v1/system/admin/session \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "password": "yourpassword"}'

Response:

json
{
  "session_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "bearer",
  "expires_in": 86400,
  "admin_id": 1,
  "email": "[email protected]",
  "name": "Admin"
}

Tokens expire after 24 hours by default.


Response Format

Success (list)

All list endpoints return a resource array with optional meta for pagination:

json
{
  "resource": [
    {"id": 1, "name": "Alice", "email": "[email protected]"},
    {"id": 2, "name": "Bob", "email": "[email protected]"}
  ],
  "meta": {
    "count": 2,
    "total": 150,
    "limit": 25,
    "offset": 0,
    "took_ms": 3.45
  }
}

Meta fields:

FieldTypeDescription
countintegerNumber of records in this response
totalintegerTotal matching records (only when include_count=true)
limitintegerMaximum records per page
offsetintegerNumber of records skipped
took_msfloatQuery execution time in milliseconds

Success (single)

Single-resource endpoints return the resource object directly (not wrapped in resource).

Error

json
{
  "error": {
    "code": 404,
    "message": "Table not found: nonexistent",
    "context": {
      "service": "mydb",
      "table": "nonexistent"
    }
  }
}
FieldTypeDescription
codeintegerHTTP status code
messagestringHuman-readable error description
contextobjectAdditional error details (optional)

Health Check Endpoints

These endpoints require no authentication.

GET /healthz

Liveness probe. Returns 200 if the process is running.

json
{"status": "ok"}

GET /readyz

Readiness probe. Returns 200 when the server is ready to accept traffic. Pings all database services and reports their status. Returns 503 if any service is unhealthy.

json
{
  "status": "ok",
  "checks": {
    "mydb": "ok",
    "analytics": "ok"
  }
}

GET /openapi.json

Returns the combined OpenAPI 3.1 specification for all connected services.


System API

All system endpoints (except session management) require admin authentication.

Session Management

POST /api/v1/system/admin/session

Authenticate and obtain a JWT token.

Request body:

json
{
  "email": "[email protected]",
  "password": "yourpassword"
}

Response (200):

json
{
  "session_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "bearer",
  "expires_in": 86400,
  "admin_id": 1,
  "email": "[email protected]",
  "name": "Admin"
}

DELETE /api/v1/system/admin/session

Log out (instructs client to discard token).

Response (200):

json
{
  "success": true,
  "message": "Session invalidated"
}

Service Management

GET /api/v1/system/service

List all configured database services.

Response (200):

json
{
  "resource": [
    {
      "id": 1,
      "name": "mydb",
      "label": "My Database",
      "driver": "postgres",
      "schema": "public",
      "read_only": false,
      "raw_sql_allowed": false,
      "is_active": true,
      "created_at": "2025-01-15T10:30:00Z",
      "updated_at": "2025-01-15T10:30:00Z"
    }
  ],
  "meta": {"count": 1}
}

Note: The DSN is never exposed in list responses.

POST /api/v1/system/service

Create a new database service.

Request body:

json
{
  "name": "mydb",
  "label": "My Database",
  "driver": "postgres",
  "dsn": "postgres://user:pass@localhost:5432/mydb?sslmode=disable",
  "schema": "public",
  "read_only": false,
  "raw_sql_allowed": false
}

Required fields: name, driver, dsn

GET /api/v1/system/service/

Get a single service by name.

PUT /api/v1/system/service/

Update a service configuration. Only non-empty fields in the request body are applied.

DELETE /api/v1/system/service/

Delete a service and disconnect it.

Role Management

GET /api/v1/system/role

List all roles.

POST /api/v1/system/role

Create a new role with optional access rules.

Request body:

json
{
  "name": "readonly",
  "description": "Read-only access to all services",
  "access": [
    {
      "service_name": "mydb",
      "component": "_table/*",
      "verb_mask": 1
    }
  ]
}

See RBAC for verb mask values and access rule details.

GET /api/v1/system/role/

Get a single role by ID, including its access rules.

PUT /api/v1/system/role/

Update a role. If access is provided, it replaces all existing access rules.

DELETE /api/v1/system/role/

Delete a role by ID.

Admin Management

GET /api/v1/system/admin

List all admin accounts (passwords are never exposed).

POST /api/v1/system/admin

Create a new admin account.

Request body:

json
{
  "email": "[email protected]",
  "password": "securepassword",
  "name": "New Admin"
}

Password must be at least 8 characters.

API Key Management

GET /api/v1/system/api-key

List all API keys. The actual key value is never shown -- only the prefix.

Response (200):

json
{
  "resource": [
    {
      "id": 1,
      "key_prefix": "faucet_a1b2c3d",
      "label": "CI pipeline",
      "role_id": 2,
      "is_active": true,
      "created_at": "2025-01-15T10:30:00Z",
      "last_used": "2025-01-16T08:00:00Z"
    }
  ],
  "meta": {"count": 1}
}

POST /api/v1/system/api-key

Create a new API key. The plaintext key is returned once in the response and cannot be retrieved again.

Request body:

json
{
  "role_id": 2,
  "label": "CI pipeline",
  "expires_at": "2026-01-01T00:00:00Z"
}

Required field: role_id

Response (201):

json
{
  "id": 1,
  "api_key": "faucet_a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef12345678",
  "key_prefix": "faucet_a1b2c3d",
  "label": "CI pipeline",
  "role_id": 2,
  "is_active": true,
  "created_at": "2025-01-15T10:30:00Z"
}

Save the api_key value immediately. It cannot be retrieved after this response.

DELETE /api/v1/system/api-key/

Revoke (deactivate) an API key by ID.

Connection Testing

GET /api/v1/system/service/{serviceName}/test

Test connectivity to a database service by pinging it.

Response (200):

json
{
  "success": true,
  "message": "Connection successful"
}

MCP Configuration

GET /api/v1/system/mcp

Returns MCP server configuration, available tools, resources, and transports.

Schema Contract Locking

Manage schema contracts that protect your API from silent breaking changes. See Schema Locking for full documentation.

PUT /api/v1/system/contract/{serviceName}/mode

Set the schema lock mode for a service.

Request body:

json
{ "mode": "auto" }

Valid modes: none, auto, strict.

POST /api/v1/system/contract/

Lock all tables in a service (create contracts for each table).

GET /api/v1/system/contract/

List all contracts for a service.

DELETE /api/v1/system/contract/

Unlock all tables in a service (remove all contracts).

GET /api/v1/system/contract/{serviceName}/diff

Get a drift report comparing all locked contracts against the live database schema.

Response (200):

json
{
  "service_name": "mydb",
  "has_drift": true,
  "has_breaking": true,
  "total_additive": 2,
  "total_breaking": 1,
  "tables": [
    {
      "table_name": "users",
      "has_drift": true,
      "has_breaking": false,
      "items": [
        {
          "type": "additive",
          "category": "column_added",
          "table_name": "users",
          "column_name": "bio",
          "description": "Column \"bio\" was added to table \"users\""
        }
      ]
    }
  ]
}

POST /api/v1/system/contract/{serviceName}/

Lock a single table (create or replace its contract).

GET /api/v1/system/contract/{serviceName}/

Get a single contract. Add ?check=true to include live drift comparison.

DELETE /api/v1/system/contract/{serviceName}/

Unlock a single table (remove its contract).

POST /api/v1/system/contract/{serviceName}/{tableName}/promote

Promote a contract to match the current live schema.


Table CRUD Endpoints

These endpoints operate on data in connected databases. The URL pattern is:

/api/v1/{serviceName}/_table/{tableName}

Where {serviceName} is the name of a registered database service and {tableName} is a table in that database.

GET /api/v1/{serviceName}/_table

List all table names in the service's database.

Response (200):

json
{
  "resource": [
    {"name": "users"},
    {"name": "orders"},
    {"name": "products"}
  ]
}

GET /api/v1/{serviceName}/_table/

Query records from a table.

Query parameters:

ParameterTypeDefaultDescription
filterstring--Filter expression (syntax)
fieldsstringallComma-separated column names to return
orderstring--Sort order (e.g., name ASC, created_at DESC)
limitinteger25Max records to return (capped at 1000)
offsetinteger0Number of records to skip
include_countbooleanfalseInclude total count in meta
idsstring--Comma-separated primary key values

Examples:

bash
# Basic query
curl "http://localhost:8080/api/v1/mydb/_table/users"

# With filtering
curl "http://localhost:8080/api/v1/mydb/_table/users?filter=age%20%3E%2021%20AND%20status%20%3D%20'active'"

# Select specific fields
curl "http://localhost:8080/api/v1/mydb/_table/users?fields=id,name,email"

# With ordering and pagination
curl "http://localhost:8080/api/v1/mydb/_table/users?order=created_at%20DESC&limit=10&offset=20"

# Include total count
curl "http://localhost:8080/api/v1/mydb/_table/users?include_count=true&limit=10"

Response (200):

json
{
  "resource": [
    {"id": 1, "name": "Alice", "email": "[email protected]"},
    {"id": 2, "name": "Bob", "email": "[email protected]"}
  ],
  "meta": {
    "count": 2,
    "limit": 25,
    "offset": 0,
    "took_ms": 1.23
  }
}

NDJSON streaming: Set Accept: application/x-ndjson to receive results as newline-delimited JSON (one JSON object per line). Useful for large result sets.

POST /api/v1/{serviceName}/_table/

Insert one or more records. Accepts three body formats.

Batch mode query parameters: rollback and continue (see Batch Transaction Modes below).

Single record:

json
{"name": "Alice", "email": "[email protected]"}

Array of records:

json
[
  {"name": "Alice", "email": "[email protected]"},
  {"name": "Bob", "email": "[email protected]"}
]

Resource envelope (DreamFactory-compatible):

json
{
  "resource": [
    {"name": "Alice", "email": "[email protected]"},
    {"name": "Bob", "email": "[email protected]"}
  ]
}

Response (201): For databases that support RETURNING (PostgreSQL), the created records including auto-generated fields are returned. For others, the input records plus a row count are returned.

json
{
  "resource": [
    {"id": 1, "name": "Alice", "email": "[email protected]", "created_at": "2025-01-15T10:30:00Z"}
  ],
  "meta": {
    "count": 1,
    "took_ms": 2.45
  }
}

PUT /api/v1/{serviceName}/_table/

Replace records (full update). Each record in the body must include its primary key (id field) or a filter query parameter must be provided.

Batch mode query parameters: rollback and continue (see Batch Transaction Modes below).

Request body:

json
{
  "resource": [
    {"id": 1, "name": "Alice Updated", "email": "[email protected]", "status": "active"}
  ]
}

PATCH /api/v1/{serviceName}/_table/

Partial update of records matching a filter or ID list. Only the provided fields are modified.

Query parameters: filter or ids (at least one required)

Request body:

json
{"status": "archived"}

Or with IDs in the body:

json
{
  "ids": [1, 2, 3],
  "status": "archived"
}

Examples:

bash
# Update by filter
curl -X PATCH "http://localhost:8080/api/v1/mydb/_table/users?filter=status%20%3D%20'inactive'" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": "archived"}'

# Update by IDs
curl -X PATCH "http://localhost:8080/api/v1/mydb/_table/users?ids=1,2,3" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": "archived"}'

DELETE /api/v1/{serviceName}/_table/

Delete records matching a filter or ID list. A filter or IDs must be provided to prevent accidental full-table deletes.

Query parameters: filter or ids (at least one required)

Request body (alternative):

json
{"ids": [1, 2, 3]}

Or:

json
{
  "resource": [
    {"id": 1},
    {"id": 2}
  ]
}

Response (200):

json
{
  "meta": {
    "count": 3,
    "took_ms": 1.12
  }
}

Batch Transaction Modes

All write operations (POST, PUT, PATCH, DELETE) support batch transaction modes via query parameters. These control how errors are handled during batch operations.

Modes

ModeParameterBehavior
Halt (default)(none)Stop at first error. Prior operations are already committed.
Rollback?rollback=trueWrap all operations in a database transaction. On any error, roll back everything. All-or-nothing.
Continue?continue=trueProcess every record independently. Collect errors and return mixed results.

If both rollback and continue are set, rollback takes precedence.

When to use each mode

  • Halt — Default behavior. Good for simple operations where you want fast failure.
  • Rollback — Data integrity scenarios: financial transactions, order processing, any case where partial state is worse than no change.
  • Continue — Bulk data loading: importing CSVs, syncing systems, ETL jobs where you want to process everything and fix failures separately.

Rollback mode

Wraps the entire batch in a database transaction. If any operation fails, all changes are rolled back.

bash
# All-or-nothing insert: if any record fails, none are inserted
curl -X POST "http://localhost:8080/api/v1/mydb/_table/users?rollback=true" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    {"name": "Alice", "email": "[email protected]"},
    {"name": "Bob", "email": "[email protected]"}
  ]'

On success, returns the standard response (201 for POST, 200 for PUT/PATCH/DELETE). On failure, returns the appropriate error status and no records are modified.

Continue mode

Processes each record independently. Returns per-record results with a summary of successes and failures. Available for POST and PUT operations.

bash
# Insert with continue: process all records, report mixed results
curl -X POST "http://localhost:8080/api/v1/mydb/_table/users?continue=true" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    {"name": "Alice", "email": "[email protected]"},
    {"name": "Bob", "email": "[email protected]"},
    {"name": "Charlie", "email": "[email protected]"}
  ]'

Response (200 when there are errors, 201 when all succeed):

json
{
  "resource": [
    {"id": 1, "name": "Alice", "email": "[email protected]"},
    {"error": {"code": 409, "message": "Insert failed: UNIQUE constraint failed: users.email"}},
    {"id": 3, "name": "Charlie", "email": "[email protected]"}
  ],
  "meta": {
    "count": 3,
    "succeeded": 2,
    "failed": 1,
    "errors": [1],
    "took_ms": 5.2
  }
}

Response fields:

FieldTypeDescription
meta.countintegerTotal number of operations attempted
meta.succeededintegerNumber of successful operations
meta.failedintegerNumber of failed operations
meta.errorsarrayZero-indexed positions of failed operations in the resource array
meta.took_msnumberTotal execution time in milliseconds

Each entry in the resource array is either the result of a successful operation (the created/updated record) or an error object with error.code and error.message.

Database support

All five supported databases (PostgreSQL, MySQL, SQL Server, SQLite, Snowflake) support transactions and work with all batch modes.


Schema Endpoints

Introspect and modify database schemas.

GET /api/v1/{serviceName}/_schema

Get the full schema for a service, including all tables, views, columns, primary keys, foreign keys, and indexes.

GET /api/v1/{serviceName}/_schema/

Get the detailed schema for a single table.

Response (200):

json
{
  "name": "users",
  "type": "table",
  "primary_key": ["id"],
  "columns": [
    {
      "name": "id",
      "position": 1,
      "db_type": "integer",
      "go_type": "int32",
      "json_type": "integer",
      "nullable": false,
      "is_primary_key": true,
      "is_auto_increment": true,
      "is_unique": false,
      "default": "nextval('users_id_seq'::regclass)"
    },
    {
      "name": "name",
      "position": 2,
      "db_type": "character varying",
      "go_type": "string",
      "json_type": "string",
      "nullable": false,
      "max_length": 255,
      "is_primary_key": false,
      "is_auto_increment": false,
      "is_unique": false
    },
    {
      "name": "email",
      "position": 3,
      "db_type": "character varying",
      "go_type": "string",
      "json_type": "string",
      "nullable": true,
      "max_length": 255,
      "is_primary_key": false,
      "is_auto_increment": false,
      "is_unique": true
    }
  ],
  "foreign_keys": [],
  "indexes": []
}

POST /api/v1/{serviceName}/_schema

Create a new table.

Request body:

json
{
  "name": "products",
  "columns": [
    {"name": "id", "type": "serial", "is_primary_key": true},
    {"name": "name", "type": "varchar(255)", "is_nullable": false},
    {"name": "price", "type": "decimal(10,2)"},
    {"name": "created_at", "type": "timestamp", "default": "now()"}
  ]
}

Returns the schema of the newly created table (201).

PUT /api/v1/{serviceName}/_schema/

Alter an existing table. Provide a list of schema changes.

Request body:

json
{
  "changes": [
    {
      "type": "add_column",
      "column": "description",
      "definition": {"name": "description", "type": "text", "is_nullable": true}
    },
    {
      "type": "drop_column",
      "column": "old_field"
    },
    {
      "type": "rename_column",
      "column": "name",
      "new_name": "title"
    }
  ]
}

Supported change types: add_column, drop_column, rename_column, modify_column

DELETE /api/v1/{serviceName}/_schema/

Drop a table. This is irreversible.

Response (200):

json
{
  "success": true,
  "message": "Table 'products' dropped successfully"
}

Stored Procedure Endpoints

GET /api/v1/{serviceName}/_proc

List all stored procedures and functions for a service.

Response (200):

json
{
  "resource": [
    {
      "name": "calculate_total",
      "type": "function",
      "return_type": "numeric",
      "parameters": [
        {"name": "order_id", "type": "integer"}
      ]
    }
  ],
  "meta": {"count": 1}
}

POST /api/v1/{serviceName}/_proc/

Call a stored procedure with parameters.

Request body:

json
{
  "order_id": 42
}

Query parameters are also accepted and merged into the parameter map. This allows simple calls like:

bash
curl -X POST "http://localhost:8080/api/v1/mydb/_proc/calculate_total?order_id=42" \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "resource": [
    {"total": 299.99}
  ],
  "meta": {
    "count": 1,
    "took_ms": 5.67
  }
}

Per-Service OpenAPI Spec

GET /api/v1/{serviceName}/_doc

Returns an OpenAPI 3.1 specification for a single service, including schemas derived from actual table definitions.


CORS

Faucet enables CORS with the following defaults:

  • Allowed origins: *
  • Allowed methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
  • Allowed headers: Accept, Authorization, Content-Type, X-API-Key, X-Requested-With
  • Exposed headers: X-Total-Count, X-Request-ID, Link
  • Max age: 300 seconds

Rate Limiting

Request rate limiting middleware is available. See Deployment for configuration.


Request Size Limit

The maximum request body size is 10 MB by default.

Released under the MIT License.