Common Errors Quick Reference
When errors occur while using the CloudBase PostgreSQL database, they mostly involve the three layers of GRANT / RLS Policy / JWT. This article lists common issues in the format "symptom → root cause → troubleshooting" for quick localization.
💡 Reading suggestion: Start with the "Quick Troubleshooting Decision Tree" to locate the broad category of the problem, then refer to the specific section.
Quick Troubleshooting Decision Tree
Request failed / no data obtained
│
├─ HTTP 401 ? → JWT missing / expired / signature error → see [§ JWT and Auth](#jwt)
├─ HTTP 403 ? → Insufficient table-level GRANT → see [§ Table-level GRANT](#grant)
│ → or RLS WITH CHECK rejected the write
├─ HTTP 404 ? → Table / RPC function does not exist → see [§ Resource Not Found](#not-found)
├─ HTTP 406 ? → Single-object return violates count constraint → see [§ Return Format](#return-format)
├─ HTTP 409 ? → Unique constraint / foreign key conflict → check table constraints
├─ HTTP 500 ? → RAISE inside function / instance error → see [§ Internal Error](#internal)
└─ 200 but empty data ? → RLS silently filtered → see [§ RLS Silent Filter](#silent)
JWT and Auth
401 Unauthorized / JWT expired
| Symptom | Root Cause | Troubleshooting |
|---|---|---|
No Authorization Header at all | Client forgot to attach it | Ensure the frontend uses the SDK (auto-attached) or manually add Authorization: Bearer <token> |
JWT expired | access_token expired | Frontend calls refresh or re-signs in; Publishable Key / API Key never expire |
Invalid JWT | Signature error, token tampered | Check for spaces or line breaks introduced during copy-paste; confirm environment ID matches aud / project_id in the token |
iss claim mismatch | Used a token from another environment across environments | Frontend environment ID must match the token issuance environment |
Check the role / sub in the Current Token
The fastest method is to use jwt.io or decode on the backend (without verification):
echo "<your-jwt>" | awk -F. '{print $2}' | base64 -d 2>/dev/null
You should see role: "anon" | "authenticated" | "service_role" and sub: "<uuid>".
Table-level GRANT
permission denied for table xxx
ERROR: permission denied for table todos
| Root Cause | Troubleshooting |
|---|---|
| Role lacks GRANT for the corresponding DML | Check with \dp public.todos; as needed GRANT SELECT/INSERT/UPDATE/DELETE ON public.todos TO <role>; |
| RLS Policy written but GRANT missing | RLS is the second layer; GRANT is the first. Both layers must pass |
| Used a non-business role | Application traffic must fall into one of anon / authenticated / service_role; other system roles are unavailable |
permission denied for sequence xxx_id_seq
ERROR: permission denied for sequence todos_id_seq
Cause: A serial / bigserial primary key is backed by a SEQUENCE object; granting INSERT on the table does not automatically grant sequence permission.
Fix:
GRANT USAGE, SELECT ON SEQUENCE public.todos_id_seq TO authenticated;
GRANT USAGE, SELECT ON SEQUENCE public.todos_id_seq TO service_role;
A table's sequence name is usually
<table>_<column>_seq, viewable with\ds public.*.
RLS Policy
new row violates row-level security policy for table "xxx"
ERROR: new row violates row-level security policy for table "orders"
| Root Cause | Troubleshooting |
|---|---|
WITH CHECK not satisfied | A written field does not satisfy the Policy. Most common: frontend forges owner_id/buyer_id inconsistent with JWT sub → let the database DEFAULT auto-bind, frontend should not pass it |
| INSERT Policy missing | After enabling RLS, you must explicitly write an INSERT Policy for each role (or the role has BYPASSRLS) |
UPDATE Policy missing WITH CHECK | When only USING is written, the row after UPDATE is not checked; it is recommended to pair USING + WITH CHECK |
200 OK but SELECT returns empty array (RLS silent filter)
Characteristic: HTTP 200, no error, but data is [] or null.
| Root Cause | Troubleshooting |
|---|---|
Current role (e.g., anon) has no matching SELECT Policy | Even adding ?owner_id=eq.xxx filter won't help; RLS must be passed first |
| Policy condition did not match the current user | Use ExecutePGSql with Role: authenticated to simulate execution and see if it matches expectations |
current_setting('request.jwt.claims', true) is empty | Check whether the request actually carried the JWT and whether the gateway successfully parsed it (generally select current_setting(...) directly in the SQL editor won't show it — it is only injected by PostgREST) |
Debugging Tip: Simulate a Specific Role with ExecutePGSql
{
"EnvId": "<envId>",
"Sql": "SELECT * FROM public.todos",
"Role": "authenticated"
}
You can verify whether the Policy takes effect without actually sending an HTTP request.
Resource Not Found
404 Not Found when calling RPC
| Root Cause | Troubleshooting |
|---|---|
| Function name misspelled | Exactly the same as the CREATE FUNCTION name in SQL (including schema) |
Function not under public schema and db-schemas not configured | By default only functions under public are exposed by PostgREST |
| Function just created, PostgREST metadata not refreshed | Wait a few seconds and retry, or try to notify the backend to refresh |
| Parameter signature mismatch | PostgREST addresses by parameter name + type; all keys in the JSON body must exactly match the function argname |
404 Not Found when accessing a table
{"message":"relation \"public.todoes\" does not exist"}
- Table name, schema misspelled
- Table not in
publicschema (needAccept-Profile: <schema>Header to switch)
Return Format
406 Not Acceptable JSON object requested, multiple (or no) rows returned
{"message":"JSON object requested, multiple (or no) rows returned"}
Cause: You set Accept: application/vnd.pgrst.object+json, but the result is not exactly 1 row.
Fix: Either remove that Header, or use .single() / .maybeSingle() and ensure the query condition precisely matches 1 row.
Get the full row after writing
Add Prefer: return=representation:
curl -X POST '.../v1/rdb/rest/orders' \
-H 'Authorization: Bearer <token>' \
-H 'Prefer: return=representation' \
-H 'Content-Type: application/json' \
-d '{"product_id":1, ...}'
ExecutePGSql Errors
InternalError executing DDL failed
Some DDL statements (CREATE/ALTER/DROP/GRANT/REVOKE/TRUNCATE/COMMENT, etc.) may return InternalError when executed directly.
Fix: Wrap with DO LANGUAGE plpgsql $$ BEGIN EXECUTE '...'; END $$ and retry. This requires BEGIN and dynamic EXECUTE, so it explicitly uses LANGUAGE plpgsql:
DO LANGUAGE plpgsql $$
BEGIN
EXECUTE 'CREATE TABLE public.products (id serial PRIMARY KEY, name text)';
END
$$;
Single quotes
'inside the string need to be escaped as''.
Each API call can only execute one SQL statement
ExecutePGSql does not support multiple statement concatenation; split and call them one by one by ;.
permission denied (when the Role parameter is present)
After adding the "Role": "authenticated" parameter, it is executed as a normal role; this triggers GRANT/RLS. This is not a bug — it is exactly its purpose. To bypass permission debugging, remove the Role parameter.
Internal Error
500 Internal Server Error from function RAISE
{"message":"Product does not exist or insufficient stock"}
This is a business error thrown by RAISE EXCEPTION '...' inside the function body — a normal failure path. The frontend can handle it according to the business message.
Instance unavailable / 503
The database instance has a temporary fault or restart. The client should retry with exponential backoff.
Gateway Errors
INVALID_REQUEST / PERMISSION_DENIED / RESOURCE_NOT_FOUND
Refer to the error code table in HTTP API - PostgREST RESTful API and the correspondence with native PostgreSQL errors:
| Gateway Error Code | HTTP | Meaning |
|---|---|---|
INVALID_PARAM | 400 | Invalid request parameter |
INVALID_REQUEST | 400 / 406 | Invalid request body / single-object count constraint not satisfied |
PERMISSION_DENIED | 401 / 403 | Authentication failed / insufficient permission |
RESOURCE_NOT_FOUND | 404 | Table / function does not exist |
SYS_ERR | 500 | Internal system error |
OPERATION_FAILED | 503 | Database connection failed |
RESOURCE_UNAVAILABLE | 503 | Database unavailable |
General Troubleshooting Checklist
When encountering database-related issues, 80% can be located by self-checking in the following order:
- Is the JWT attached?
Authorization: Bearer <token>cannot be omitted - Is the
rolein the JWT as expected? Decode the payload with base64 to take a look - Is the table-level GRANT given to the corresponding role? Check with
\dp <table> - Is RLS enabled?
SELECT relname, relrowsecurity FROM pg_class WHERE relname='xxx'; - Does the role + operation have a matching Policy? View the Policy list with
\d+ <table> - Does the Policy's USING / WITH CHECK actually hold for the current user? Simulate execution with
ExecutePGSqlusingRole - Did you forget to grant SEQUENCE permission for the
serialprimary key?
Next Steps
- Architecture and Permission Model — GRANT / RLS / two-tier permissions
- PG: Authentication — includes JWT / roles / Keys
- RPC — function calls and SECURITY modes
- Basic Permission Management