SDK Connection Guide

This guide shows how to connect to your self-hosted WoWSQL instance from JavaScript, Python, and other languages using the official SDK or raw HTTP.

Getting Your Credentials

You need two values to connect:

  1. API URL — The Kong gateway address (default: http://localhost:8080)
  2. Anon Key — The public API key (typically wowsql_anon_...)

Both are available in the dashboard under Settings → API. REST / Auth / Storage send the key as the apikey HTTP header. Realtime WebSockets use the same key as ?apikey= on /realtime/v1/websocket (browsers cannot set WebSocket headers). Missing key closes 4001; invalid key closes 4003.

Use the anon key in browsers and apps. Keep the service role key (wowsql_service_...) on the server only — it bypasses Row Level Security.

JavaScript / TypeScript

Install

npm install @wowsql/sdk

Initialize

import WowSQLClient from '@wowsql/sdk'
import { ProjectAuthClient, WowSQLStorage } from '@wowsql/sdk'

const client = new WowSQLClient({
  projectUrl: 'http://localhost:8080',  // Kong gateway
  apiKey: 'YOUR_ANON_KEY',              // wowsql_anon_...
  secure: false                         // http:// local
})

Query data

const { data } = await client.table('todos').select('*').get()

const created = await client.table('todos').insert({
  title: 'Buy groceries',
  completed: false
})

await client.table('todos').update(created.id, { completed: true })

await client.table('todos').delete(created.id)

Authentication

const auth = new ProjectAuthClient({
  projectUrl: 'http://localhost:8080',
  apiKey: 'YOUR_ANON_KEY',
  secure: false
})

const { session, user } = await auth.signUp({
  email: 'user@example.com',
  password: 'secure-password'
})

const signedIn = await auth.signIn({
  email: 'user@example.com',
  password: 'secure-password'
})

// CAPTCHA — only when Attack Protection → Turnstile is enabled with your own widget keys
await auth.signIn({
  email: 'user@example.com',
  password: 'secure-password',
  captchaToken: turnstileToken
})

const me = await auth.getUser(signedIn.session.accessToken)
await auth.logout(signedIn.session.accessToken)

Realtime

Connects to ws://localhost:8080/realtime/v1/websocket?apikey=YOUR_ANON_KEY. Enable the table first with POST /realtime/v1/enable. Channel send / presence do not need a Postgres trigger. Payloads are capped at 64 KiB.

const channel = client.realtime.channel('todos-changes')
channel
  .on('postgres_changes', { event: '*', schema: 'public', table: 'todos' }, (payload) => {
    console.log('Change:', payload.new || payload.old)
  })
  .on('broadcast', { event: 'typing' }, ({ payload }) => {
    console.log('typing', payload)
  })
  .subscribe()

await channel.send({ type: 'broadcast', event: 'typing', payload: { user: 'Alice' } })

Storage

const storage = new WowSQLStorage({
  projectUrl: 'http://localhost:8080',
  apiKey: 'YOUR_ANON_KEY',
  secure: false
})

await storage.upload('avatars', file, 'user1.png')
const url = storage.getPublicUrl('avatars', 'user1.png')

Python

Install

pip install wowsql

Initialize

from wowsql import WowSQLClient, ProjectAuthClient, WowSQLStorage

client = WowSQLClient(
    project_url="http://localhost:8080",
    api_key="YOUR_ANON_KEY",
    secure=False,
)

Query data

result = client.table("todos").select("*").get()
print(result["data"])

created = client.table("todos").insert({
    "title": "Buy groceries",
    "completed": False,
})

client.table("todos").update(created["id"], {"completed": True})
client.table("todos").delete(created["id"])

Authentication

auth = ProjectAuthClient(
    project_url="http://localhost:8080",
    api_key="YOUR_ANON_KEY",
    secure=False,
)

signed_up = auth.sign_up(email="user@example.com", password="secure-password")
signed_in = auth.sign_in(email="user@example.com", password="secure-password")
user = auth.get_user(signed_in.session.access_token)
auth.logout(signed_in.session.access_token)

Realtime

def on_change(change):
    print(change["event"], change.get("new") or change.get("old"))

sub = client.realtime.subscribe("todos", on_change, schema="public", event="*")

channel = client.realtime.channel("chat")
channel.on("broadcast", {"event": "typing"}, lambda msg: print(msg["payload"]))
channel.subscribe()
channel.send(event="typing", payload={"user": "Alice"})

Raw HTTP (cURL)

You can use any HTTP client. Pass the anon key in both headers:

Read data

curl 'http://localhost:8080/rest/v1/todos?select=*' \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY"

Insert data

curl 'http://localhost:8080/rest/v1/todos' \
  -X POST \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY" \
  -H "Content-Type: application/json" \
  -H "Prefer: return=representation" \
  -d '{"title": "Buy groceries", "completed": false}'

Filter and paginate

# Filter: completed = true
curl 'http://localhost:8080/rest/v1/todos?completed=eq.true' \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY"

# Paginate: 10 items starting from offset 20
curl 'http://localhost:8080/rest/v1/todos?select=*&limit=10&offset=20' \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY"

# Order by created_at descending
curl 'http://localhost:8080/rest/v1/todos?order=created_at.desc' \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY"

Enable a table for realtime

curl 'http://localhost:8080/realtime/v1/enable' \
  -X POST \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"schema_name":"public","table_name":"todos"}'

Then connect a WebSocket to ws://localhost:8080/realtime/v1/websocket?apikey=YOUR_ANON_KEY. Send {"type":"subscribe","schema":"public","table":"todos","event":"*"} for postgres changes, or {"type":"join","channel":"chat"} plus {"type":"broadcast","channel":"chat","event":"typing","payload":{}} for ephemeral events.

Authenticate a user

# Sign up
curl 'http://localhost:8080/auth/v1/signup' \
  -X POST \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "password": "securepass"}'

# Sign in (returns access_token)
curl 'http://localhost:8080/auth/v1/token?grant_type=password' \
  -X POST \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "password": "securepass"}'

Use authenticated token

After signing in, use the returned access_token in subsequent requests:

curl 'http://localhost:8080/rest/v1/todos' \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer USER_ACCESS_TOKEN"
Note: The apikey header always uses the anon key. The Authorization header uses the user's access token after login. This tells PostgREST to execute queries as the authenticated role.

Using the Service Role Key

The service role key bypasses Row Level Security. Use it only in server-side code (backend APIs, scripts, admin tools).

// Server-side only
import WowSQLClient from '@wowsql/sdk'

const admin = new WowSQLClient({
  projectUrl: 'http://localhost:8080',
  apiKey: 'YOUR_SERVICE_ROLE_KEY',
  secure: false
})

const { data } = await admin.table('todos').select('*').get()
Never expose the service role key in client-side code. It grants unrestricted access to all data in your database.

Row Level Security Basics

After creating a table, enable RLS and write policies to control access:

-- Enable RLS on the table
ALTER TABLE public.todos ENABLE ROW LEVEL SECURITY;

-- Allow anyone to read all todos
CREATE POLICY "Public read access"
  ON public.todos FOR SELECT
  USING (true);

-- Only the owner can insert
CREATE POLICY "Users insert own todos"
  ON public.todos FOR INSERT
  WITH CHECK (auth.uid() = user_id);

-- Only the owner can update
CREATE POLICY "Users update own todos"
  ON public.todos FOR UPDATE
  USING (auth.uid() = user_id);

-- Only the owner can delete
CREATE POLICY "Users delete own todos"
  ON public.todos FOR DELETE
  USING (auth.uid() = user_id);
Tip: Without RLS policies, a table with RLS enabled returns empty results for the anon and authenticated roles. Always create at least a SELECT policy.