HTTP 403

403 Forbidden

Client Error

The server understood the request and identified the caller, but refuses to authorize the action. Unlike 401, presenting different or refreshed credentials will not help. The identity is established and explicitly denied access. 403 is the right answer for permission failures, plan-gate violations, IP allow-list rejections, and CORS denials. Some APIs use 404 instead of 403 to avoid leaking the existence of a protected resource; this is a deliberate security trade-off rather than a misuse.

When does this happen?

Return 403 when the caller is authenticated but lacks the rights needed for the action. Typical triggers include a member-role user attempting an admin action, a free-plan account hitting a paid-only endpoint, an IP outside a corporate allow-list trying to reach an internal API, or a CORS preflight that rejects the request's Origin. The response body should describe the policy that denied the request without leaking unrelated information. For example, "plan upgrade required" or "role admin required" rather than dumping the entire ACL. 403 is the standard answer for write-protection on read-only resources (a viewer trying to delete a record). Be careful with org-scoped APIs: returning 403 instead of 404 for a resource that exists in another organization leaks the existence of that resource. Most enterprise APIs return 404 in that case to avoid the leak.

Common causes

How to fix it

Real-world examples

Member-role user clicks Delete on a record that only admins can remove.
Server returns 403 with {"error":"role admin required"}. UI disables the button and surfaces the message.
Free-plan user calls POST /v1/monitors which requires a paid plan.
Server returns 403 with {"error":"upgrade_required","upgrade_url":"/billing"}. Frontend redirects the user to the billing page.
Developer working from a coffee shop tries to access an admin endpoint that only allows office IPs.
Server returns 403 with {"error":"ip_not_allowed"}. Developer connects to VPN and the request succeeds.
Browser-based app makes a fetch from app.example.com to api.example.com but api.example.com does not include app.example.com in Access-Control-Allow-Origin.
Browser preflight returns 403 (or 200 without the right CORS headers) and the actual request never fires. DevTools shows a CORS error in the console.
User in the EU accesses a US-only video endpoint.
Server returns 403 with {"error":"geo_restricted"}. Client surfaces a message explaining the content is not available in their region.
Compromised account is suspended by an admin while the user has an open session.
Subsequent API calls return 403 with {"error":"account_suspended"}. UI forces a logout and redirects to a support page.

Debugging

How it differs from related codes

HTTP 401

401 means authentication failed. 403 means authentication succeeded but authorization failed. The user can fix 401 by providing credentials; 403 requires a permission change.

HTTP 404

404 means the resource is not found. Many APIs intentionally return 404 instead of 403 when a resource exists but belongs to a different organization, to avoid leaking existence.

HTTP 451

451 is a specialized 403 for legal restrictions. Content blocked by court order or regulation. Use 403 for ordinary permission denials and 451 only when the cause is a legal demand.

Related status codes

400 401 404 405 410 429

See HTTP 403 in your redirect chains?

Check your URLs with checkredirects.io