API returns 401 Unauthorized

You have a valid token but every request is rejected.

Symptom: curl, Python, .NET, or TypeScript SDK calls return 401 Unauthorized with body {"error":"not signed in"} or {"error":"session expired"}. The dashboard works fine in the browser.

Likely causes (in probability order):

The hosted backend originally only checked req.cookies.mnueron_session for the bearer token. The fix that lets Authorization: Bearer mnu_... work landed in commit <TBD-after-merge> — if your mnueron.com is running an older build, no header-based auth will succeed.

Verify: hit https://www.mnueron.com/api/health (no auth). Response should be {"ok":true,"service":"mnueron",...}. The Vercel deployment ID is in response headers — confirm it's a build after the fix landed.

Fix: redeploy the latest main from Vercel. No DB changes needed.

2. RLS is blocking api_tokens

If row-level security is enforced on api_tokens, the server's SELECT … FROM api_tokens WHERE token_hash = $1 returns zero rows even when the token exists.

Verify: in Supabase SQL Editor —

SELECT c.relname, c.relrowsecurity AS rls
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public' AND c.relname = 'api_tokens';

Should show rls = false. If it's true, run:

ALTER TABLE api_tokens DISABLE ROW LEVEL SECURITY;

3. The token was revoked or expired

Verify: in SQL Editor —

SELECT id, prefix, name, expires_at, created_at
FROM api_tokens
WHERE prefix = LEFT('your_token_first_8_chars', 8);

expires_at should be NULL or in the future. If the row is missing, the token was revoked. Issue a fresh one at /account-settings/tokens.

4. The token has invisible chars from clipboard / autofill

Some browsers / clipboard tools introduce trailing whitespace or smart-quote substitutions. Verify the token sent by the client by opening DevTools → Network → click the request → "Headers" → check the literal Authorization value matches what you copied character-by- character.

Last updated 2026-05-17edit