VibeSQL Documentation
Complete technical reference for VibeSQL Micro, Server, and SDK.
Quick Start
VibeSQL Micro Micro
Download the single binary for your platform and run it. No installation, no sign-up.
# Download from GitHub releases # https://github.com/PayEz-Net/vibesql-micro/releases # Windows .\vibesql-micro-windows-x64.exe # macOS ./vibesql-micro-macos-arm64 # Linux ./vibesql-micro-linux-x64
The server starts on http://127.0.0.1:5173. Data is stored in ./vibe-data/ relative to where you run the binary.
# Test it immediately
curl -X POST http://127.0.0.1:5173/v1/query \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT 1 AS hello"}'
{
"success": true,
"rows": [{"hello": 1}],
"rowCount": 1,
"executionTime": 0.42
}
VibeSQL Server Server
Production deployment with Docker, Kubernetes, or bare metal.
# Docker docker run -d -p 5432:5432 vibesql/server:latest # Kubernetes kubectl apply -f vibesql-server.yaml
Vibe SDK SDK
TypeScript client for Node.js and browser applications.
npm install @vibe/client
import { createVibeClient } from '@vibe/client'; const client = createVibeClient({ apiUrl: 'http://127.0.0.1:5173', }); const users = client.collection('users'); const result = await users.list({ limit: 10 });
Editions Overview
| Feature | Micro | Server | Cloud |
|---|---|---|---|
| License | Apache 2.0 | Apache 2.0 | Managed |
| Deployment | Single binary | Docker / K8s / bare metal | Fully managed |
| Binary size | ~77 MB | N/A | N/A |
| PostgreSQL | Embedded 16.1 | External (16+) | Managed |
| Multi-tenant | No | Yes | Yes |
| Authentication | None (localhost) | HMAC-SHA256 | Token-based |
| Encryption governance | No | Yes | Yes |
| Schema evolution | No | Yes | Yes |
| Tier-based limits | Fixed | Configurable per tier | Plan-based |
| API endpoint | POST /v1/query |
POST /v1/query + Admin API |
POST /v1/query + Admin API |
| Platforms | Windows, macOS, Linux | Any (.NET 9) | N/A |
Endpoint
All SQL queries are sent as JSON to a single HTTP endpoint:
POST /v1/query Content-Type: application/json
| Edition | Default URL | Port |
|---|---|---|
| Micro | http://127.0.0.1:5173/v1/query |
5173 (HTTP) / 5433 (internal PG) |
| Server | http://your-server:5432/v1/query |
Configurable |
Request Format
{
"sql": "SELECT * FROM users WHERE id = 1"
}
| Field | Type | Required | Constraints |
|---|---|---|---|
sql |
string | Yes | Max 10,240 bytes (10 KB). Must start with a valid SQL keyword. |
Response Format
Success (HTTP 200)
{
"success": true,
"rows": [
{"id": 1, "name": "Alice", "email": "alice@example.com"}
],
"rowCount": 1,
"executionTime": 0.42
}
| Field | Type | Description |
|---|---|---|
success | boolean | Always true for successful queries |
rows | array | null | Array of row objects (column name → value). null for non-SELECT statements without RETURNING. |
rowCount | integer | Number of rows returned or affected |
executionTime | float | Execution time in milliseconds |
Error (HTTP 4xx/5xx)
{
"success": false,
"error": {
"code": "INVALID_SQL",
"message": "Invalid SQL syntax",
"detail": "PostgreSQL error: relation \"nonexistent\" does not exist"
}
}
| Field | Type | Description |
|---|---|---|
success | boolean | Always false |
error.code | string | Machine-readable error code (see Error Codes) |
error.message | string | Human-readable error message |
error.detail | string | Additional context (optional) |
Value encoding notes:
- SQL
NULLvalues are returned as JSONnull - JSONB columns are returned as JSON objects/arrays (not as strings)
- TIMESTAMP values are returned as ISO 8601 strings
- NUMERIC values are returned as JSON numbers
- BOOLEAN values are returned as JSON
true/false
Supported SQL Statements
Queries must start with one of these 8 keywords (case-insensitive):
| Keyword | Description | WHERE Required? |
|---|---|---|
SELECT | Read rows from tables | No |
INSERT | Create new rows | No |
UPDATE | Modify existing rows | Yes |
DELETE | Remove rows | Yes |
CREATE | Create tables, indexes, etc. | No |
DROP | Remove tables | No |
ALTER | Modify table structure | No |
TRUNCATE | Remove all rows from a table | No |
WHERE 1=1. Omitting WHERE returns UNSAFE_QUERY (HTTP 400).
PostgreSQL Data Types
VibeSQL supports all standard PostgreSQL data types. Here is the complete reference with size restrictions:
| Type | Description | Size / Range |
|---|---|---|
TEXT | Variable-length string | Unlimited (up to 1 GB) |
VARCHAR(n) | Variable-length string with limit | Max n characters |
CHAR(n) | Fixed-length string | Exactly n characters, space-padded |
INTEGER / INT | Signed integer | -2,147,483,648 to 2,147,483,647 (4 bytes) |
BIGINT | Large signed integer | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 (8 bytes) |
SMALLINT | Small signed integer | -32,768 to 32,767 (2 bytes) |
SERIAL | Auto-incrementing integer | 1 to 2,147,483,647 |
BIGSERIAL | Auto-incrementing large integer | 1 to 9,223,372,036,854,775,807 |
NUMERIC(p,s) | Exact decimal number | p total digits, s after decimal. Max precision: 131,072 digits before decimal, 16,383 after. |
REAL | Floating-point (single) | 6 decimal digits precision (4 bytes) |
DOUBLE PRECISION | Floating-point (double) | 15 decimal digits precision (8 bytes) |
BOOLEAN | True or false | TRUE, FALSE, NULL |
DATE | Calendar date | 4713 BC to 5874897 AD (4 bytes) |
TIME | Time of day (no timezone) | 00:00:00 to 24:00:00 (8 bytes) |
TIMESTAMP | Date and time (no timezone) | 4713 BC to 294276 AD (8 bytes) |
TIMESTAMPTZ | Date and time with timezone | Same range, timezone-aware (8 bytes) |
UUID | Universally unique identifier | 128-bit RFC 4122 format (16 bytes) |
JSONB | Binary JSON data | Up to 255 MB per value |
JSON | Text JSON data | Up to 255 MB per value (prefer JSONB) |
TEXT[] | Array of text | Multi-dimensional arrays supported |
INTEGER[] | Array of integers | Multi-dimensional arrays supported |
UUID[] | Array of UUIDs | Multi-dimensional arrays supported |
BYTEA | Binary data | Up to 1 GB |
CREATE TABLE
-- Basic table CREATE TABLE users ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE, age INTEGER, created_at TIMESTAMP DEFAULT NOW() ); -- Table with JSONB and UUID CREATE TABLE documents ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), data JSONB NOT NULL, tags TEXT[], price NUMERIC(10,2), is_active BOOLEAN DEFAULT true ); -- Table with foreign keys CREATE TABLE messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), conversation_id UUID REFERENCES conversations(id), agent_id UUID REFERENCES agents(id), content TEXT, metadata JSONB, created_at TIMESTAMP DEFAULT NOW() );
SELECT
-- All columns SELECT * FROM users; -- Specific columns SELECT name, email FROM users; -- With WHERE SELECT * FROM users WHERE age > 21 AND is_active = true; -- ORDER BY, LIMIT, OFFSET (pagination) SELECT * FROM users ORDER BY name ASC LIMIT 10 OFFSET 20; -- Aggregates SELECT role, COUNT(*) AS count FROM users GROUP BY role HAVING COUNT(*) > 5; -- JOINs SELECT u.name, m.content FROM users u JOIN messages m ON u.id = m.user_id WHERE m.created_at > '2024-01-01'; -- Subqueries SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE total > 100);
INSERT
-- Single row INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com'); -- With RETURNING (get inserted data back) INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com') RETURNING id, name, created_at; -- Multiple rows INSERT INTO users (name, email) VALUES ('Charlie', 'charlie@example.com'), ('Diana', 'diana@example.com'); -- With JSONB data INSERT INTO documents (data) VALUES ('{"name": "Invoice", "amount": 99.50, "tags": ["billing", "q4"]}'); -- With ON CONFLICT (upsert) INSERT INTO users (email, name) VALUES ('alice@example.com', 'Alice Updated') ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name;
UPDATE
UNSAFE_QUERY (HTTP 400). Use WHERE 1=1 to update all rows intentionally.
-- Update specific row UPDATE users SET email = 'newemail@example.com' WHERE id = 1; -- Update multiple columns UPDATE users SET name = 'Alice Smith', age = 31 WHERE id = 1; -- Update all rows (explicit) UPDATE users SET is_active = false WHERE 1=1; -- Update with RETURNING UPDATE users SET name = 'Updated' WHERE id = 1 RETURNING *; -- Update JSONB field UPDATE documents SET data = jsonb_set(data, '{status}', '"completed"') WHERE id = 1;
DELETE
UNSAFE_QUERY (HTTP 400). Use WHERE 1=1 to delete all rows intentionally.
-- Delete specific row DELETE FROM users WHERE id = 1; -- Delete with condition DELETE FROM messages WHERE created_at < '2024-01-01'; -- Delete all rows (explicit) DELETE FROM temp_data WHERE 1=1; -- Delete with RETURNING DELETE FROM users WHERE id = 1 RETURNING id, name;
ALTER TABLE
-- Add column ALTER TABLE users ADD COLUMN phone TEXT; -- Drop column ALTER TABLE users DROP COLUMN phone; -- Rename column ALTER TABLE users RENAME COLUMN name TO full_name; -- Change column type ALTER TABLE users ALTER COLUMN age TYPE BIGINT; -- Add constraint ALTER TABLE users ADD CONSTRAINT email_unique UNIQUE (email); -- Set default ALTER TABLE users ALTER COLUMN is_active SET DEFAULT true; -- Rename table ALTER TABLE users RENAME TO accounts;
DROP TABLE
-- Drop table DROP TABLE users; -- Drop if exists (no error if missing) DROP TABLE IF EXISTS users; -- Drop with cascading foreign keys DROP TABLE IF EXISTS users CASCADE;
TRUNCATE
-- Remove all rows (faster than DELETE WHERE 1=1) TRUNCATE TABLE users; -- Truncate with cascade TRUNCATE TABLE users CASCADE; -- Truncate and reset auto-increment TRUNCATE TABLE users RESTART IDENTITY;
JSONB Operators
VibeSQL supports all 9 PostgreSQL JSONB operators through standard SQL syntax.
-> Get Field as JSON
Returns a JSON element by key (object) or index (array). Result is JSON type.
SELECT data->'name' FROM documents; -- Returns: "Alice" (as JSON string, with quotes) SELECT data->'tags'->0 FROM documents; -- Returns: "staff" (first array element) SELECT data->'address' FROM documents; -- Returns: {"city": "Portland", "state": "OR"}
->> Get Field as Text
Returns a JSON element as plain text. The most common operator for extracting values.
SELECT data->>'name' FROM documents; -- Returns: Alice (as text, no quotes) SELECT data->>'age' FROM documents; -- Returns: 30 (as text) SELECT data->'address'->>'city' FROM documents; -- Returns: Portland
#> Get Path as JSON
Navigates a JSON path and returns JSON. Path is specified as a text array.
SELECT data #> '{address,city}' FROM documents; -- Returns: "Portland" (as JSON string) SELECT data #> '{tags,0}' FROM documents; -- Returns: "staff" (as JSON string)
#>> Get Path as Text
Navigates a JSON path and returns text.
SELECT data #>> '{address,city}' FROM documents; -- Returns: Portland (as text) SELECT data #>> '{address,state}' FROM documents; -- Returns: OR
@> Contains
Tests if the left JSONB value contains the right JSONB value. Ideal for filtering.
SELECT * FROM documents WHERE data @> '{"role": "admin"}'; -- Rows where data contains role=admin SELECT * FROM documents WHERE data @> '{"tags": ["manager"]}'; -- Rows where tags array contains "manager" SELECT * FROM documents WHERE data->'address' @> '{"state": "OR"}'; -- Rows where address.state = "OR"
<@ Contained By
Tests if the left JSONB value is contained by the right. Reverse of @>.
SELECT * FROM documents WHERE '{"role": "admin", "name": "Alice"}' <@ data; -- Rows where data contains both role=admin AND name=Alice
? Key Exists
Tests if a key exists in the top-level of a JSONB object.
SELECT * FROM documents WHERE data ? 'name'; -- Rows that have a "name" key SELECT * FROM documents WHERE data ? 'phone'; -- Rows that have a "phone" key
?| Any Key Exists
Tests if any of the given keys exist.
SELECT * FROM documents WHERE data ?| array['phone', 'email', 'name']; -- Rows with at least one of: phone, email, or name
?& All Keys Exist
Tests if all of the given keys exist.
SELECT * FROM documents WHERE data ?& array['name', 'age', 'role']; -- Rows that have ALL of: name, age, and role
Operator Summary
| Operator | Returns | Use When |
|---|---|---|
-> | JSONB | You need JSON for further operations or chaining |
->> | text | You need the value as a plain string |
#> | JSONB | You need a nested value as JSON via path |
#>> | text | You need a nested value as text via path |
@> | boolean | Filtering: left contains right |
<@ | boolean | Filtering: left is contained by right |
? | boolean | Check if a single key exists |
?| | boolean | Check if any key in array exists |
?& | boolean | Check if all keys in array exist |
Common JSONB Patterns
-- Filter by nested value SELECT data->>'name' AS name FROM documents WHERE data->'address'->>'city' = 'Portland'; -- Sort by JSONB field (cast to int for numeric sort) SELECT data->>'name' AS name, (data->>'age')::int AS age FROM documents ORDER BY (data->>'age')::int DESC; -- Count by JSONB field SELECT data->>'role' AS role, COUNT(*) AS count FROM documents GROUP BY data->>'role'; -- Check array contains a specific value SELECT * FROM documents WHERE data->'tags' @> '"manager"'; -- Extract multiple fields SELECT data->>'name' AS name, data->>'role' AS role, data #>> '{address,city}' AS city FROM documents; -- Multiple JSONB conditions SELECT data->>'name' AS name FROM documents WHERE data @> '{"role": "admin"}' AND data ? 'address' AND (data->>'age')::int > 25;
JSONB Type Casting
The ->> and #>> operators always return text. Cast to other types as needed:
(data->>'age')::int -- Cast to integer (data->>'price')::numeric -- Cast to decimal (data->>'active')::boolean -- Cast to boolean (data->>'created')::timestamp -- Cast to timestamp (data->>'score')::double precision -- Cast to float
Safety Rules
VibeSQL enforces safety rules to prevent accidental data loss:
| Rule | Applies To | Error Code |
|---|---|---|
| WHERE clause required | UPDATE, DELETE |
UNSAFE_QUERY (400) |
| Must start with valid SQL keyword | All queries | INVALID_SQL (400) |
| Query size ≤ 10 KB | All queries | QUERY_TOO_LARGE (413) |
WHERE 1=1. This confirms the operation is intentional.
Query Limits Micro
VibeSQL Micro enforces fixed limits. These are hardcoded and not configurable.
| Limit | Value | Error Code |
|---|---|---|
| Max query size | 10 KB (10,240 bytes) | QUERY_TOO_LARGE (413) |
| Max result rows | 1,000 rows | RESULT_TOO_LARGE (413) |
| Query timeout | 5 seconds | QUERY_TIMEOUT (408) |
| Max concurrent connections | 2 | — |
| Connection pool max | 5 | — |
| Idle connections | 2 | — |
| Connection max lifetime | 1 hour | — |
| HTTP read timeout | 10 seconds | — |
| HTTP write timeout | 10 seconds | — |
| HTTP header timeout | 5 seconds | — |
Tier-Based Limits Server
VibeSQL Server supports configurable, tier-based limits. Timeouts and row limits are set per tenant tier.
Default Timeouts by Tier
| Tier | Timeout | Config Key |
|---|---|---|
free | 2 seconds | VibeQueryTimeouts:FreeSeconds |
starter | 5 seconds | VibeQueryTimeouts:StarterSeconds |
pro | 10 seconds | VibeQueryTimeouts:ProSeconds |
enterprise | 30 seconds | VibeQueryTimeouts:EnterpriseSeconds |
| (default / unspecified) | 5 seconds | VibeQueryTimeouts:DefaultSeconds |
Configurable Limits
| Setting | Config Key | Default |
|---|---|---|
| Max result rows | VibeQueryLimits:MaxResultRows | 1,000 |
| Max query size | Hardcoded | 10 KB (10,240 bytes) |
The client tier is passed via the X-Vibe-Client-Tier HTTP header during HMAC authentication.
Error Format
All errors return a JSON response with "success": false and an error object:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message",
"detail": "Additional context"
}
}
Error Codes
INVALID_SQL HTTP 400
SQL query has syntax errors, references undefined tables/columns, uses unsupported functions, or doesn't start with a valid SQL keyword.
Triggers: SQL syntax errors, undefined table (42P01), undefined column (42703), undefined function (42883), data type mismatch (42804), invalid keyword.
Resolution: Check SQL syntax. Verify table and column names exist. Ensure query starts with one of: SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, TRUNCATE.
MISSING_REQUIRED_FIELD HTTP 400
The sql field is missing or empty in the request body.
// Wrong: using "query" instead of "sql" {"query": "SELECT 1"} // Correct {"sql": "SELECT 1"}
UNSAFE_QUERY HTTP 400
An UPDATE or DELETE statement is missing a WHERE clause.
// This fails: {"sql": "DELETE FROM users"} // This works (intentional delete-all): {"sql": "DELETE FROM users WHERE 1=1"}
QUERY_TIMEOUT HTTP 408
Query exceeded the maximum execution time. Default is 5 seconds (Micro) or tier-based (Server).
Resolution: Optimize the query (add indexes, reduce data scanned), add LIMIT, break into smaller operations.
QUERY_TOO_LARGE HTTP 413
SQL query exceeds the 10 KB (10,240 bytes) size limit.
Resolution: Reduce query size. Break large INSERT statements into multiple smaller requests.
RESULT_TOO_LARGE HTTP 413
Query result exceeds 1,000 rows.
Resolution: Add LIMIT 1000 (or smaller) to your query. Use OFFSET for pagination. Add WHERE clauses to filter results.
DOCUMENT_TOO_LARGE HTTP 413
A JSONB document or statement exceeds PostgreSQL's internal limits.
Triggers: PostgreSQL SQLSTATE 54000 (program_limit_exceeded), 54001 (statement_too_complex).
Resolution: Reduce JSONB document size. Simplify deeply nested JSON structures. Break large documents into smaller related records.
INTERNAL_ERROR HTTP 500
Unexpected server error not covered by other error codes.
Resolution: Check server logs for details. Retry the request. If persistent, restart the server.
SERVICE_UNAVAILABLE HTTP 503
The server is not ready to handle requests (still starting up).
Resolution: Wait for the server to finish starting. Check that the process is running.
DATABASE_UNAVAILABLE HTTP 503
The embedded (Micro) or external (Server) PostgreSQL instance is unreachable.
Triggers: PostgreSQL process crashed, connection failure (08xxx), insufficient resources (53xxx), too many connections (53300).
Resolution: Restart the server. Check disk space (PostgreSQL needs space for WAL files). Check if another process is using the PostgreSQL port.
HTTP Status Summary
| Status | Meaning |
|---|---|
200 | Query executed successfully |
400 | Invalid SQL, missing field, or unsafe query |
401 | HMAC authentication failed (Server only) |
408 | Query timed out |
413 | Query or result too large |
500 | Internal server error |
503 | Database or service unavailable |
PostgreSQL SQLSTATE Mapping
VibeSQL maps PostgreSQL SQLSTATE codes to VibeSQL error codes:
| SQLSTATE | VibeSQL Code | PostgreSQL Description |
|---|---|---|
42601 | INVALID_SQL | syntax_error |
42703 | INVALID_SQL | undefined_column |
42P01 | INVALID_SQL | undefined_table |
42P02 | INVALID_SQL | undefined_parameter |
42883 | INVALID_SQL | undefined_function |
42804 | INVALID_SQL | datatype_mismatch |
57014 | QUERY_TIMEOUT | query_canceled |
53000 | DATABASE_UNAVAILABLE | insufficient_resources |
53100 | DATABASE_UNAVAILABLE | disk_full |
53200 | DATABASE_UNAVAILABLE | out_of_memory |
53300 | DATABASE_UNAVAILABLE | too_many_connections |
53400 | DATABASE_UNAVAILABLE | configuration_limit_exceeded |
08000 | DATABASE_UNAVAILABLE | connection_exception |
08003 | DATABASE_UNAVAILABLE | connection_does_not_exist |
08006 | DATABASE_UNAVAILABLE | connection_failure |
08001 | DATABASE_UNAVAILABLE | sqlclient_unable_to_establish |
08004 | DATABASE_UNAVAILABLE | sqlserver_rejected_establishment |
54000 | DOCUMENT_TOO_LARGE | program_limit_exceeded |
54001 | DOCUMENT_TOO_LARGE | statement_too_complex |
Micro Configuration Micro
Environment Variables
| Variable | Default | Description |
|---|---|---|
VIBE_BIND_HOST | 127.0.0.1 | Host address to bind the HTTP server. Set to 0.0.0.0 for external access. |
POSTGRES_BIN | (embedded) | Path to external PostgreSQL binaries (overrides embedded). |
Ports
| Port | Protocol | Description |
|---|---|---|
5173 | HTTP | VibeSQL API endpoint |
5433 | PostgreSQL | Internal embedded PostgreSQL (not for direct access) |
Data Storage
Data is stored in ./vibe-data/ relative to the working directory where vibe serve is run. This directory contains PostgreSQL data files and is persistent across restarts.
vibe-data directory. To reset, delete the directory and restart.
Server Configuration Server
appsettings.json Keys
| Key | Default | Description |
|---|---|---|
VibeQueryTimeouts:DefaultSeconds | 5 | Default query timeout (when tier is not specified) |
VibeQueryTimeouts:FreeSeconds | 2 | Timeout for free tier |
VibeQueryTimeouts:StarterSeconds | 5 | Timeout for starter tier |
VibeQueryTimeouts:ProSeconds | 10 | Timeout for pro tier |
VibeQueryTimeouts:EnterpriseSeconds | 30 | Timeout for enterprise tier |
VibeQueryLimits:MaxResultRows | 1000 | Maximum rows per query result |
VibeSQL:DevBypassHmac | false | Bypass HMAC auth in development (never in production) |
HMAC Authentication Server
VibeSQL Server authenticates requests using HMAC-SHA256 signatures. Micro does not require authentication (localhost only).
Required Headers
| Header | Value | Description |
|---|---|---|
X-Vibe-Timestamp | Unix epoch (seconds) | Current time as a Unix timestamp. Must be within 5 minutes of server time. |
X-Vibe-Signature | Base64 string | HMAC-SHA256 signature of the string-to-sign. |
Optional Headers
| Header | Description |
|---|---|
X-Vibe-Service | Service identifier (for logging and audit trails) |
X-Vibe-Client-Tier | Client tier for timeout configuration (free, starter, pro, enterprise) |
X-Vibe-Tier-Claims | Comma-separated tier claims |
Signature Computation
1. Build the string to sign:
{timestamp}|{method}|{path}
Example: 1706745600|POST|/v1/query
2. Compute HMAC-SHA256 using your signing key (Base64-decoded):
const stringToSign = `${timestamp}|${method}|${path}`; const key = base64Decode(signingKey); const signature = base64Encode(hmacSHA256(key, stringToSign));
3. Include in request headers:
curl -X POST http://your-server/v1/query \
-H "Content-Type: application/json" \
-H "X-Vibe-Timestamp: 1706745600" \
-H "X-Vibe-Signature: aB3dEf...base64...==" \
-d '{"sql": "SELECT 1"}'
Timing Constraints
| Constraint | Value | Error |
|---|---|---|
| Max timestamp age | 5 minutes | TIMESTAMP_EXPIRED (401) |
| Max clock skew (future) | 1 minute | TIMESTAMP_FUTURE (401) |
Auth Error Codes
| Code | HTTP | Description |
|---|---|---|
HMAC_REQUIRED | 401 | Missing X-Vibe-Timestamp or X-Vibe-Signature headers |
INVALID_TIMESTAMP | 401 | Timestamp is not a valid number |
TIMESTAMP_EXPIRED | 401 | Request timestamp is more than 5 minutes old |
TIMESTAMP_FUTURE | 401 | Request timestamp is more than 1 minute in the future |
SIGNATURE_MISMATCH | 401 | Computed signature does not match the provided signature |
Public Endpoints Server
These endpoints do not require HMAC authentication:
| Path | Description |
|---|---|
/health | Health check |
/v1/health | Health check (versioned) |
/swagger | Swagger UI |
/swagger/index.html | Swagger UI |
/swagger/v1/swagger.json | OpenAPI spec |
Schema Versioning Server
VibeSQL Server supports JSON Schema versioning and evolution for collections. Schemas are scoped by client (tenant) and collection.
Schema Properties
| Property | Type | Default | Description |
|---|---|---|---|
CollectionSchemaId | int | — | Primary key |
ClientId | int | — | Tenant identifier (IDP client) |
Collection | string | "" | Collection name |
JsonSchema | string? | null | JSON Schema definition |
Version | int | 1 | Schema version (incremented on updates) |
IsActive | bool | true | Whether this is the active schema version |
IsSystem | bool | false | System collections are exempt from tier limits |
IsLocked | bool | false | Locked schemas prevent structural modifications |
CreatedAt | DateTimeOffset | — | Creation timestamp |
UpdatedAt | DateTimeOffset? | null | Last update timestamp |
IsLocked = true, structural changes (add, rename, delete fields) are blocked. Document CRUD (INSERT, UPDATE, DELETE) is still allowed.
Migration Transforms Server
Schema migrations define transforms applied to documents when migrating between schema versions. Transforms are defined in the JSON Schema under the x-vibe-migrations extension.
Transform Definition
{
"x-vibe-migrations": {
"1_to_2": [
{
"field": "price",
"transform": "multiply",
"args": 100,
"reason": "Convert dollars to cents"
}
]
}
}
Available Transforms
| Transform | Args | Description | Example |
|---|---|---|---|
multiply |
number | Multiplies a numeric field | {"field": "price", "transform": "multiply", "args": 100} |
divide |
number | Divides a numeric field (checks for zero divisor) | {"field": "price", "transform": "divide", "args": 100} |
map |
object (key→value) | Maps string values | {"field": "status", "transform": "map", "args": {"active": "enabled", "inactive": "disabled"}} |
default |
any | Sets a default value if null or missing | {"field": "role", "transform": "default", "args": "viewer"} |
cast |
string (target type) | Casts to a different type | {"field": "age", "transform": "cast", "args": "integer"} |
rename |
string (new name) | Renames the field | {"field": "name", "transform": "rename", "args": "full_name"} |
Cast Target Types
| Type String | Result |
|---|---|
"int" or "integer" | Integer |
"double" or "number" | Floating-point |
"string" | String |
"bool" or "boolean" | Boolean |
Compatibility Checks Server
Before applying schema changes, VibeSQL checks compatibility between the current and proposed schema:
| Level | Description |
|---|---|
FullyCompatible | No breaking changes. Safe to apply immediately. |
ForwardCompatible | Changes exist but migration transforms are available. Documents will be migrated. |
Breaking | Fields removed or types changed without migration transforms. Requires manual review. |
Schema Change Types
| Change | Description |
|---|---|
Added | New field added to the schema |
Removed | Existing field removed from the schema |
TypeChanged | Field type changed (e.g., string → integer) |
SDK Installation SDK
npm install @vibe/client
Environment Variables
| Variable | Required | Description |
|---|---|---|
VIBE_API_URL or NEXT_PUBLIC_VIBE_API_URL | For direct mode | VibeSQL API URL (e.g., http://127.0.0.1:5173) |
IDP_URL or NEXT_PUBLIC_IDP_URL | For proxy mode | Identity provider URL for proxied requests |
VIBE_CLIENT_ID | For proxy mode | Client ID for multi-tenant routing |
VIBE_HMAC_KEY | For Server auth | HMAC signing key (Base64) |
VIBE_COLLECTION | No | Default collection name (default: vibe_app) |
Client Setup SDK
createVibeClient()
import { createVibeClient } from '@vibe/client'; // Direct mode (Micro or direct Server access) const client = createVibeClient({ apiUrl: 'http://127.0.0.1:5173', }); // Proxy mode (through IDP) const client = createVibeClient({ idpUrl: 'https://your-idp.example.com', clientId: 'your-client-id', signingKey: 'your-hmac-key-base64', }); // With all options const client = createVibeClient({ apiUrl: 'http://127.0.0.1:5173', idpUrl: 'https://your-idp.example.com', clientId: 'your-client-id', signingKey: 'your-hmac-key-base64', defaultCollection: 'my_app', timeout: 30000, // 30 seconds (default) debug: false, // Enable debug logging getAccessToken: async () => 'bearer-token', });
Configuration Interface
| Property | Type | Default | Description |
|---|---|---|---|
apiUrl | string? | env var | Direct API URL |
idpUrl | string? | env var | IDP proxy URL (enables proxy mode) |
clientId | string? | env var | Client ID for multi-tenant |
signingKey | string? | env var | HMAC signing key (Base64) |
defaultCollection | string? | "vibe_app" | Default collection name |
getAccessToken | () => Promise<string | null> | — | Bearer token provider |
timeout | number? | 30000 | Request timeout in milliseconds |
debug | boolean? | false | Enable debug logging |
getVibeClient() — Singleton
import { getVibeClient } from '@vibe/client'; // Auto-initializes using environment variables const client = getVibeClient();
Connection Modes
Direct mode: SDK connects directly to the VibeSQL API. Used with Micro or direct Server access.
Proxy mode: SDK routes requests through the IDP proxy at {idpUrl}/api/vibe/proxy. Enabled when idpUrl is set. The proxy handles HMAC signing and multi-tenant routing.
Collection CRUD SDK
const users = client.collection<User>('users');
list(options?)
const result = await users.list({ limit: 20, // default: 20 offset: 0, // default: 0 orderBy: 'created_at', orderDir: 'desc', // 'asc' | 'desc' filter: { role: 'admin' }, }); // result.data → User[] // result.pagination → { total, limit, offset, hasMore }
get(id)
const user = await users.get(1); // by number ID const user = await users.get('abc-uuid'); // by string ID // Returns User | null (null if not found)
create(data)
const newUser = await users.create({ name: 'Alice', email: 'alice@example.com', role: 'admin', }); // Returns the created User with server-assigned id
update(id, data)
const updated = await users.update(1, { name: 'Alice Smith', }); // HTTP PATCH — partial update, returns updated User
delete(id)
await users.delete(1); // Returns void
Filter Format
Simple filters use {field: value} syntax (operator defaults to eq):
// Simple equality filter { role: 'admin' } // With explicit operator { age: { operator: 'gt', value: 21 } }
Filters are converted to the Vibe query format: [{ field, operator, value }]
Pagination Interface
| Field | Type | Description |
|---|---|---|
total | number | Total matching documents |
limit | number | Page size |
offset | number | Current offset |
hasMore | boolean | Whether more pages exist |
Admin API SDK
const admin = client.admin;
Roles
// List roles const roles = await admin.roles.list({ limit: 50, offset: 0 }); // Get role by ID const role = await admin.roles.get(1); // Create role const newRole = await admin.roles.create({ name: 'editor', description: 'Can edit content', source: 'custom', // optional }); // Update role const updated = await admin.roles.update(1, { description: 'Updated description', }); // Delete role await admin.roles.delete(1);
Users
// List users const users = await admin.users.list({ limit: 50 }); // Get user by ID const user = await admin.users.get('user-uuid'); // Get user's roles const roles = await admin.users.getRoles('user-uuid');
Tenant
// Get tenant configuration const config = await admin.tenant.getConfig(); // Returns: { client_id, site_name, branding?: { logo_url?, primary_color? } }
Admin Type Definitions
| Type | Fields |
|---|---|
Role | id: number, name: string, description?: string, source: string, created_at: string, updated_at: string |
CreateRole | name: string, description?: string, source?: string |
UpdateRole | name?: string, description?: string |
User | id: string, email: string, name?: string, avatar_url?: string, created_at: string, last_login?: string |
TenantConfig | client_id: string, site_name: string, branding?: { logo_url?: string, primary_color?: string } |
Auth Utilities SDK
import { hasRole, hasAnyRole, hasAllRoles, isAdmin, isPlatformAdmin, isClientAdmin, getHighestRoleLevel, meetsRoleLevel, VibeRoles, ADMIN_ROLES, ROLE_HIERARCHY } from '@vibe/client';
Role Constants
| Constant | Value | Hierarchy Level |
|---|---|---|
VibeRoles.PLATFORM_ADMIN | "platform_admin" | 4 (highest) |
VibeRoles.VIBE_APP_ADMIN | "vibe_app_admin" | 3 |
VibeRoles.VIBE_CLIENT_ADMIN | "vibe_client_admin" | 2 |
VibeRoles.IDP_CLIENT_ADMIN | "idp_client_admin" | 2 |
VibeRoles.VIBE_APP_USER | "vibe_app_user" | 1 (lowest) |
Role Groups
| Group | Roles |
|---|---|
ADMIN_ROLES | platform_admin, vibe_app_admin, vibe_client_admin, idp_client_admin |
PLATFORM_ADMIN_ROLES | platform_admin, vibe_app_admin |
CLIENT_ADMIN_ROLES | vibe_client_admin, idp_client_admin |
Utility Functions
| Function | Signature | Description |
|---|---|---|
hasRole | (roles, role) => boolean | Check if user has a specific role |
hasAnyRole | (roles, roles[]) => boolean | Check if user has any of the given roles |
hasAllRoles | (roles, roles[]) => boolean | Check if user has all of the given roles |
isAdmin | (roles) => boolean | Has any admin role |
isPlatformAdmin | (roles) => boolean | Has platform_admin or vibe_app_admin |
isClientAdmin | (roles) => boolean | Has client admin role (not platform admin) |
getHighestRoleLevel | (roles) => number | Returns highest hierarchy level (0 if no roles) |
meetsRoleLevel | (roles, level) => boolean | Check if user meets minimum role level |
// Usage examples const userRoles = ['vibe_client_admin', 'vibe_app_user']; isAdmin(userRoles); // true isPlatformAdmin(userRoles); // false isClientAdmin(userRoles); // true hasRole(userRoles, 'platform_admin'); // false getHighestRoleLevel(userRoles); // 2 meetsRoleLevel(userRoles, 3); // false
Error Handling SDK
import { VibeError } from '@vibe/client'; try { const user = await users.get(999); } catch (error) { if (error instanceof VibeError) { console.log(error.code); // 'NOT_FOUND' console.log(error.message); // 'Document not found' console.log(error.status); // 404 console.log(error.isRetryable()); // false } }
SDK Error Codes
| Code | HTTP Status | Retryable | Description |
|---|---|---|---|
NETWORK_ERROR | — | Yes | Network connectivity issue |
UNAUTHORIZED | 401 | No | Authentication failed |
FORBIDDEN | 403 | No | Insufficient permissions |
NOT_FOUND | 404 | No | Resource not found |
VALIDATION_ERROR | 422 | No | Invalid input data |
CONFLICT | 409 | No | Resource conflict (e.g., duplicate key) |
RATE_LIMITED | 429 | Yes | Too many requests |
SERVER_ERROR | 500+ | Yes | Server-side error |
UNKNOWN_ERROR | other | No | Unexpected error |
Type Generation SDK
The @vibe/next-plugin package generates TypeScript types from your collection schemas automatically.
npm install @vibe/next-plugin
Configuration
| Option | Env Var | Description |
|---|---|---|
idpUrl | IDP_URL, NEXT_PUBLIC_IDP_URL | Identity provider URL |
clientId | VIBE_CLIENT_ID, NEXT_PUBLIC_VIBE_CLIENT_ID | Client ID |
signingKey | VIBE_HMAC_KEY, IDP_SIGNING_KEY | HMAC signing key |
outputDir | — | Output directory (default: node_modules/.vibe/types) |
collections | — | Specific collections to generate (default: all) |
Generated Output
The plugin generates:
- A
.d.tsfile for each collection with the document type - An
index.d.tswith re-exports and aVibeCollectionsinterface - Type augmentation for
@vibe/clientsoclient.collection('name')returns the correct type
// After type generation, collections are fully typed: const users = client.collection('users'); // TypeScript knows users.list() returns ListResult<UserDocument> // TypeScript knows users.get() returns UserDocument | null
AI Coding Skills
VibeSQL ships AI coding skills — lightweight plugins that let your AI coding assistant talk directly to your VibeSQL database. No servers to run, no packages to install. Just drop a skill file into your editor and go.
/vibe-sql show me all tables), your AI assistant translates it to PostgreSQL, executes it against VibeSQL's HTTP API, and presents the results — all in one step.
What Skills Can Do
- Explore — list tables, describe schemas, inspect columns and types
- Query — SELECT with filters, joins, aggregations, JSONB operators
- Create — CREATE TABLE, ALTER TABLE, add columns, set defaults
- CRUD — INSERT...RETURNING, UPDATE...WHERE, DELETE...WHERE
- JSONB — full PostgreSQL JSONB support with all 9 operators
Every skill embeds the full VibeSQL API reference, so your AI assistant knows the endpoint, response format, error codes, safety rules, and PostgreSQL syntax without any extra configuration.
Claude Code Available Now
The /vibe-sql skill for Claude Code lets you talk to your database in natural language directly from your terminal.
# Examples
/vibe-sql show me all tables
/vibe-sql create a users table with name, email, and age
/vibe-sql add a row: name=Widget, price=9.99
/vibe-sql what's the average price in stripe_sales
/vibe-sql add a phone column to the users table
The skill translates your request to PostgreSQL SQL, executes it via POST /v1/query, and presents formatted results. It enforces safety rules (UPDATE/DELETE require WHERE) and handles all error codes.
Supported Platforms
The /vibe-sql skill ships for three AI coding tools today:
| Tool | Skill Location | Status |
|---|---|---|
| Claude Code | ~/.claude/skills/vibe-sql/ | Available |
| OpenCode | ~/.opencode/skills/vibe-sql/ | Available |
| Codex CLI | ~/.agents/skills/vibe-sql/ | Available |
Same skill body, platform-specific frontmatter. All three are QA-tested and passing 10/10 scenarios.
Installation
Skills are single files. Copy the skill into your project and your AI editor discovers it automatically.
Claude Code
git clone https://github.com/PayEz-Net/vibesql-skills.git /tmp/vibesql-skills cp -r /tmp/vibesql-skills/claude/vibe-sql ~/.claude/skills/vibe-sql
Open Claude Code and type /vibe-sql show me all tables.
OpenCode
git clone https://github.com/PayEz-Net/vibesql-skills.git /tmp/vibesql-skills cp -r /tmp/vibesql-skills/opencode/vibe-sql ~/.opencode/skills/vibe-sql
Codex CLI
git clone https://github.com/PayEz-Net/vibesql-skills.git /tmp/vibesql-skills cp -r /tmp/vibesql-skills/codex/vibe-sql ~/.agents/skills/vibe-sql
http://localhost:5173 (VibeSQL Micro). If your instance runs elsewhere, set the VIBESQL_URL environment variable.
VibeSQL is open source under the Apache 2.0 license.
Server Edition · Micro Edition · AI Skills · Contact · Built by IdealVibe