Troubleshooting

Authentication and permission errors

Updated 110 views

Two status codes cover nearly all of these, and they mean different things.

401 Unauthorized — the key is missing, malformed, revoked, or is the wrong kind of key. 403 Forbidden — the request was understood, but it is not allowed. Almost always one of two causes: the origin is not in the project's Allowed Origins, or the request carried only a project id while the hosted portal is off.

Check the key reaches the SDK

The most common cause is not a wrong key but an undefined one. An environment variable that did not load produces a request with no usable key.

TypeScript
console.log('Key present:', Boolean(process.env.KOOKEE_API_KEY));
console.log('Key length:', process.env.KOOKEE_API_KEY?.length);

Never log the key itself — logs get shipped, shared and pasted into issues.

Usual reasons a variable is undefined:

  • The .env file is not in the directory the process starts from

  • The framework needs a specific prefix (VITE_, NEXT_PUBLIC_) for client-side access

  • The dev server was not restarted after the file changed

  • Production has no environment variables configured at all — a very common first deploy failure

Verify the key itself

Bash
curl -i -H "Kookee-API-Key: YOUR_API_KEY" \
  https://api.kookee.dev/v1/health

A success confirms the key is valid. A 401 means it is not — create a new one on Connect → API keys.

Paste carefully. A trailing newline, a truncated copy, or an inline comment accidentally included after the value (KEY="kk_…" # local) all produce exactly this failure.

401 with a key that looks right: is it an MCP token?

Kookee has two kinds of credential. The API key (Connect → API keys, starts with kk_) is for the SDK and REST API. The MCP token (Connect → MCP server, starts with kkm_) is only for AI assistants talking to the MCP server.

The public API rejects MCP tokens with 401 API_KEY_INVALID. If the value in your app starts with kkm_, that is the problem — create an API key instead.

Errors from the MCP server

An assistant that cannot connect usually reports one of these:

Code

What it means

What to do

MCP_PERSONAL_TOKEN_REQUIRED

The client sent an API key, or a shared MCP key from before v1.12.0

Create a personal token on Connect → MCP server and reconnect with it

MCP_TOKEN_INVALID

The token was revoked or mistyped, or it no longer belongs to an active member of the project

Create a new token

RATE_LIMIT_EXCEEDED

Too many requests in the last minute

Wait a minute and retry

MONTHLY_API_LIMIT_EXCEEDED

The project owner's monthly request quota is used up

Upgrade, or wait for the next month

MCP_TOKEN_INVALID deliberately does not say which of those it was. If the token still appears on the MCP server page, check that you copied all of it.

An assistant that says a tool does not exist, such as create_entry, is usually connected with a read-only token: those are not offered write tools. Create a token with Read and write access if it needs to change anything.

Errors when connecting by signing in

Apps that connect by signing in (Claude, ChatGPT, VS Code and Claude Code) first send you to a Kookee page. It can show:

  • This sign-in link has expired — the page was open for too long. Start connecting again from the app.

  • This link is not valid — the address was changed or cut short. Start again from the app.

  • Guest accounts cannot connect apps — create an account, or sign in to one, and try again.

Once connected, an assistant may report one of these codes. It usually corrects itself:

Code

What it means

What to do

MCP_PROJECT_REQUIRED

You belong to several projects and the request did not say which

Name the project in your request

MCP_PROJECT_NOT_FOUND

None of your projects has that slug

Use one of the projects the error lists

MCP_NO_PROJECTS

The account is not a member of any project

Create a project, or accept an invitation

An app that used to work and now asks you to connect again was disconnected: under Security → Connected apps in your account settings, or by a password change or reset. Connect it again.

403: allowed origins

If your project has allowed origins set, browser requests from any domain not on the list are refused with 403 ORIGIN_NOT_ALLOWED. Typical triggers:

  • A new domain or subdomain (www. versus the bare domain, a preview deployment, localhost)

  • The request works from curl or from your server but fails in the browser — server requests carry no Origin header and skip the check entirely

Add the domain in project settings, or clear the list while developing.

The chat widget is rejected on a new site

The AI chat widget runs in the browser, so it is subject to the same origin check. When your project has Allowed Origins set and you install the widget on a new website, add that website's domain to the project's Allowed Origins in project settings, or its chat requests are rejected.

This is the usual reason a widget that works on one site shows an error on another.

403: portal not enabled

A request that carries only a project id — new Kookee({ projectId }), or the Kookee-Project-Id header — works only while the project's hosted portal is enabled. With the portal off, it is refused with 403 PORTAL_NOT_ENABLED. Enable the portal, or switch to an API key (apiKey) and restrict it to your site's domains with the project's Allowed Origins.

The same code comes back for a project id that does not exist (a value that is not a UUID at all gets 400 VALIDATION_ERROR), so if the portal is on, compare the id with the one in your project settings first.

An empty apiKey throws immediately

TypeScript
const kookee = new Kookee({ apiKey: '' }); // throws

The constructor rejects an empty key rather than failing later on the first request. If you see this at startup, the variable is empty rather than merely wrong.

Errors in the browser

A 401 from client-side code, when the server works, usually means the key was never bundled. Check the prefix requirement — VITE_ for Vite, NEXT_PUBLIC_ for Next.js — and rebuild. Environment variables are inlined at build time, so a variable added after the build is not in the bundle.

Handling it in code

TypeScript
import { KookeeApiError } from '@kookee/sdk';

try {
  return await kookee.blog.list();
} catch (error) {
  if (!(error instanceof KookeeApiError)) throw error;

  if (error.status === 401) {
    // Configuration problem — fail loudly, do not degrade silently
    throw new Error('Kookee API key is invalid or missing');
  }

  if (error.status === 403) {
    // ORIGIN_NOT_ALLOWED or PORTAL_NOT_ENABLED — see the two 403 sections above
    throw new Error(`Kookee refused this request: ${error.code}`);
  }

  throw error;
}

Do not fall back to cached or empty content on a 401. A rate limit or a 500 is worth degrading around; an auth failure is a deploy problem, and hiding it means discovering weeks later that a page has been silently empty.

After a leak

Revoke the key on Connect → API keys and create a replacement. Revocation is immediate, so deploy the new key first if you want to avoid downtime.

A leaked API key is low severity — it only reaches already-public content — but rotate it so usage stays attributed correctly. A leaked MCP token should be revoked at once on Connect → MCP server; it reads your drafts, and a token with write access can publish content.

Still stuck?

Email admin@kookee.dev with your project slug, the status code, the code from the error, and whether the curl health check above succeeds.

Do not send your API key — not in an email, a screenshot or a log paste. We never need it and will never ask for it. If a key has already been exposed somewhere, revoke it first, then get in touch.

Was this article helpful?