Skip to content

Error codes

Errors are JSON. Most carry a typed code you can switch on:

{
"error": "Unauthorized",
"code": "TOKEN_EXPIRED",
"message": "Token has expired. Refresh via /auth/refresh or re-run `vaif login`.",
"requestId": "req_…"
}

error is the HTTP status class, code the specific reason, and details, when present, has field-level information. Some routes answer with an upper-case error and no code, such as { "error": "NOT_FOUND", "message": "…" }, and plan limits name the limit in message. Quote the requestId when you contact support.

Status Meaning
400 The request is malformed or failed validation
401 Missing, invalid or expired credentials
402 A plan quota is used up
403 Signed in, but not allowed: role, scope, plan limit or a deactivated project
404 Not found, or not visible to you
409 Conflicts with something that exists
429 Rate limited: wait for Retry-After seconds
500 Server error: retry, and quote the requestId if it persists

This table is the API’s own list of typed codes, with the message each one answers with.

CodeStatusMessage
TOKEN_MISSING401 UnauthorizedMissing Authorization header. Send 'Authorization: Bearer <jwt>' or 'x-vaif-key: vk_<...>'.
TOKEN_INVALID401 UnauthorizedToken signature is invalid or the JWT is malformed.
TOKEN_EXPIRED401 UnauthorizedToken has expired. Refresh via /auth/refresh or re-run vaif login.
SCOPE_INSUFFICIENT403 ForbiddenYour token does not grant access to this endpoint. Use a user JWT for control-plane routes.
CORS_ORIGIN_NOT_ALLOWED403 ForbiddenThis browser origin is not allowed. Change allowed origins in Project settings → API.
INVALID_API_KEY401 UnauthorizedAPI key not recognized. Check the value in the dashboard.
PROJECT_NOT_FOUND404 Not foundProject does not exist or you don't have access.
FEATURE_DISABLED403 ForbiddenThis feature is off for this project. Turn it on in Project settings → Features.
PROJECT_DEACTIVATED403 ForbiddenThis project has been deactivated. Contact support@vaif.studio to reactivate.
SESSION_NOT_FOUND404 Not foundSession not found or already revoked.
INVALID_WHERE400 Bad requestInvalid where clause shape. Expected {field: string, operator: 'eq'|'neq'|..., value: any}.
INVALID_SELECT400 Bad requestInvalid select. Use comma-separated column names or '*'.
INVALID_COLUMN400 Bad requestColumn does not exist on this table.
LIMIT_TOO_HIGH400 Bad requestlimit must be ≤ 1000. Use offset for pagination.
SQL_PARSE_ERROR400 Bad requestSQL is not valid Postgres syntax.
SQL_FORBIDDEN_SET_ROLE400 Bad requestSET ROLE is not allowed; the engine manages role per project.
SQL_FORBIDDEN_SET_SESSION_AUTH400 Bad requestSET SESSION AUTHORIZATION is not allowed.
SQL_FORBIDDEN_SET_SEARCH_PATH400 Bad requestSET search_path is not allowed; the engine sets it per project.
SQL_FORBIDDEN_RESET_ROLE400 Bad requestRESET ROLE is not allowed.
SQL_FORBIDDEN_EXTENSION400 Bad requestCREATE/DROP EXTENSION is not allowed.
SQL_FORBIDDEN_SECURITY_DEFINER400 Bad requestSECURITY DEFINER functions are not allowed.
SQL_FORBIDDEN_COPY_PROGRAM400 Bad requestCOPY FROM PROGRAM is not allowed.
SQL_FORBIDDEN_CROSS_SCHEMA400 Bad requestStatements may only reference your project's schema, pg_catalog, or information_schema.
SQL_FORBIDDEN_TEMP_SCHEMA400 Bad requestTemporary tables, views, sequences and types are not allowed, and statements may not reference the pg_temp or pg_toast schemas.
SQL_FORBIDDEN_TRANSACTION_CONTROL400 Bad requestTransaction control is not allowed; each request already runs as one transaction (a single leading BEGIN and trailing COMMIT are accepted).
SQL_FORBIDDEN_SET_CONFIG400 Bad requestset_config() may not change role, session_authorization, search_path or the read-only settings, and must be transaction-local (third argument true).
SQL_FORBIDDEN_SESSION_SET400 Bad requestSession-level SET and RESET are not allowed; use SET LOCAL, which lasts for the request's transaction.
SQL_FORBIDDEN_SET_TIMEOUT400 Bad requestUse SET LOCAL with a positive literal timeout no higher than the platform cap. Zero, session SET and dynamic timeout changes are refused.
SQL_FORBIDDEN_SETTING400 Bad requestThis setting cannot be changed from SQL: memory, parallel-worker, JIT and other server resource settings are set by the platform. Formatting, planner switches and namespaced settings (request.*) may be set with SET LOCAL or set_config(..., true).
SQL_FORBIDDEN_SETTINGS_WRITE400 Bad requestWriting pg_settings is not allowed; it changes server settings for the whole session.
SQL_FORBIDDEN_STATEMENT400 Bad requestThis kind of statement is not allowed (code blocks, functions, roles, grants, prepared statements, NOTIFY, and server-level commands).
SQL_FORBIDDEN_FUNCTION400 Bad requestThis function is not allowed (it runs SQL text, reads server files, uses large objects or advisory locks, or signals other sessions).
SQL_FORBIDDEN_COPY_FILE400 Bad requestCOPY to or from a server file is not allowed.
MULTI_STATEMENT_SQL_RESULT200Multi-statement SQL — response is an array of per-statement results.
RATE_LIMITED429 Too many requestsToo many requests. Slow down.
QUOTA_EXCEEDED402 Payment requiredPlan quota exceeded. Upgrade at https://console.vaif.studio/app/billing.
ORIGIN_NOT_ALLOWED403 ForbiddenOrigin not in the project's allowed list.
SECRET_KEY_IN_BROWSER403 ForbiddenThis is a secret key (vk_srv_…), and secret keys only work from a server. In browser code use the project's publishable key (vk_pub_…) and row rules; keep the secret key on your server.
CSRF_TOKEN_INVALID403 ForbiddenMissing or invalid CSRF token. Reload the page and try again.
REAUTH_REQUIRED403 ForbiddenConfirm it's you before changing how you sign in.
MFA_LOCKED429 Too many requestsToo many failed MFA attempts. Try again in a few minutes.
IMPERSONATION_READ_ONLY403 ForbiddenThis is a read-only impersonation session — writes as the impersonated user are blocked.
MCP_DISABLED403 ForbiddenHosted MCP is not enabled on this server.
MCP_CONNECTION_ENDED401 UnauthorizedThis MCP connection was revoked or no longer exists. Reconnect from your MCP client.
MCP_PROJECT_MISMATCH403 ForbiddenThis MCP connection is bound to a different project.
MCP_READ_ONLY403 ForbiddenThis MCP connection is read-only. Reconnect and allow writes to change data.
MCP_ROUTE_NOT_ALLOWED403 ForbiddenThis endpoint is not available to hosted MCP connections.
MCP_PRODUCTION_WRITES_DISABLED403 ForbiddenProduction writes are not allowed on this MCP connection. Reconnect and allow production writes (admin role).
BAD_REQUEST400 Bad requestRequest validation failed.
INTERNAL_ERROR500 Server errorUnexpected server error. Reference the requestId when contacting support.

Requests your app makes with an API key are limited per key and per signed-in user (per client address when nobody is signed in), so one busy user cannot use up the key for everyone else. Each key also has an overall ceiling. The routes for your app’s users have their own limits per client address and project, for example 5 sign-ups per 10 minutes and 30 refreshes per minute.

A limited request gets 429 with a Retry-After header, in seconds. Browsers can read it, so a web app can wait exactly that long; @vaifstudio/client gives it to you as VaifRateLimitError.retryAfter. Responses also carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset.

  1. Read the whole body. code and message usually say exactly what is wrong.
  2. Check the key. A vk_pub_… key sees only the rows the row rules allow, and a request without the user’s access token is anonymous.
  3. Check the function logs on the Functions page when a function fails.
  4. Watch the rate-limit headers before you reach a 429.

@vaifstudio/client throws typed errors. Every one extends VaifError, which has code, statusCode, requestId and details:

import { createVaifClient, VaifError, VaifRateLimitError, type VaifErrorCode } from "@vaifstudio/client";
const vaif = createVaifClient({
projectId: process.env.VAIF_PROJECT_ID,
apiKey: process.env.VAIF_PUBLISHABLE_KEY,
});
try {
await vaif.from("posts").list();
} catch (error) {
if (error instanceof VaifRateLimitError) {
console.warn(`rate limited, retry in ${error.retryAfter ?? 1}s`);
} else if (error instanceof VaifError) {
const code = error.code as VaifErrorCode;
console.error(error.statusCode, code, error.message, error.requestId);
} else {
throw error;
}
}

See the JavaScript SDK for the other error classes. For help, email support@vaif.studio with the requestId.