403 Forbidden
The server knows who is calling and still refuses, because that user, key, or visitor lacks permission for this request.
Also appears as
- Failed to load resource: the server responded with a status of 403 (Forbidden)
- AxiosError: Request failed with status code 403
Prompt to fix it
A request in our app fails with "403 Forbidden". Find the failing request, read the response body, and tell me which system refused it: a Supabase policy or grant, an API key without access, a firewall rule, or private storage. Fix the permission at its source, for example with a row-level security policy that allows exactly this action for the right users. Keep RLS on, don’t use a service role key in the browser, and don’t widen access beyond what this feature needs.
Paste into Layerwise or any AI coding tool.
What it means
A 403 means the server understood the request and decided not to allow it. Signing in again as the same user gets the same answer, because the missing piece is a permission, a policy, or a rule on the server side.
Where that rule lives depends on the service. Supabase returns 403 when a table’s grants or row-level security policies block the signed-in user’s role. AI and other API providers return it for keys without access to a model, project, or region. Firewalls and bot protection, such as Cloudflare’s, return it when a security rule blocks the visitor, and S3-style storage returns it for private files requested without a signed URL.
The response body almost always names the rule. Read it in the Network tab before you change code: a Postgres error with code 42501, a Cloudflare block page, and a provider message about model access each need a different fix.
Common causes
- A Supabase row-level security policy or table grant doesn’t allow this action for the signed-in user’s role.
- The API key is valid but restricted: it lacks access to that model, project, or endpoint.
- A firewall rule, bot protection, or IP allowlist on the server blocks the request.
- The app requests a private file from storage without a signed URL.
- The server checks the request’s origin or a CSRF token, and the published domain isn’t on its allowed list.
How to fix it
- Open the failed request in the Network tab and read the response body. It usually names the policy, key scope, or rule.
- For Supabase, review the table’s policies for that action and role, and confirm the user is signed in when the request runs.
- For an API provider, check the key’s permissions and project in the provider’s dashboard.
- For a firewall or bot block, find the request in its security event log and adjust the rule that matched.
- For private files, create a signed URL on the server, and make public only the files meant to be public.
Related errors
All errors401 Unauthorized
The server couldn’t confirm who was calling: the request had no credentials, or its API key, session, or token was wrong or expired.
Permission denied for table
The database role making the request has no grant on that table, so Postgres refuses before row-level security even runs.
New row violates row-level security policy
Postgres refused to save a row because no row-level security policy on that table lets the current user write it.
Blocked by CORS policy
The browser hid a response from another site because that server never said your site is allowed to read it.
Build with an agent that checks its work
The Layerwise agent clicks through the live preview and reads the console, so it catches errors before you do.