Edge Function 401 error response

Last edited: 4/3/2026

A 401 response from an Edge Function means either:

Quick triage#

Check the response body returned by the request

Case 1: "Invalid JWT" or "Missing authorization header"#

1
{ "code": 401, "message": "Invalid JWT" }
1
{ "code": 401, "message": "Missing authorization header" }

Both of these messages come from the legacy auth verification check

Go to: Built-in JWT check failures

Case 2: Custom message or empty body#

If the response body contains a message you coded, or nothing at all, then your function code did execute and returned a 401 itself.

Go to: Your function returned a 401

Case 3: Not sure#

Run this query in Log Explorer to classify recent 401s:

1
select
2
cast(timestamp as datetime) as timestamp,
3
req.pathname as function_name,
4
case
5
when metadata.execution_id is not null then 'your_code_returned_401'
6
when metadata.execution_id is null
7
and (
8
new_auth.prefix is not null
9
or legacy_payload.algorithm != 'HS256'
10
) then 'incompatible_keys'
11
when metadata.execution_id is null
12
and (
13
(legacy_auth_data.invalid is not null or new_auth.error is not null)
14
or legacy_payload.algorithm = 'HS256'
15
) then 'invalid_key'
16
when metadata.execution_id is null
17
and legacy_auth_data is null
18
and new_auth.prefix is null then 'missing_auth_header'
19
end as cause
20
from
21
function_edge_logs
22
-- unnesting metadata
23
cross join UNNEST(metadata) as metadata
24
cross join UNNEST(metadata.request) as req
25
cross join UNNEST(metadata.response) as res
26
-- unnesting auth details
27
left join UNNEST(req.sb) as sb
28
left join UNNEST(sb.apikey) as apikey
29
left join UNNEST(apikey.authorization) as new_auth
30
left join UNNEST(sb.jwt) as legacy_jwt
31
left join UNNEST(legacy_jwt.authorization) as legacy_auth_data
32
left join UNNEST(legacy_auth_data.payload) as legacy_payload
33
where res.status_code = 401
34
order by timestamp desc
35
limit 50;

Depending on the output, you can use this table to find the appropriate debugging section:

ValueGo to
your_code_returned_401Your function returned a 401
incompatible_keysIncompatible key format
invalid_keyInvalid key
missing_auth_headerMissing Authorization header

Your function returned a 401#

Your function ran, and somewhere in your code, its logic returned a 401.

Example:

1
return new Response(JSON.stringify(data), {
2
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
3
status: 401, // <-- you set this
4
})

How to fix:

  1. Search your function code for 401. Look for explicit status codes on Response objects.
  2. Trace the condition that triggered it. If you're interacting with a third-party API in your code, that service may be returning 401 that you're forwarding in the response object.
  3. Add logging before the return so future occurrences leave a trace:
1
console.error('Returning 401 - reason:', reason)

See: Error handling in Edge Functions


Built-in JWT check failures#

Supabase Edge Functions have a legacy auth verification check that runs before your code. When it fails, your function never executes, and you get a 401 with "Invalid JWT" or "Missing authorization header" directly from the platform.

The subsections below cover specific failure modes.

Incompatible key format#

Your project uses the new asymmetric keys for authentication. However, the legacy auth verification check only understands the legacy format.

Fix: Disable the built-in JWT check using one of the below methods and optionally handle auth in your function code

Invalid key#

The built-in check is enabled and the key you sent doesn't match your project's keys.

Fix (recommended): Disable the built-in check using the steps in Incompatible key format.

Fix (alternative): If you want to keep the built-in check, ensure you're sending a valid key. Use one of your legacy API keys with the Supabase client library when making your request.

1
const supabase = createClient('https://xyzcompany.supabase.co', 'anon-key-or-service_role-key')

Missing authorization header#

The built-in check is enabled but your request has no Authorization header at all.

If you're using a Supabase client library, the header is added automatically. If you're calling the function from an external client (cURL, fetch, etc.), you need to supply it:

1
curl -L -X POST 'https://PROJECT_REF.supabase.co/functions/v1/hello-world' \
2
-H 'Authorization: Bearer YOUR_ANON_OR_SERVICE_ROLE_KEY' \
3
--data '{"name":"Functions"}'

Alternatively, you can disable the built-in check entirely (see Incompatible key format).


Additional resources#

Still stuck?#