Documentation
Home GitHub

VibeSQL Documentation

Complete technical reference for VibeSQL Micro, Server, and SDK.

AI Agents: Fetch /docs.md for a clean, machine-readable version of this reference optimized for prompt context.

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"
}
FieldTypeRequiredConstraints
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
}
FieldTypeDescription
successbooleanAlways true for successful queries
rowsarray | nullArray of row objects (column name → value). null for non-SELECT statements without RETURNING.
rowCountintegerNumber of rows returned or affected
executionTimefloatExecution 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"
  }
}
FieldTypeDescription
successbooleanAlways false
error.codestringMachine-readable error code (see Error Codes)
error.messagestringHuman-readable error message
error.detailstringAdditional context (optional)

Value encoding notes:

Supported SQL Statements

Queries must start with one of these 8 keywords (case-insensitive):

KeywordDescriptionWHERE Required?
SELECTRead rows from tablesNo
INSERTCreate new rowsNo
UPDATEModify existing rowsYes
DELETERemove rowsYes
CREATECreate tables, indexes, etc.No
DROPRemove tablesNo
ALTERModify table structureNo
TRUNCATERemove all rows from a tableNo
Safety: UPDATE and DELETE require a WHERE clause. To intentionally affect all rows, use 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:

TypeDescriptionSize / Range
TEXTVariable-length stringUnlimited (up to 1 GB)
VARCHAR(n)Variable-length string with limitMax n characters
CHAR(n)Fixed-length stringExactly n characters, space-padded
INTEGER / INTSigned integer-2,147,483,648 to 2,147,483,647 (4 bytes)
BIGINTLarge signed integer-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 (8 bytes)
SMALLINTSmall signed integer-32,768 to 32,767 (2 bytes)
SERIALAuto-incrementing integer1 to 2,147,483,647
BIGSERIALAuto-incrementing large integer1 to 9,223,372,036,854,775,807
NUMERIC(p,s)Exact decimal numberp total digits, s after decimal. Max precision: 131,072 digits before decimal, 16,383 after.
REALFloating-point (single)6 decimal digits precision (4 bytes)
DOUBLE PRECISIONFloating-point (double)15 decimal digits precision (8 bytes)
BOOLEANTrue or falseTRUE, FALSE, NULL
DATECalendar date4713 BC to 5874897 AD (4 bytes)
TIMETime of day (no timezone)00:00:00 to 24:00:00 (8 bytes)
TIMESTAMPDate and time (no timezone)4713 BC to 294276 AD (8 bytes)
TIMESTAMPTZDate and time with timezoneSame range, timezone-aware (8 bytes)
UUIDUniversally unique identifier128-bit RFC 4122 format (16 bytes)
JSONBBinary JSON dataUp to 255 MB per value
JSONText JSON dataUp to 255 MB per value (prefer JSONB)
TEXT[]Array of textMulti-dimensional arrays supported
INTEGER[]Array of integersMulti-dimensional arrays supported
UUID[]Array of UUIDsMulti-dimensional arrays supported
BYTEABinary dataUp 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

WHERE clause is required. UPDATE without WHERE returns 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

WHERE clause is required. DELETE without WHERE returns 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

OperatorReturnsUse When
->JSONBYou need JSON for further operations or chaining
->>textYou need the value as a plain string
#>JSONBYou need a nested value as JSON via path
#>>textYou need a nested value as text via path
@>booleanFiltering: left contains right
<@booleanFiltering: left is contained by right
?booleanCheck if a single key exists
?|booleanCheck if any key in array exists
?&booleanCheck 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:

RuleApplies ToError 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)
Intentional bulk operations: To UPDATE or DELETE all rows, explicitly use WHERE 1=1. This confirms the operation is intentional.

Query Limits Micro

VibeSQL Micro enforces fixed limits. These are hardcoded and not configurable.

LimitValueError Code
Max query size10 KB (10,240 bytes)QUERY_TOO_LARGE (413)
Max result rows1,000 rowsRESULT_TOO_LARGE (413)
Query timeout5 secondsQUERY_TIMEOUT (408)
Max concurrent connections2
Connection pool max5
Idle connections2
Connection max lifetime1 hour
HTTP read timeout10 seconds
HTTP write timeout10 seconds
HTTP header timeout5 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

TierTimeoutConfig Key
free2 secondsVibeQueryTimeouts:FreeSeconds
starter5 secondsVibeQueryTimeouts:StarterSeconds
pro10 secondsVibeQueryTimeouts:ProSeconds
enterprise30 secondsVibeQueryTimeouts:EnterpriseSeconds
(default / unspecified)5 secondsVibeQueryTimeouts:DefaultSeconds

Configurable Limits

SettingConfig KeyDefault
Max result rowsVibeQueryLimits:MaxResultRows1,000
Max query sizeHardcoded10 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

StatusMeaning
200Query executed successfully
400Invalid SQL, missing field, or unsafe query
401HMAC authentication failed (Server only)
408Query timed out
413Query or result too large
500Internal server error
503Database or service unavailable

PostgreSQL SQLSTATE Mapping

VibeSQL maps PostgreSQL SQLSTATE codes to VibeSQL error codes:

SQLSTATEVibeSQL CodePostgreSQL Description
42601INVALID_SQLsyntax_error
42703INVALID_SQLundefined_column
42P01INVALID_SQLundefined_table
42P02INVALID_SQLundefined_parameter
42883INVALID_SQLundefined_function
42804INVALID_SQLdatatype_mismatch
57014QUERY_TIMEOUTquery_canceled
53000DATABASE_UNAVAILABLEinsufficient_resources
53100DATABASE_UNAVAILABLEdisk_full
53200DATABASE_UNAVAILABLEout_of_memory
53300DATABASE_UNAVAILABLEtoo_many_connections
53400DATABASE_UNAVAILABLEconfiguration_limit_exceeded
08000DATABASE_UNAVAILABLEconnection_exception
08003DATABASE_UNAVAILABLEconnection_does_not_exist
08006DATABASE_UNAVAILABLEconnection_failure
08001DATABASE_UNAVAILABLEsqlclient_unable_to_establish
08004DATABASE_UNAVAILABLEsqlserver_rejected_establishment
54000DOCUMENT_TOO_LARGEprogram_limit_exceeded
54001DOCUMENT_TOO_LARGEstatement_too_complex

Micro Configuration Micro

Environment Variables

VariableDefaultDescription
VIBE_BIND_HOST127.0.0.1Host 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

PortProtocolDescription
5173HTTPVibeSQL API endpoint
5433PostgreSQLInternal 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.

Backup: To back up your data, stop the server and copy the entire vibe-data directory. To reset, delete the directory and restart.

Server Configuration Server

appsettings.json Keys

KeyDefaultDescription
VibeQueryTimeouts:DefaultSeconds5Default query timeout (when tier is not specified)
VibeQueryTimeouts:FreeSeconds2Timeout for free tier
VibeQueryTimeouts:StarterSeconds5Timeout for starter tier
VibeQueryTimeouts:ProSeconds10Timeout for pro tier
VibeQueryTimeouts:EnterpriseSeconds30Timeout for enterprise tier
VibeQueryLimits:MaxResultRows1000Maximum rows per query result
VibeSQL:DevBypassHmacfalseBypass 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

HeaderValueDescription
X-Vibe-TimestampUnix epoch (seconds)Current time as a Unix timestamp. Must be within 5 minutes of server time.
X-Vibe-SignatureBase64 stringHMAC-SHA256 signature of the string-to-sign.

Optional Headers

HeaderDescription
X-Vibe-ServiceService identifier (for logging and audit trails)
X-Vibe-Client-TierClient tier for timeout configuration (free, starter, pro, enterprise)
X-Vibe-Tier-ClaimsComma-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

ConstraintValueError
Max timestamp age5 minutesTIMESTAMP_EXPIRED (401)
Max clock skew (future)1 minuteTIMESTAMP_FUTURE (401)

Auth Error Codes

CodeHTTPDescription
HMAC_REQUIRED401Missing X-Vibe-Timestamp or X-Vibe-Signature headers
INVALID_TIMESTAMP401Timestamp is not a valid number
TIMESTAMP_EXPIRED401Request timestamp is more than 5 minutes old
TIMESTAMP_FUTURE401Request timestamp is more than 1 minute in the future
SIGNATURE_MISMATCH401Computed signature does not match the provided signature

Public Endpoints Server

These endpoints do not require HMAC authentication:

PathDescription
/healthHealth check
/v1/healthHealth check (versioned)
/swaggerSwagger UI
/swagger/index.htmlSwagger UI
/swagger/v1/swagger.jsonOpenAPI spec

Schema Versioning Server

VibeSQL Server supports JSON Schema versioning and evolution for collections. Schemas are scoped by client (tenant) and collection.

Schema Properties

PropertyTypeDefaultDescription
CollectionSchemaIdintPrimary key
ClientIdintTenant identifier (IDP client)
Collectionstring""Collection name
JsonSchemastring?nullJSON Schema definition
Versionint1Schema version (incremented on updates)
IsActivebooltrueWhether this is the active schema version
IsSystemboolfalseSystem collections are exempt from tier limits
IsLockedboolfalseLocked schemas prevent structural modifications
CreatedAtDateTimeOffsetCreation timestamp
UpdatedAtDateTimeOffset?nullLast update timestamp
Locked schemas: When 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

TransformArgsDescriptionExample
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 StringResult
"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:

LevelDescription
FullyCompatibleNo breaking changes. Safe to apply immediately.
ForwardCompatibleChanges exist but migration transforms are available. Documents will be migrated.
BreakingFields removed or types changed without migration transforms. Requires manual review.

Schema Change Types

ChangeDescription
AddedNew field added to the schema
RemovedExisting field removed from the schema
TypeChangedField type changed (e.g., string → integer)

SDK Installation SDK

npm install @vibe/client

Environment Variables

VariableRequiredDescription
VIBE_API_URL or NEXT_PUBLIC_VIBE_API_URLFor direct modeVibeSQL API URL (e.g., http://127.0.0.1:5173)
IDP_URL or NEXT_PUBLIC_IDP_URLFor proxy modeIdentity provider URL for proxied requests
VIBE_CLIENT_IDFor proxy modeClient ID for multi-tenant routing
VIBE_HMAC_KEYFor Server authHMAC signing key (Base64)
VIBE_COLLECTIONNoDefault 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

PropertyTypeDefaultDescription
apiUrlstring?env varDirect API URL
idpUrlstring?env varIDP proxy URL (enables proxy mode)
clientIdstring?env varClient ID for multi-tenant
signingKeystring?env varHMAC signing key (Base64)
defaultCollectionstring?"vibe_app"Default collection name
getAccessToken() => Promise<string | null>Bearer token provider
timeoutnumber?30000Request timeout in milliseconds
debugboolean?falseEnable 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

FieldTypeDescription
totalnumberTotal matching documents
limitnumberPage size
offsetnumberCurrent offset
hasMorebooleanWhether 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

TypeFields
Roleid: number, name: string, description?: string, source: string, created_at: string, updated_at: string
CreateRolename: string, description?: string, source?: string
UpdateRolename?: string, description?: string
Userid: string, email: string, name?: string, avatar_url?: string, created_at: string, last_login?: string
TenantConfigclient_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

ConstantValueHierarchy 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

GroupRoles
ADMIN_ROLESplatform_admin, vibe_app_admin, vibe_client_admin, idp_client_admin
PLATFORM_ADMIN_ROLESplatform_admin, vibe_app_admin
CLIENT_ADMIN_ROLESvibe_client_admin, idp_client_admin

Utility Functions

FunctionSignatureDescription
hasRole(roles, role) => booleanCheck if user has a specific role
hasAnyRole(roles, roles[]) => booleanCheck if user has any of the given roles
hasAllRoles(roles, roles[]) => booleanCheck if user has all of the given roles
isAdmin(roles) => booleanHas any admin role
isPlatformAdmin(roles) => booleanHas platform_admin or vibe_app_admin
isClientAdmin(roles) => booleanHas client admin role (not platform admin)
getHighestRoleLevel(roles) => numberReturns highest hierarchy level (0 if no roles)
meetsRoleLevel(roles, level) => booleanCheck 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

CodeHTTP StatusRetryableDescription
NETWORK_ERRORYesNetwork connectivity issue
UNAUTHORIZED401NoAuthentication failed
FORBIDDEN403NoInsufficient permissions
NOT_FOUND404NoResource not found
VALIDATION_ERROR422NoInvalid input data
CONFLICT409NoResource conflict (e.g., duplicate key)
RATE_LIMITED429YesToo many requests
SERVER_ERROR500+YesServer-side error
UNKNOWN_ERRORotherNoUnexpected error

Type Generation SDK

The @vibe/next-plugin package generates TypeScript types from your collection schemas automatically.

npm install @vibe/next-plugin

Configuration

OptionEnv VarDescription
idpUrlIDP_URL, NEXT_PUBLIC_IDP_URLIdentity provider URL
clientIdVIBE_CLIENT_ID, NEXT_PUBLIC_VIBE_CLIENT_IDClient ID
signingKeyVIBE_HMAC_KEY, IDP_SIGNING_KEYHMAC signing key
outputDirOutput directory (default: node_modules/.vibe/types)
collectionsSpecific collections to generate (default: all)

Generated Output

The plugin generates:

// 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.

How it works: You type a natural language command (e.g. /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

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:

ToolSkill LocationStatus
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
Default URL: Skills default to 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