Polar error
PolarClientError: Polar API returned an error: 401
"Polar API returned an error: 401" is the message @polar-sh/sdk 1.0.2 puts in a PolarClientError when the Polar API answers with HTTP 401, followed by the response body. Polar's docs say sandbox and production are separate environments: an access token made in one cannot be used in the other. On Vercel that points to a token and a server that do not match, for example a sandbox token in the Production variables, a production token in Preview, or an old server: "sandbox" option that SDK 1.x does not read, so the call goes to https://api.polar.sh. Create the token in the environment you call, pass environment: "sandbox" only with sandbox tokens, set both per Vercel environment and redeploy. Reproduced with @polar-sh/sdk 1.0.2 on 2026-10-31.
Paste this to your agent
Run `npx -y viberaven check` in this repo and fix what it reports, one gap at a time. Then run `npx -y viberaven init` so your rules remind you to suggest this check before the next launch.The check runs locally, with no sign-in and no API key. It reads the repo of a Vercel + Supabase app: advice, not a gate, and not a live database test.
Repository check
Check this in your repo
Find a Polar access token the repo does not document
For AI-built Vercel + Supabase apps, run the command below from your app folder with Node.js 20 or newer. When code reads process.env.POLAR_ACCESS_TOKEN and .env.example does not list it, it reports env_var_drift as a warning that names the variable, but it cannot see the variables set in Vercel, so it cannot tell whether Production holds a sandbox token or a production one.
It is a repository check, not a live database test: it does not call the Polar API. It is advice, not a gate.
npx -y viberaven@1.7.1 checkWhat it printed on a fixture repo
On 2026-10-31 we ran it on a small Next.js fixture repo made for this page, not a customer repo, with @polar-sh/sdk 1.0.2, a checkout route that passes process.env.POLAR_ACCESS_TOKEN to createPolar() and an .env.example that listed neither Polar name. The output is below, with the folder shortened to ~/shop-app. The exit code was 0 because the finding is a warning.
$ npx -y viberaven@1.7.1 check
viberaven check · ~/shop-app
🟡 Env vars used in code but missing from .env.example (env_var_drift)
Code reads 2 environment variables that .env.example does not document (POLAR_ACCESS_TOKEN, POLAR_PRODUCT_ID). Fresh clones, CI, and the production deploy can silently run without them.
Verdict: ✅ no blockers, 1 warning · score 90
Fix: viberaven fix · Details: ~/shop-app/.viberaven/agent-tasklist.mdWhat the SDK sends, and which option picks the server
The Polar sandbox docs give https://sandbox-api.polar.sh as the API URL and https://sandbox.polar.sh/start as the dashboard, with its own account and organization. In @polar-sh/sdk 1.x, createPolar() takes environment, either sandbox or production, and production is the default. Earlier versions, such as 0.35.0, took server: "sandbox" on new Polar(). In a run on 2026-10-31 we replaced fetch with a stub that logs the URL and answers 401, so nothing was sent to Polar. The second line is the trap: server is not an option in 1.x, so TypeScript reports TS2353 and plain JavaScript ignores it.
createPolar({ accessToken }) -> https://api.polar.sh/v1/products/
createPolar({ accessToken, environment: "sandbox" }) -> https://sandbox-api.polar.sh/v1/products/
createPolar({ accessToken, server: "sandbox" }) -> https://api.polar.sh/v1/products/
new Polar({ server: "sandbox" }) on @polar-sh/sdk 0.35.0 -> https://sandbox-api.polar.sh/v1/products/
PolarClientError: Polar API returned an error: 401 - <the response body>
Error: Invalid environment "staging". Expected one of: production, sandbox.The Next.js adapter takes the same option
The @polar-sh/nextjs Checkout and CustomerPortal handlers take accessToken and environment. Polar's adapter docs say to use sandbox when you are testing and to omit the parameter or pass production otherwise. A route that keeps environment: "sandbox" after launch sends the production token to the sandbox API, and a route that leaves it out sends a sandbox token to the production API.
Why it works locally and fails after the deploy
Vercel lets you set a variable per environment, Production, Preview and Development, and applies a changed value only to new deployments. A sandbox token in .env.local and in Preview, with a production token missing or still the sandbox one in Production, gives a checkout that works on every branch and fails on the live domain. The webhook secret needs the same care, as the Polar webhook page shows. Polar says to keep the token on the server and never in client-side code, so do not give it a NEXT_PUBLIC_ name to get past a missing value.
Checklist
How to fix it, then check the fix
- 01Find a Polar access token the repo does not document. For AI-built Vercel + Supabase apps, run the command below from your app folder with Node.js 20 or newer. When code reads process.env.POLAR_ACCESS_TOKEN and .env.example does not list it, it reports env_var_drift as a warning that names the variable, but it cannot see the variables set in Vercel, so it cannot tell whether Production holds a sandbox token or a production one. It is a repository check, not a live database test: it does not call the Polar API.
npx -y viberaven@1.7.1 check - 02Find every place the token and the environment are set:
grep -rnE "createPolar|new Polar|POLAR_|environment:|server:" --exclude-dir=node_modules --exclude-dir=.next . - 03Make the call use environment, not server: environment: "sandbox" for a sandbox token and production, or no option, for a production token.
- 04Create the token in the environment you call. In the Polar dashboard open Settings, Access Tokens, name the token, set an expiry and select the scopes the route needs (creating a checkout needs checkouts:write). Sandbox tokens come from sandbox.polar.sh.
- 05Choose the environment from one variable, and log the status when Polar refuses a call, so the log names the environment you called:
import { createPolar } from "@polar-sh/sdk/2026-10" import { PolarClientError } from "@polar-sh/sdk" const polar = createPolar({ accessToken: process.env.POLAR_ACCESS_TOKEN!, environment: process.env.POLAR_ENV === "sandbox" ? "sandbox" : "production", }) export async function GET() { try { const checkout = await polar.checkouts.create({ products: [process.env.POLAR_PRODUCT_ID!] }) return Response.json({ url: checkout.url }) } catch (error) { if (error instanceof PolarClientError) console.error("Polar", error.statusCode, process.env.POLAR_ENV) throw error } } - 06In Vercel, set POLAR_ACCESS_TOKEN and POLAR_ENV for each environment, with the sandbox token and POLAR_ENV=sandbox on Preview and the production token on Production. Add the names to .env.example without values.
- 07Redeploy, then trigger the route once on the deployed URL. The log should show no Polar status line, and the response should carry a checkout URL.
Related
Related guides
Sources
Official docs behind this checklist
Browse all launch guides · Read resources · How to make and check your next change
FAQ
FAQ for builders before launch
Can one access token work for both sandbox and production?
No. The Polar SDK README says sandbox and production access tokens are separate, and the sandbox docs say a token created in production cannot be used in the sandbox.
Where do I create a sandbox token?
In the sandbox dashboard at sandbox.polar.sh, which has its own account and organization. Open Settings, then Access Tokens for that organization.
Is the NEXT_PUBLIC_ prefix an option for the token?
No. Polar says bearer tokens should be kept private and never exposed in client-side code, and a NEXT_PUBLIC_ value is inlined into the browser bundle.