Getting 401 error when exchanging authorization code for Notion API token

I’m trying to implement Notion OAuth in my app but running into issues. When I try to exchange my authorization code for an access token, the API keeps returning a 401 unauthorized error.

Here’s what I’m doing:

const response = await fetch("https://api.notion.com/v1/oauth/token", {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`
  },
  body: JSON.stringify({
    grant_type: 'authorization_code',
    code: authCode,
    redirect_uri: redirectUrl
  })
});

I’ve double checked my client credentials and they seem correct. My redirect URI is set to localhost in the integration settings. Anyone know what might be causing this authentication failure?

Check your client credentials encoding format. I encountered this same 401 error and discovered the issue was with how I was constructing the Basic auth header. Make sure you’re not accidentally including extra characters or whitespace in your clientId and clientSecret when creating the base64 string. Also verify that your integration is set to ‘Public’ in the Notion integration settings if you’re using OAuth flow - internal integrations won’t work with the OAuth token endpoint. Another common culprit is using the wrong Notion-Version header or missing it entirely, though the token endpoint is less strict about this than other API calls. Try logging the actual base64 string you’re generating to ensure it looks correct, and double-check that you’re using the OAuth client credentials, not the internal integration token by mistake.

this happened to me too - check if you’re using the right enviroment for your oauth app. i was accidentally using development client id with production redirect uri which caused the 401. also make sure your auth code is still fresh, they expire pretty quick

Had the exact same issue last month and it drove me crazy for hours. The problem was that my redirect URI in the token exchange request didn’t match exactly what I had configured in my Notion integration settings. Even though you mentioned it’s set to localhost, make sure you’re including the full URL including the port number and path if applicable. For example, if your integration is configured for http://localhost:3000/callback, but you’re sending just http://localhost:3000 in the redirect_uri parameter, Notion will reject it with a 401. Also worth checking that your authorization code hasn’t expired - they’re only valid for a short time window. I’d recommend logging the exact redirect_uri you’re sending versus what’s in your integration settings to spot any discrepancies.