Skip to main content
This page is a list of things people get wrong, with the fix for each. Most are documented elsewhere too; this page exists so you can grep for the specific error message or symptom and jump to the right fix without reading three concept pages. If you’re hitting something here, read the linked source-of-truth page after applying the fix. The pitfalls list is a shortcut; the concept pages are the explanations.

Auth and tokens

”You only need the Anon Key client-side; the Service Role Key is server-side only”

The Service Role Key bypasses RLS. Don’t embed it in browsers, mobile apps, or anything else that ships to user devices. If you’ve accidentally shipped it, rotate it from the Studio and treat any data accessed during the leak window as compromised. See Auth model.

Authorization header with Bearer (trailing space) returns 401 silently on webhook routes

The webhook trigger route (POST /api/webhooks/{id}) checks auth_header.lower().startswith("bearer ") (with a trailing space) and pulls the token by slicing from index 7. If your client constructs Authorization: Bearer ${token ?? ""} and token is undefined, you send literally "Bearer " and get an empty string as the token. Returns 401 with no useful error. Fix: guard the construction so you don’t send a Bearer header when the token is missing, or pass the secret via the ?token=... query parameter instead. The general /api/* auth path uses auth_header.split() and is whitespace-tolerant. A Bearer there returns 401 “Authorization header required” rather than a confusing empty-token failure.

”I’m sending the access token but getting unauthorized”

The access token expires after 1 hour. Most client SDKs refresh automatically; if you’re rolling your own, you need to call POST /auth/v1/token?grant_type=refresh_token before the 1-hour mark. See Signup, signin, magic link.

”The user is signed in but my queries return zero rows”

Probably an RLS policy issue. The signed-in user has the authenticated role; check that your tables have policies granting SELECT to authenticated for the rows the user should see. See RLS Cookbook. If you’re hitting /rest/v1/* directly, confirm you’re sending the user’s access token in Authorization, not the Anon Key.

RLS and the ai schema

”My user’s token gets 403 on /api/*”

Since 0.12.0, a signed-in user’s JWT works only on the conversation routes: their own agent and orchestration sessions, runs on them, and approvals of their own paused runs. Every other /api/* route, including knowledge bases, knowledge-base search and sources, answers 403 with This endpoint requires the project's service role key. Call those routes from your backend with the Service Role key. The route list is in Which key for which route. A 403 on /run or /run/stream that names knowledge_bases (or runtime_knowledge_bases, context_handler_id, context_items[].item_id) means an end user’s run tried to choose its own knowledge bases or stored context. Configure the knowledge bases on the agent, or run the agent from your backend. A 404 on a run with a session_id means the session doesn’t exist or isn’t that user’s; end users can’t name a new session id. See Rules for end-user runs.

Querying ai.* via PostgREST fails, even with Accept-Profile: ai

The ai schema isn’t exposed through PostgREST (it’s not in PGRST_DB_SCHEMAS), whichever key you send. Reach AI resources through the typed /api/* endpoints instead.

”I added an RLS policy but my old policy still applies”

RLS policies are additive (OR-combined) by default. Adding a permissive policy doesn’t replace existing permissive policies; they all apply. If you intended to replace, DROP POLICY first.

Agent runs

Agent data tools see more on backend runs than on user runs

An agent’s data tools act as whoever started the run. With a user’s own JWT, database_query and database_write run as the authenticated role with that user’s claims, so your RLS policies apply to the agent’s configured tables, and the storage tools use the user’s token. With the Service Role key, they use the agent’s own database login, which bypasses RLS on its configured tables, and the storage tools see every bucket except the internal sources bucket, even in a session owned by a user. If your backend runs agents for users, configure those agents only with tables and buckets every such user may see in full. See What agent data tools act as and BaaS+AI cookbook Recipe 2.

temperature at the top level of agent body silently dropped

Agent create/update bodies accept name, model, system_prompt, and settings. Top-level temperature is silently dropped. Nest it inside settings:

MCP transport: "sse" works but "http" is the default

The platform’s MCP integration accepts both sse and http, but the default at the database level is http. Newer MCP servers use streamable HTTP; SSE is the older variant. Use http unless you know your server only supports SSE.

Agent runs return provider_key_decrypt_failed

Your BYOK provider key can’t be decrypted. Re-upsert it via POST /api/ai-provider-keys. See Billing model.

Workflows

{"type": "input"} block type returns 400 Unknown block type

The block registry has 10 canonical types: starter, agent, code, condition, general_api, platform_api, response, split, webhook, orchestration. input, output, and llm are not real. See Workflows concept.

/execute body uses input but the new field is variables

POST /api/workflows/{id}/execute accepts both variables (canonical) and input (legacy alias); the platform reads data.get("variables", data.get("input", {})). Prefer variables in new code.

/arm returns {"ok": true, "armed_until": ...} not {"webhook_id", "secret"}

The arm endpoint doesn’t return webhook credentials. The webhook_id and webhook_secret live in the webhook block’s config; fetch the workflow and read them from there. See Workflows reference.

Workflow /execute returns 429

The endpoint is rate-limited at 20 requests per minute per user. Back off with jitter and re-try. See Rate limits.

Storage

Public-bucket URLs ignore RLS

GET /storage/v1/object/public/{bucket}/{path} returns files without auth from any bucket where public: true. No RLS check happens on the public URL. If you don’t want files publicly fetchable, don’t put them in a public bucket; use a private bucket with signed URLs instead. See Storage policies.

File over 50MB returns 413

The per-request file size limit is 50MB. For larger files, use TUS resumable uploads at /storage/v1/upload/resumable. See Storage uploads.

Storage MIME allowlist isn’t real security

The MIME check looks at the Content-Type header your client sends, not the actual file contents. A malicious client can upload an executable with Content-Type: image/png. Treat the allowlist as UX; validate file contents server-side for security.

Realtime

postgres_changes subscription joined but no events arrive

The supabase_realtime publication isn’t set up by default on Powabase projects. You have to CREATE PUBLICATION supabase_realtime FOR TABLE public.your_table (or FOR ALL TABLES IN SCHEMA public). See Realtime model.

Realtime returns 403 TenantNotFound

Only happens on self-hosted deployments where Kong isn’t preserving the Host header. Powabase managed cloud handles this; if you see it from managed cloud, file a support ticket.

WebSocket closes after 60 seconds

You’re not sending heartbeats. Send a { topic: "phoenix", event: "heartbeat", payload: {}, ref: "..." } frame every 30 seconds. See Realtime subscriptions.

Postgres / pooler

prepared statement "..." does not exist

PgBouncer transaction mode breaks prepared statements. Disable them in your driver’s config; see Connection pooling for the per-driver table.

LISTEN/NOTIFY through the pooler doesn’t work

PgBouncer doesn’t maintain session-level state across statements. The LISTEN registers on one server connection; the next statement lands on a different one. Use Realtime instead.

SET statement_timeout = '5s' outside a transaction doesn’t apply

Same reason as LISTEN/NOTIFY: the SET sticks to one server connection. Use SET LOCAL inside a transaction, or pass settings via the connection string.

Username is <ref> not postgres

Powabase’s PgBouncer routes by database name, where the database is <ref> and the user is also <ref>. Coming from Supabase or a standalone Postgres, the muscle memory of postgres:postgres@host/postgres is wrong. Copy the URL from the Connect modal verbatim.

Webhooks (agentic, inbound)

Webhook returns 401 even with the right secret

Common cause: extra whitespace in the secret. The platform uses hmac.compare_digest which is byte-exact. Also check that you’re not sending Bearer with no token (see auth section).

Webhook returns 403 after firing once

Armed (single-use) webhooks fire exactly once per arm. Re-arm with POST /api/workflows/{id}/arm. For unlimited fires, deploy the workflow instead. See Workflows reference.

Webhook secret rotates on arm/deploy?

No. The webhook secret is fixed in the webhook block’s config when the block was created. Arming and deploying don’t rotate it. To rotate, update the block config via PUT /api/workflows/{id}/graph.

Billing

Agent run returns 402 insufficient_credits

Free-tier hard cap. The response includes balance, estimated_cost, and renews_at. Surface the renewal date to the user; don’t retry. See Billing model.

Agent run returns 503 billing service unreachable

Transient. Back off and retry. Don’t treat as a permanent error.

Confusingly named

”Webhook”: agentic vs database

  • Agentic webhooks: external systems triggering workflows. POST /api/webhooks/{id}.
  • Database webhooks: Postgres rows changing and Postgres calling out via pg_net.
See Glossary.

”Session”: agent vs auth

  • Agent session: a multi-turn conversation. In ai.agent_sessions.
  • Auth session: a signed-in user’s authenticated state. GoTrue-internal.

”Hook”: agent vs database trigger vs GoTrue

  • Agent hook: lifecycle callback (PreToolUse, approval, etc.).
  • Database trigger: Postgres function fired on row changes.
  • GoTrue hook: auth-layer extension point (not exposed on Powabase per-project today).

Debugging a failed run

When an agent run, workflow execution, or orchestration finishes with status: failed, the pieces you need are spread across several endpoints. Check them in this order.

Step 1: get the run record

GET /api/agents/runs/{run_id} returns the full run: status, error, usage, events (every SSE event that was persisted), tool_calls, reasoning_steps, retrieved_context, input_messages, output_messages. The error field is the highest-signal place to start.

Step 2: scan the events array for the first non-success event

The events array preserves the order of execution. The first event with a failure shape (type: "error", type: "tool_error", etc.) is usually the actual cause; everything after it is symptom. Common shapes:

Step 3: for retrieval issues, get the retrieved context

GET /api/sessions/{session_id}/runs/{run_id}/retrieved-context returns what context was injected into the LLM call. If the agent’s answer is “I don’t know,” compare this to what you expected. Empty or wrong-source context here is the root cause.

Step 4: for workflow runs, get the per-block logs

GET /api/workflows/{id}/executions/{execution_id}/logs returns the per-block logs for a workflow execution. A failed general_api block’s response body lives here, as do code-block stderr lines.

Step 5: rate limit?

If the failure is on the 22nd+ workflow execution within a minute, you’ve hit the in-memory rate limit (20/min per user). Wait 60 seconds or pace your calls. The error is "Rate limit exceeded".

Step 6: still stuck?

The Studio’s run-detail view renders all the above on one page and is usually faster than stitching the API calls yourself. For platform issues (suspected bug, weird state), file a support ticket with the run_id; the platform team can read the same data plus the internal logs.

Next steps

Glossary

The full terminology disambiguation list.

API conventions

The shared patterns across all /api/* endpoints. Once you know them, fewer pitfalls.

Migrating from Supabase

A specific subset of pitfalls for users coming from Supabase.

BaaS + AI cookbook

The recipes designed to keep you out of the worst of these.