Sign in with WoWSQL (OAuth Connect)
Sign in with WoWSQL (OAuth Connect)
Sign in with WoWSQL lets a third-party application, such as Zapier, n8n, Make or your own service, work with one WoWSQL project on behalf of a WoWSQL user. WoWSQL acts as the OAuth 2.0 authorization server: the user signs in to WoWSQL, picks a project and approves the app, and the app receives an access token that can read and, if approved, write data in that one project.
This is not end-user sign-in for your own application. To let the users of your app sign in with Google, GitHub and other providers, see OAuth for Projects.
In short:
- Standard OAuth 2.0 authorization code grant with a confidential client (client ID and client secret). Refresh tokens are supported. PKCE is not supported.
- Every token is bound to the single project the user selected on the consent screen. It cannot list or reach the user's other projects, and it cannot call the dashboard API.
- Two scopes:
readandwrite. An app can never do more than the WoWSQL user who approved it is allowed to do on that project, and this is re-checked on every request. - The app calls a dedicated, project-scoped API at
/api/v1/integrations/connectwithAuthorization: Bearer ACCESS_TOKEN. No project API key is needed.
Endpoints
All OAuth and data endpoints are on the WoWSQL API host, not on your project host. The consent screen is on the dashboard host.
| Purpose | Method and URL | Authentication |
|---|---|---|
| Authorization (start) | GET https://api.wowsql.com/api/v1/auth/oauth/authorize | None. Redirects (302) to the consent screen. |
| Consent screen | https://app.wowsql.com/oauth/consent | The user's WoWSQL sign-in. Reached through the authorization URL; do not link to it directly. |
| Token (code exchange and refresh) | POST https://api.wowsql.com/api/v1/auth/oauth/token | Client ID and client secret. |
| Current user and project | GET https://api.wowsql.com/api/v1/integrations/connect/me | Authorization: Bearer ACCESS_TOKEN |
| Data API | https://api.wowsql.com/api/v1/integrations/connect/... | Authorization: Bearer ACCESS_TOKEN |
| Public configuration | GET https://api.wowsql.com/api/v1/auth/oauth/config?project_ref={slug} | None. Returns the URLs above and example settings for n8n, Make and Zapier. Never returns a client secret. |
There is no token revocation endpoint and no introspection endpoint. Sign in with WoWSQL is plain OAuth 2.0, not OpenID Connect: no ID token is issued. Use GET /me to identify the user. Older Zapier builds use the legacy aliases /api/v1/auth/oauth/zapier/... and /api/v1/integrations/zapier/...; they behave the same, but new apps should use the paths above.
How the Flow Works
| Step | Who | What happens |
|---|---|---|
| 1 | Your app | Sends the user's browser to the authorization URL with client_id, redirect_uri, state and scope. |
| 2 | WoWSQL API | Checks that the client exists and is active, that redirect_uri is registered for it and that the scopes are valid, then redirects to the consent screen. Errors at this step are returned as JSON (400); the browser is not sent back to your app. |
| 3 | User | Signs in to WoWSQL if needed, selects a project and clicks Allow. |
| 4 | WoWSQL | Redirects the browser to redirect_uri?code=...&state=.... The code is single-use and expires after 10 minutes. |
| 5 | Your app (server) | Checks state, then exchanges the code at the token endpoint with its client ID and secret. |
| 6 | Your app (server) | Calls the data API with the access token, and uses the refresh token to get a new access token when it expires. |
Registering an OAuth Client
OAuth clients belong to a project. Anyone with write access to the project can create, edit and delete them.
- Open the project in the dashboard and go to Settings → OAuth (the section is titled OAuth 2.0 Client IDs).
- Click Create client.
- Enter a Name. Users see it on the consent screen ("Name is asking to work with one of your WoWSQL projects"), so use your product name.
- Under Authorized redirect URIs, paste the callback URL your app or integration platform shows. Add more with + Add URI.
- Click Create. Copy the Client ID and Client secret. The secret is shown only once.
Expand OAuth endpoints on the same page to copy the Authorization URI, Token URI and Token test URI (/integrations/connect/me).
| Field | Rules |
|---|---|
| Name | Up to 255 characters. Defaults to "Sign in with WoWSQL" when created through the API without a name. |
| Authorized redirect URIs | At least one. Each must be https://, except http://localhost and http://127.0.0.1 for development. A trailing slash is ignored when matching; everything else must match exactly. |
| Client ID | Generated, in the form wowsql_.... Not secret. |
| Client secret | Generated. Stored only as a hash, so WoWSQL cannot show it again. Keep it on your server. |
Local development. If a client has a registered loopback redirect URI such as http://localhost:3000/oauth/callback, that client may also use the same URI on any other port (RFC 8252). The scheme, the host (localhost and 127.0.0.1 are different hosts) and the path must match. Loopback URIs that are not registered on the client are rejected, and the WoWSQL-managed Zapier client accepts none.
Editing. Click the client name or the pencil icon to change the name or the redirect URIs. Saving replaces the whole URI list. The secret cannot be viewed or rotated; to rotate it, create a new client, move your app to it and delete the old one.
Deleting. The trash icon deletes (revokes) the client. This cannot be undone. Revoked clients are hidden from the list. Deleting a client disconnects every user of it at once; see Revoking Access.
The dashboard uses these account-level endpoints, which need a WoWSQL account access token (not an OAuth app token):
GET https://api.wowsql.com/api/v1/projects/{slug}/oauth/clients
POST https://api.wowsql.com/api/v1/projects/{slug}/oauth/clients
{ "name": "My app", "redirect_uris": ["https://myapp.example.com/oauth/callback"] }
→ { "client_id": "wowsql_...", "client_secret": "...", "name": "My app",
"project_ref": "{slug}", "redirect_uris": [...] }
PATCH https://api.wowsql.com/api/v1/projects/{slug}/oauth/clients/{client_id}
{ "name": "New name", "redirect_uris": [...] } (both optional)
DELETE https://api.wowsql.com/api/v1/projects/{slug}/oauth/clients/{client_id}
→ { "success": true, "client_id": "wowsql_..." }
Authorization Request
GET https://api.wowsql.com/api/v1/auth/oauth/authorize
?response_type=code
&client_id=wowsql_xxxxxxxxxxxxxxxxxxxxxxxx
&redirect_uri=https%3A%2F%2Fmyapp.example.com%2Foauth%2Fcallback
&scope=read%20write
&state=RANDOM_VALUE_YOU_STORED
| Parameter | Required | Description |
|---|---|---|
client_id | Yes | Your client ID. |
redirect_uri | Yes | One of the client's registered redirect URIs. |
response_type | No | Only code is accepted (the default). |
scope | No | Space-separated. Default read write. See Scopes. |
state | Recommended | Returned unchanged with the code. Generate a random value per request, store it in the user's session and compare it when the user returns. WoWSQL does not validate it. |
project_ref | No | Project slug to preselect on the consent screen. Ignored for clients created in a project; see below. |
Consent screen
The consent screen loads everything it shows about the app from WoWSQL, not from the link: the app name registered on the client, the exact redirect URL and its host ("After you allow, you'll be sent to ..."), the signed-in WoWSQL account (with a Switch link), a project picker and what the app will be able to do. A user who is not signed in is sent to the WoWSQL sign-in page and returned afterwards.
- Verification label. WoWSQL's own Zapier integration is shown as Official WoWSQL integration. Every client created in a project, including yours, is shown as Unverified app: registered by a WoWSQL user. Only continue if you trust {host}. Use a name and a redirect host your users will recognise.
- Projects. The picker lists the projects the user owns and the projects where their team role allows reading, with the role shown. The token is bound to the project they choose.
- Access is capped by role. If the app asks for
writebut the user's role on the chosen project only allows reading (for example a viewer), the screen says the app will get read-only access, and the code is issued withreadonly. See Scopes. - For a client created in a project, that project is preselected and locked for users who have access to it. Other WoWSQL users choose one of their own projects.
- If
client_idis unknown or deleted, orredirect_uriis not allowed for it, the screen shows Can't continue with the reason and does not redirect anywhere. - Not now returns the user to the WoWSQL dashboard. Your app does not receive an
error=access_deniedredirect, so do not wait for one.
When the user clicks Allow, the browser is sent to:
https://myapp.example.com/oauth/callback?code=AUTHORIZATION_CODE&state=RANDOM_VALUE_YOU_STORED
If redirect_uri already has a query string, code and state are appended with &.
Token Endpoint
Call the token endpoint from your server. Send the body as application/x-www-form-urlencoded (standard) or JSON. Authenticate the client with client_id and client_secret in the body (client_secret_post) or with an HTTP Basic Authorization header (client_secret_basic).
Exchange the authorization code
curl -X POST https://api.wowsql.com/api/v1/auth/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=authorization_code \
-d code=AUTHORIZATION_CODE \
-d redirect_uri=https://myapp.example.com/oauth/callback \
-d client_id=wowsql_xxxxxxxxxxxxxxxxxxxxxxxx \
-d client_secret=YOUR_CLIENT_SECRET
redirect_uri is required and must equal the one used in the authorization request (a trailing slash is ignored). A code can only be redeemed with the credentials of the client it was issued to; any other client gets Invalid authorization code. The exchange also re-checks the user's access to the project, so scope in the response can be narrower than what you requested. Always read scope from the response.
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "kq3...",
"scope": "read write",
"project_ref": "my-project-1a2b3c4d",
"project_slug": "my-project-1a2b3c4d"
}
project_ref and project_slug both hold the slug of the project the user selected. The access token is a signed JWT; treat it as opaque and do not parse it.
Refresh the access token
curl -X POST https://api.wowsql.com/api/v1/auth/oauth/token \
-u "wowsql_xxxxxxxxxxxxxxxxxxxxxxxx:YOUR_CLIENT_SECRET" \
-d grant_type=refresh_token \
-d refresh_token=YOUR_REFRESH_TOKEN
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "NEW_REFRESH_TOKEN",
"scope": "read write",
"project_ref": "my-project-1a2b3c4d",
"project_slug": "my-project-1a2b3c4d"
}
Refresh tokens rotate. Every refresh returns a new refresh_token and invalidates the one you sent. Save the new token before you use the new access token. Each refresh token works once:
- A refresh token can only be used with the credentials of the client it was issued to.
- Sending an already-used refresh token fails with
Refresh token expired or revoked. Within 10 seconds of the rotation (a network retry) nothing else happens; keep the token you received first. - After those 10 seconds, reuse is treated as a leaked token: every refresh token that user holds for your app on that project is revoked, and the user must authorize your app again. Access tokens already issued keep working until they expire.
- Refreshing re-checks that the user is active and still has access to the project. The new tokens carry the scope the user's role allows now; if it was reduced, it stays reduced even if the role is raised later, until the user authorizes again.
Lifetimes
| Item | Lifetime |
|---|---|
| Authorization code | 10 minutes, single use |
| Access token | 1 hour (expires_in in the token response is authoritative) |
| Refresh token | Single use; 30 days from when it was issued. Each refresh issues a new one, so a connection that refreshes at least once every 30 days does not expire. |
Scopes and Permissions
| Scope | Grants |
|---|---|
read | GET /me, GET /projects, listing and describing tables, reading rows, POST /tables/{table}/query, and POST /sql for read statements. Always granted when the user can read the project. |
write | Insert, update and delete rows, and POST /sql for statements that modify data or schema, subject to the user's permissions below. |
For compatibility, zapier:read, openid and profile are accepted as read, and zapier:write as write. Any other value fails with 400 Unsupported scope: .... Request only read if your app does not change data.
An app acts as the WoWSQL user who approved it and is limited by that user's team permissions on the project. The project owner has all of them. The check runs on every request, so demoting or removing a member immediately limits or disconnects every app they approved.
| Operation | Scope | Team permission needed |
|---|---|---|
| Read tables and rows, read-only SQL | read | can_read |
Insert or update rows; INSERT, UPDATE, MERGE in SQL | write | can_write (and can_read) |
Delete rows; DELETE in SQL | write | can_delete |
Schema changes in SQL: CREATE, ALTER, DROP, TRUNCATE, COMMENT, SELECT ... INTO, table-level GRANT | write | can_manage_schema |
A user without can_write can only connect an app with read, and a user without can_read cannot connect apps to that project. An operation the user's role does not allow fails with 403 Your role on this project does not allow this action.
Project Data API
Base URL: https://api.wowsql.com/api/v1/integrations/connect. Send Authorization: Bearer ACCESS_TOKEN on every request. There is no project slug in the path; the token decides the project. Tables are those in the project's application schema (your tables, not the auth schema).
All read endpoints (/tables, /tables/{table}, /tables/{table}/rows, /tables/{table}/query) run in a read-only database transaction, and so does POST /sql for a token without the write scope. The database itself rejects any write in those transactions, including a write made inside a function.
| Method and path | Scope | Description |
|---|---|---|
GET /me | read | The user and project: id, email, name, project_slug, project_name, project_id, scopes. |
GET /projects | read | The one project the token is bound to: { "projects": [ {id, name, slug} ], "count": 1, "project": {...}, "scopes": "..." }. |
GET /tables | read | { "tables": [...], "count": n } |
GET /tables/{table} | read | Columns (name, type, nullable, default, key) and primary_key. |
GET /tables/{table}/rows | read | Latest rows as a JSON array, for polling triggers. Query: order (column, default the primary key), order_direction (desc default, or asc), limit (1 to 100, default 50). |
POST /tables/{table}/query | read | Filtered query. Returns { "data": [...], "count": n, "id": ... }. |
POST /tables/{table}/rows | write | Insert one row: { "data": { "column": value } }. Returns the new primary key as id. |
PATCH /tables/{table}/rows/{id} | write | Update the row whose primary key is {id}: { "data": {...} }. Returns affected_rows. |
DELETE /tables/{table}/rows/{id} | write | Delete the row whose primary key is {id}. Needs the can_delete permission. |
POST /sql | read or write | Run SQL: { "query": "..." } or { "queries": ["...", "..."] }. See the SQL rules below. |
Row ids. Every row returned by /rows and /query has an id field. It is the primary key value as a string; when the primary key column is not called id, the original value is also returned as record_id. When /rows is ordered by updated_at (or updatedAt), id is {primary key}_{updated_at}, so a polling trigger sees each update as a new item. Update and delete need a table with a primary key.
Timestamps. On insert and update, ISO 8601 strings are converted for timestamp and date columns.
Query rows
curl -X POST https://api.wowsql.com/api/v1/integrations/connect/tables/orders/query \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"select": ["id", "status", "total"],
"filters": [ { "column": "status", "operator": "eq", "value": "paid" } ],
"order_by": "created_at",
"order_direction": "desc",
"limit": 20
}'
Body fields: select, filters (each {column, operator, value, logical_op}; operators eq, neq, gt, gte, lt, lte, like, in, not_in, between, is), group_by, order_by (a column, or a list of {column, direction}), order_direction, limit (at most 100) and offset. having is not applied on this endpoint.
Insert a row
curl -X POST https://api.wowsql.com/api/v1/integrations/connect/tables/orders/rows \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "data": { "status": "new", "total": 49.5, "created_at": "2026-10-21T09:30:00Z" } }'
{ "id": 1042, "success": true, "message": "Record created successfully" }
Run SQL
curl -X POST https://api.wowsql.com/api/v1/integrations/connect/sql \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "query": "SELECT status, count(*) FROM orders GROUP BY status" }'
{
"success": true,
"id": "paid",
"type": "select",
"data": [ { "status": "paid", "count": 12 }, { "status": "new", "count": 3 } ],
"row_count": 2,
"rows_affected": 0,
"queries_executed": 1,
"results": null
}
For a batch, data describes the first statement and results lists every statement (type select with columns, data, row_count, or mutation with rows_affected). Statements run in order on one connection and are committed together at the end. SQL rules:
- Put one statement in each entry of
queries. - Allowed statements start with
SELECT,WITH,VALUES,TABLE,SHOW,EXPLAIN,INSERT,UPDATE,DELETE,MERGE,CREATE,ALTER,DROP,TRUNCATE,COMMENTorGRANT. Anything else, such asDO,COPY,CALL,SETorVACUUM, is rejected with 403. GRANTis limited to table-level grants to an API role, for exampleGRANT SELECT, INSERT ON TABLE orders TO authenticated(rolesanon,authenticated,service_role).- Rejected anywhere in a statement: role switching (
SET ROLE,SET SESSION AUTHORIZATION), role and user management,set_config,pg_sleep, server file and administration functions (such aspg_read_fileandpg_terminate_backend), large-object functions,dblink,COPY ... PROGRAM,ALTER SYSTEM, ownership changes,ALTER DEFAULT PRIVILEGES, changing the transaction mode, and creating or altering databases, schemas, extensions, servers, foreign tables, publications, subscriptions, event triggers, languages and tablespaces. The check also reads string literals, so a statement that merely contains one of these names in a string is rejected too. - Each statement also needs the scope and team permission for its kind; see Scopes and Permissions.
Integration Platforms
Create one OAuth client per integration in Settings → OAuth and paste that platform's callback URL as a redirect URI. All platforms use the same endpoints:
| Setting | Value |
|---|---|
| Grant type | Authorization Code (no PKCE) |
| Authorization URL | https://api.wowsql.com/api/v1/auth/oauth/authorize |
| Access token URL | https://api.wowsql.com/api/v1/auth/oauth/token |
| Client authentication | Client ID and secret in the request body |
| Scope | read write (or read) |
| Test URL | https://api.wowsql.com/api/v1/integrations/connect/me |
n8n
- Create an OAuth2 API credential and use the settings above. Set Authentication to Body.
- Redirect URI to register:
https://oauth.n8n.cloud/oauth2/callbackfor n8n Cloud, or{N8N_EDITOR_BASE_URL}/rest/oauth2-credential/callbackfor self-hosted n8n. Use the exact URL n8n shows on the credential. - Optional: put
project_ref=your-project-slugin Auth URI Query Parameters to preselect a project. - Use the HTTP Request node with this credential to call the data API.
Make
Use an OAuth 2.0 connection with the settings above. The usual callback is https://www.make.com/oauth/cb/oauth2, but register the exact callback URL Make shows for your connection.
Zapier
WoWSQL has a Zapier app with these triggers and actions: New Row, Updated Row (uses updated_at when the table has it), Find Row, Create Row, Update Row, Delete Row and Run SQL. The connection is labelled {email} / {project_slug}, and access tokens are refreshed automatically. The app uses a WoWSQL-managed client, so you do not create a client for it; the project is chosen on the consent screen.
To build your own private Zapier integration instead, create a project client, set the Zapier OAuth v2 redirect URL shown in the Zapier developer platform as its redirect URI, and use the settings above with automatic refresh enabled.
Custom apps
Implement the standard authorization code flow on your server. Keep the client secret on the server, generate and check state, store the refresh token encrypted, refresh when you get a 401 or when expires_in has passed, save the new refresh_token from every refresh (the old one stops working), make sure only one refresh per connection runs at a time, and send the user through authorization again when refreshing fails. Mobile and single-page apps cannot keep a client secret and PKCE is not supported, so route the flow through your backend.
// Express example: start and finish Sign in with WoWSQL
import crypto from 'node:crypto';
const AUTH = 'https://api.wowsql.com/api/v1/auth/oauth';
const REDIRECT_URI = 'https://myapp.example.com/oauth/callback';
app.get('/connect/wowsql', (req, res) => {
const state = crypto.randomBytes(24).toString('base64url');
req.session.wowsqlState = state;
const url = new URL(`${AUTH}/authorize`);
url.search = new URLSearchParams({
response_type: 'code',
client_id: process.env.WOWSQL_CLIENT_ID!,
redirect_uri: REDIRECT_URI,
scope: 'read write',
state,
}).toString();
res.redirect(url.toString());
});
app.get('/oauth/callback', async (req, res) => {
if (!req.query.state || req.query.state !== req.session.wowsqlState) {
return res.status(400).send('Invalid state');
}
const r = await fetch(`${AUTH}/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: String(req.query.code),
redirect_uri: REDIRECT_URI,
client_id: process.env.WOWSQL_CLIENT_ID!,
client_secret: process.env.WOWSQL_CLIENT_SECRET!,
}),
});
if (!r.ok) return res.status(502).send(await r.text());
const tokens = await r.json(); // access_token, refresh_token, expires_in, project_ref
// Store tokens server-side for this user, then call the data API:
const me = await fetch('https://api.wowsql.com/api/v1/integrations/connect/me', {
headers: { Authorization: `Bearer ${tokens.access_token}` },
}).then((x) => x.json());
res.send(`Connected to ${me.project_name}`);
});
Revoking Access
| Action | Effect |
|---|---|
| Delete the client in Settings → OAuth | All of the client's refresh tokens are revoked. Its access tokens are rejected on their next request with 401 This app's access was revoked, and token requests fail with 401 Invalid client credentials. |
| Lower the approving member's role | Takes effect on the next request: the app keeps only what the new role allows (for example a member changed to viewer leaves the app read-only). If the new role cannot read the project, data calls fail with 403 Your role on this project no longer allows this app to access it. |
| Remove the user from the project | Data API calls fail with 403 Not allowed for this project, and refresh fails with 400 User no longer has access to this project. |
| Deactivate or delete the project | Data API calls fail with 404 Project not found, and refresh fails with 400. |
| Deactivate the WoWSQL account | Data API calls fail with 401 and refresh fails with 400. |
| A rotated refresh token is used again | All refresh tokens the user holds for that app and project are revoked; the user must authorize the app again. |
There is no per-connection revoke in the WoWSQL dashboard and no revocation endpoint. A user disconnects from inside the third-party app, or the project owner deletes the client, which disconnects every user of that client.
Errors
Errors are JSON with a detail message. A rejected code exchange also carries the OAuth 2.0 fields error and error_description.
| Status | detail | Endpoint and meaning |
|---|---|---|
| 400 | Invalid client_id | Authorize, consent, token: unknown or deleted client. The consent screen shows it as Can't continue. |
| 400 | redirect_uri is not registered for this client | Authorize, consent: add the exact URI to the client (see the loopback rules for local development). |
| 400 | Unsupported scope: ... | Authorize, consent: only read and write (and the aliases) are allowed. |
| 400 | Only response_type=code is supported | Authorize. |
| 400 | Project not found | Consent: the project does not exist or is inactive. |
| 403 | Not allowed for this project | Consent, data API: the user is not the owner or a member of the project. |
| 403 | Your role on this project does not allow connecting apps | Consent: the user's role cannot read the chosen project. |
| 401 | Invalid client credentials | Token: wrong or missing client ID or secret, or the client was deleted. |
| 400 | Missing code or redirect_uri | Token, authorization_code grant. |
400 invalid_grant | Invalid authorization code, Authorization code already used, Authorization code expired, redirect_uri mismatch, User not found or inactive, User no longer has access to this project | Token, code exchange. Invalid authorization code is also returned when the code belongs to another client. |
| 400 | Missing refresh_token, Invalid refresh token, Refresh token expired or revoked, User not found or inactive, User no longer has access to this project | Token, refresh_token grant. Refresh token expired or revoked includes reuse of a rotated token. Send the user through authorization again. |
| 400 | Unsupported grant_type | Token: only authorization_code and refresh_token. |
| 401 | Could not validate credentials | Data API: missing, malformed or expired access token. Refresh and retry. |
| 401 | This app's access was revoked | Data API: the client was deleted. |
| 401 | Not a Sign in with WoWSQL access token | Data API: a dashboard or other token was sent instead of an OAuth app token. |
| 401 | OAuth app tokens cannot be used on this endpoint. ... | Dashboard API (for example /api/v1/projects) called with an OAuth app token. Use /api/v1/integrations/connect. |
| 403 | Missing required scope: write | Data API: the token only has read, either as granted or because the user's role now only allows reading. |
| 403 | Your role on this project does not allow this action | Data API: the approving user lacks the permission for this operation (for example can_delete or can_manage_schema). |
| 403 | Your role on this project no longer allows this app to access it | Data API: the approving user's role no longer allows reading the project. |
| 403 | ... statements are not allowed through Sign in with WoWSQL, Dangerous operation not allowed (...), Only table-level GRANT to authenticated/anon/service_role is allowed | POST /sql: see the SQL rules above. |
| 400 | Send one statement per query (use 'queries' for several). | POST /sql: more than one statement in one entry. |
| 404 | Table 'x' not found, Record x not found | Data API. |
| 400 | Table 'x' has no primary key | Update or delete on a table without a primary key. |
| 400 | SQL error: ... cannot execute ... in a read-only transaction | POST /sql with a read-scope token: a statement or function tried to write. |
| 400 | Query error: ..., Insert error: ..., Update error: ..., Delete error: ..., SQL error: ... | Data API: the database rejected the statement; the message includes the database error. |
| 429 | Rate limit exceeded: ... | Too many requests. Wait for the Retry-After seconds. |
Rate Limits
The token endpoint and the data API are covered by the standard WoWSQL API rate limits (per minute, per hour and per day). Responses carry X-RateLimit-Limit-Minute, X-RateLimit-Remaining-Minute, X-RateLimit-Limit-Hour and X-RateLimit-Remaining-Hour. When a limit is hit the API returns 429 with Retry-After: 60. Polling triggers should poll no more often than they need to.
Troubleshooting
redirect_uri is not registered for this client, or Can't continue on the consent screen: copy the callback URL from the platform again. Scheme, host, path and query must match; only a trailing slash is ignored. For local development, register the loopback URI on the client first.redirect_uri mismatchat the token step: send the sameredirect_uriyou used in the authorization request.Authorization code already used: codes are single use. Some platforms retry the exchange; start the connection again.Invalid authorization codeorInvalid refresh tokenwith valid-looking values: the code or token was issued to a different client. Use the same client ID and secret that started the flow.Invalid client credentials: check that the client ID and secret belong to the same client, that the client was not deleted, and that your platform sends them in the body or as HTTP Basic.- The connection breaks after a refresh (
Refresh token expired or revoked): your app kept using the old refresh token, or two refreshes ran at once. Store the newrefresh_tokenfrom every refresh and serialise refreshes. If the old token was reused after the 10-second window, all of the user's refresh tokens for the app were revoked: reconnect. - The connection stopped working after a long pause: the refresh token was not used for 30 days and expired. Reconnect.
cannot execute ... in a read-only transaction: the token only hasread, so SQL runs read-only. Reconnect withwrite, by a user whose role allows writing.- The app suddenly gets 403 or became read-only: the role of the user who approved it was lowered, or the operation needs a permission that role does not have (
can_deletefor deletes,can_manage_schemafor schema changes). Ask a member with the right role to connect the app. A scope reduced during a refresh stays reduced until the user authorizes again. - "Unverified app" on the consent screen: expected for every client created in a project. Use a clear client name and a redirect host your users recognise.
- The user's other projects are missing: by design, a token covers one project. Connect again and choose another project to get a second token. Projects where the user's role cannot read are not listed.
- 401 on
/api/v1/projects: OAuth app tokens only work on/api/v1/integrations/connect. - Nothing happens after "Not now": the user is returned to the WoWSQL dashboard, not to your app.
Security
Row-level security does not apply to apps. Data API and SQL requests run as the project's database owner role, the same as the dashboard SQL editor, so row-level security policies are not applied. What limits an app is its scope and the team permissions of the user who approved it: an app is never more powerful than that member is in the dashboard. Have apps approved by a member whose role matches what the app needs, and request read when you can.
- Treat the client secret like a password. Keep it on your server or in the integration platform's credential store; never ship it in a browser or mobile app.
- Always send and verify
state. WoWSQL returns it unchanged but does not check it for you. - Request
readunless your app writes data. Awritetoken from a member with schema permissions can change and drop tables in the selected project. - Store refresh tokens encrypted and replace the stored token after every refresh. Refresh tokens are single use; a reused token disconnects the user.
- Codes and refresh tokens only work with the client they were issued to.
- Register only the redirect URIs you use, and remove old ones. Register loopback URIs only on development clients.
- Users see your client name, your redirect host and an Unverified app label on the consent screen. Tell users what to expect before they connect.
- Deleting a client disconnects all of its users immediately: refresh tokens are revoked and access tokens are rejected on their next request.
- Tokens are limited to one project and cannot call account or billing endpoints.