Error codes
What each Plexivo API and Plexivo Postgres Data API error code means, its HTTP status, and what to do about it.
When a request fails, the Plexivo API answers with an HTTP status and a stable code your program can act on. This page lists the codes you are most likely to meet: first the Plexivo API that manages your resources, then the Data API, Auth, Realtime and Functions of Plexivo Postgres.
Error format#
{
"error": {
"code": "validation_failed",
"message": "one or more fields are invalid",
"details": { "fields": { "storage_gb": ["can only grow (now 20 GB)"] } }
}
}codeis stable. Build your logic on it.messageexplains the problem to a person and may change.detailscarries what you need to fix it, such as the fields that failed, when there is something to say.
Idempotency errors use the application/problem+json format, with code, detail and fix. Every response carries an x-trace-id header; quote it when you contact support.
Sign-in and access#
| Code | Status | What to do |
|---|---|---|
unauthorized | 401 | The token or API key is missing, wrong, revoked or expired. Check the Authorization header, or create a new key. |
reauthentication_required | 401 | A console session ended. details.reason is idle or absolute. Sign in again. |
forbidden | 403 | Your role, or the key’s permissions, do not allow this. The message says what was refused; for an API key it names the scope needed. |
step_up_required | 403 | Confirm your password or authenticator code, then retry. |
support_access_required | 403 | Someone outside the organisation tried to reveal a secret or data without an approved request. See Support and agency access. |
organisation_suspended | 403 | The organisation cannot create anything new. Contact support. |
Requests#
| Code | Status | What to do |
|---|---|---|
validation_failed | 422 | Fix the fields listed in details.fields and send again. |
not_found | 404 | The resource does not exist, or you cannot see it. |
name_taken | 409 | Choose another name. |
illegal_transition | 409 | The resource is not in a state that allows this. Check its status and retry later. |
quote_expired, quote_changed, quote_invalid | 409 | Ask for a new estimate and confirm the new price. |
price_changed | 409 | The price moved since you saw it. Review it and confirm again. |
idempotency_key_reused | 409 | Use a new Idempotency-Key for a different request. |
idempotency_in_flight | 409 | The first request with this key is still running. Wait and retry with the same key. |
invalid_idempotency_key | 400 | Use a key of 256 bytes or fewer. |
endpoint_retired | 410 | This endpoint is no longer available. The message says what replaced it. |
Limits and capacity#
| Code | Status | What to do |
|---|---|---|
rate_limited | 429 | Too many requests. Wait for the Retry-After seconds and retry. |
too_many_requests | 429 | An API key’s per-minute, concurrent or daily limit was reached. See Limits on every key. |
quota_exceeded | 403 | Your plan’s limit for this kind of resource is reached. Delete something, or change your plan. |
spend_limit_exceeded | 422 | The API key’s monthly spend limit would be passed. Raise the limit or use another key. |
contact_sales | 422 | What you asked for is arranged with sales. Talk to sales. |
no_capacity, queued_capacity | 503 | There is no room for this in its region right now. Nothing was created or charged. Retry in a few minutes. |
Billing#
| Code | Status | What to do |
|---|---|---|
plan_required | 402 | Choose a plan in Settings → Plan. |
wallet_empty | 402 | Add money to your wallet. |
insufficient_funds | 422 | The wallet balance is too low for this. Add money. |
first_deploy_verification_required | 428 | Complete the one-time check the console asks for before your first deployment. |
not_eligible | 422 | The offer, such as migration credits, does not apply. details.reasons says why. |
Plexivo AI#
| Code | Status | What to do |
|---|---|---|
ai_disabled | 403 | Plexivo AI is turned off for this organisation. |
ai_daily_cap | 429 | Today’s Plexivo AI limit is reached. Try again tomorrow. |
plan_expired, plan_stale | 409 | The plan you are approving is out of date. Ask Plexivo AI to plan again. |
ai_unavailable | 503 | Plexivo AI is briefly unavailable. Retry shortly. |
Temporary problems#
A 503, such as database_unavailable or payment_provider_unavailable, means something Plexivo depends on is briefly unavailable. Retry with backoff and the same Idempotency-Key. A 500 with internal_server_error is ours to fix: send us the x-trace-id.
Plexivo Postgres#
The Data API, Auth, Realtime and Functions answer with the same shape, plus a trace_id. Database errors also carry PostgreSQL’s sqlstate, and often a hint:
{
"error": {
"code": "unique_violation",
"message": "duplicate key value violates unique constraint \"profiles_username_key\"",
"sqlstate": "23505"
},
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}The client libraries raise it as PlexivoError (JavaScript) or PlexivoException (Dart), with status, code, message, hint, sqlstate and traceId. In a transaction or a raw SQL batch, operation is the step that failed, counting from 0.
create table profiles (id uuid primary key default auth.uid(), username text unique not null);
alter table profiles enable row level security;
create policy profiles_read on profiles for select using (true);
create policy profiles_own on profiles for insert to authenticated with check (id = auth.uid());import { createClient, PlexivoError, isUniqueViolation } from '@plexivo/client';
const db = createClient(PLEXIVO_DATA_URL, PLEXIVO_PUBLISHABLE_KEY);
await db.auth.signUp({ email, password });
await db.from('profiles').create({ username: 'asha' });
const other = createClient(PLEXIVO_DATA_URL, PLEXIVO_PUBLISHABLE_KEY, { persistSession: false });
await other.auth.signUp({ email: 'second@example.com', password });
try {
await other.from('profiles').create({ username: 'asha' });
} catch (error) {
if (isUniqueViolation(error)) console.log('That username is taken.');
else if (error instanceof PlexivoError) console.log(error.status, error.code, error.traceId);
else throw error;
}Or, as with Supabase, get errors back instead of thrown, by creating the client with throwOnError: false:
const quiet = createClient(PLEXIVO_DATA_URL, PLEXIVO_PUBLISHABLE_KEY, { throwOnError: false });
const { data, error } = await quiet.from('no_such_table');
console.log(data, error.code); // null 'table_not_exposed'import 'package:plexivo_client/plexivo_client.dart';
final db = PlexivoClient(dataUrl, publishableKey);
await db.signUp(email: email, password: password);
try {
await db.from('profiles').create({'username': 'asha'});
} on PlexivoException catch (e) {
if (e.isUniqueViolation) print('That username is taken.');
print('${e.statusCode} ${e.code} ${e.traceId}');
}Keys, tokens and limits#
| Code | Status | What to do |
|---|---|---|
invalid_api_key | 401 | The apikey header is missing or wrong, or names another project. Copy the key again from the Connect tab. The secret key is never accepted in a URL. |
origin_denied | 403 | The web page’s origin is not in the Data API’s allowed origins. Add the exact address, such as https://app.example.com or http://localhost:5173. |
secret_key_in_browser | 403 | The secret key arrived with a browser’s Origin header. Use the publishable key in browsers. On your server, do not forward the browser’s Origin header with the secret key. If the key was ever in a web page, rotate it. The client libraries throw the same code if you create a client with the secret key in a browser. |
secret_key_required | 403 | Raw SQL and user import need the secret key, from a server, with no user’s token. |
token_expired | 401 | The access token has run out. The client libraries refresh it and retry once by themselves; over HTTPS, call /auth/refresh and retry. |
invalid_access_token | 401 | The token is not valid for this project, or its session was ended. Sign in again. |
sign_in_required | 401 | This needs a signed-in user: a private room, a function, or a client call such as getUser(). |
rate_limited | 429 | Too many requests a minute from one address for this project; the rate depends on your plan. Wait for the Retry-After seconds, then retry. |
project_request_cap_reached | 429 | The project used its monthly requests. It resets at the start of the next month (UTC); a bigger plan includes more. |
too_large | 413 | The request body is over its limit: 5 MB for reads and writes, 1 MB for auth and GraphQL. Send fewer rows at once. |
project_not_ready | 503 | The Data API is still being set up. Retry in a moment. |
Data#
| Code | Status | What to do |
|---|---|---|
table_not_exposed | 404 | The table does not exist, or is not in the Data API: turn on row-level security and add a policy. A view needs security_invoker. |
function_not_exposed | 404 | The database function has no @expose in its comment, is overloaded, or has unnamed arguments. |
not_found | 404 | No row has that primary key, on /rest/v1/<table>/<id>. |
invalid_request | 400 | The query is not valid: an unknown column, operator or relation, a limit over 1,000, more than 1,000 rows, or an update or delete without a filter. The message says which. |
permission_denied | 403 | Row-level security or a grant refused the write. Check the policy’s with check and the row’s owner column. |
unique_violation | 409 | A row with that unique value exists. Ask for another value, or upsert. |
foreign_key_violation | 409 | The related row does not exist, or other rows still point at this one. |
exclusion_violation | 409 | An exclusion constraint, such as overlapping bookings, refused the row. |
retry | 409 | Two transactions collided. Run it again. |
not_null_violation | 400 | A required column is missing. Send it, or give it a default. |
check_violation | 400 | A check constraint refused the value. |
raised | 400 | Your SQL function raised an exception; its message is in message. |
invalid_value | 400 | PostgreSQL refused the value or statement; message and sqlstate say why. |
statement_timeout | 504 | The query ran past its time: 8 seconds through the Data API, 30 for raw SQL. Add an index, or read fewer rows. |
database_unavailable | 503 | The database is restarting or unreachable. Retry with backoff. |
Auth#
| Code | Status | What to do |
|---|---|---|
email_taken | 409 | An account has that email. Offer sign-in or password recovery. |
weak_password | 422 | Passwords are 10 to 128 bytes. |
invalid_email | 422 | The address is not a valid email. |
metadata_too_large | 422 | user_metadata is over 4 KB. |
invalid_credentials | 401 | The email or password is wrong. |
invalid_refresh_token | 401 | The refresh token was used, revoked or has run out. Sign in again. |
mfa_required | 401 | The user has two-step verification. Ask for their authenticator code and send it as mfaCode. |
invalid_mfa_code | 401 | The authenticator code is wrong or was used already. |
invalid_passwordless_code | 401 | The email code is wrong, used or expired. Ask for a new one. |
invalid_recovery_code | 401 | The password recovery code is wrong, used or expired. Ask for a new one. |
anonymous_disabled | 403 | Guests are off. Turn on Guests under Sign-in methods. |
not_anonymous | 409 | Only a guest can add an email with linkEmail. |
redirect_not_allowed | 400 | Add the address to Return URLs. |
provider_disabled | 403 | Turn the provider on under Social sign-in. |
invalid_code | 401 | The OAuth code is wrong, used, older than 60 seconds, or its PKCE verifier does not match. Start the sign-in again. |
Functions and Realtime#
| Code | Status | What to do |
|---|---|---|
function_not_found | 404 | No deployed function has that name. |
function_unavailable | 502 | The Functions runtime did not answer. Check it is running on the Functions tab. |
body_too_large | 413 | A function’s request body is over 6 MB. |
A realtime join the server refuses reports its reason as the code, such as table_not_exposed, sign_in_required or invalid (realtime is off for the table, or the filter is not valid), through onError and ready().
From the client libraries#
These are raised by the client before or instead of a request:
| Code | What to do |
|---|---|
missing_where | An update or delete without a filter, stopped before it was sent. Add where(), or allowGlobalUpdate(). |
record_not_found | first(), take(), sole() or single() found no row. Use firstOrNull() or maybeSingle() when none is fine. |
multiple_rows | sole(), single() or maybeSingle() found more than one row. |
network_error | The Data API could not be reached. Retry when the device is online. |
aborted | Your abortSignal cancelled the request. |
Updated 28 October 2026