# Handling Authentication Errors with the Cursor SDK: 401s, Team vs User Keys

> Learn to handle Cursor SDK authentication errors like 401s and differentiate between team vs user keys. Resolve common credential and permission issues effectively.

- Repository: [Cursor/plugins](https://github.com/cursor/plugins)
- Tags: how-to-guide
- Published: 2026-05-25

---

**The Cursor SDK validates the `CURSOR_API_KEY` environment variable at startup, throwing distinct errors for missing credentials versus invalid tokens, while HTTP 401 responses indicate authentication failures and 403 responses signal permission mismatches between team and user keys.**

The cursor/plugins repository provides an SDK that authenticates via API keys to authorize plugin operations. Understanding how to handle Cursor SDK authentication errors—including the distinction between team keys for shared resources and user keys for individual identity—is essential for building robust plugins that fail gracefully when credentials are missing or insufficient.

## Token Types: Team Keys vs User Keys

The Cursor SDK supports two distinct authentication patterns that determine the scope and audit trail of API operations.

**Team keys** are created for organizations in the Cursor console. These keys authorize any member of the team to act on behalf of shared resources. Use team keys when a plugin runs as a shared entity, such as a CI/CD bot posting to a centralized Slack channel.

**User keys** are generated for individual accounts. These keys authorize only the specific user's identity and permissions. Use user keys when a plugin must act as a specific person, such as posting a comment that appears to come from the author.

Both token types populate the same `CURSOR_API_KEY` environment variable, but they differ in scope, auditing, and permission boundaries according to the [`cursor-sdk/skills/cursor-sdk/references/auth.md`](https://github.com/cursor/plugins/blob/main/cursor-sdk/skills/cursor-sdk/references/auth.md) documentation.

## Early Validation and Error Messages

The SDK enforces authentication requirements immediately upon initialization in orchestrator entry points.

When `CURSOR_API_KEY` is undefined, the CLI scripts in [`orchestrate/skills/orchestrate/scripts/cli/task.ts`](https://github.com/cursor/plugins/blob/main/orchestrate/skills/orchestrate/scripts/cli/task.ts) (line 136), [`inspect.ts`](https://github.com/cursor/plugins/blob/main/inspect.ts) (line 167), and [`forensics.ts`](https://github.com/cursor/plugins/blob/main/forensics.ts) (line 96) abort execution with the error:

```

CURSOR_API_KEY required; see cursor-sdk/references/auth.md

```

The model-probing helper at [`orchestrate/skills/orchestrate/scripts/tools/probe-models.ts`](https://github.com/cursor/plugins/blob/main/orchestrate/skills/orchestrate/scripts/tools/probe-models.ts) (line 94) logs a shorter variant:

```

CURSOR_API_KEY missing

```

This fail-fast approach in [`orchestrate/skills/orchestrate/scripts/core/agent-manager.ts`](https://github.com/cursor/plugins/blob/main/orchestrate/skills/orchestrate/scripts/core/agent-manager.ts) (line 228) prevents the SDK from attempting API calls without valid credentials.

## Handling HTTP 401 and 403 Responses

When a key is present but invalid or under-scoped, the Cursor SDK returns standard HTTP status codes that surface as JavaScript errors.

**401 Unauthorized** indicates an invalid token. The SDK throws an `AuthenticationError` or a generic `Error` containing "`401`" or "`Invalid token`". This occurs when the API key is malformed, expired, or revoked.

**403 Forbidden** indicates a permission mismatch. This error appears when authenticating with a user key that lacks access to team-level resources, or when the token's scope does not cover the requested operation.

The [`agent-manager.ts`](https://github.com/cursor/plugins/blob/main/agent-manager.ts) file (lines 862-907) demonstrates where the SDK interprets these HTTP statuses, converting them into actionable error messages before surfacing them to the user.

## Implementing Robust Error Handling

### Minimal Environment Check

Mirror the validation pattern found in the CLI entry points to fail fast when the environment variable is absent:

```ts
// my-plugin/src/main.ts
const apiKey = process.env.CURSOR_API_KEY;
if (!apiKey) {
  console.error(
    "CURSOR_API_KEY required; see cursor-sdk/references/auth.md"
  );
  process.exit(1);
}

```

This guard matches the implementation in [`task.ts`](https://github.com/cursor/plugins/blob/main/task.ts) (line 136) and prevents undefined tokens from reaching the SDK client.

### Differentiating Permission Errors

Inspect error status codes to provide specific guidance for credential issues:

```ts
import { Cursor } from "cursor-sdk";

async function runWithAuth() {
  const apiKey = process.env.CURSOR_API_KEY;
  if (!apiKey) {
    console.error("CURSOR_API_KEY required");
    process.exit(1);
  }

  try {
    const client = new Cursor({ apiKey });
    await client.performOperation();
  } catch (err: any) {
    if (err?.status === 401) {
      console.error("Authentication failed – check the CURSOR_API_KEY value.");
    } else if (err?.status === 403) {
      console.error(
        "Permission denied – the supplied key may be a user key lacking team scope."
      );
    } else {
      throw err;
    }
  }
}

```

This pattern aligns with the error handling logic in [`agent-manager.ts`](https://github.com/cursor/plugins/blob/main/agent-manager.ts), distinguishing between authentication failures and team-scope permission denials.

### Fallback Strategy for Team vs User Keys

When both key types might be available, implement a fallback chain that prefers team-level access but degrades gracefully to user-level credentials:

```ts
async function getClient() {
  const teamKey = process.env.CURSOR_TEAM_API_KEY;
  const userKey = process.env.CURSOR_USER_API_KEY;

  const tryKey = async (key: string | undefined) => {
    if (!key) return null;
    try {
      const client = new Cursor({ apiKey: key });
      await client.auth.whoAmI();
      return client;
    } catch (e) {
      return null;
    }
  };

  return (await tryKey(teamKey)) ?? (await tryKey(userKey));
}

```

This approach attempts the team key first for broader resource access, falling back to the user key only if the team key is unavailable or invalid.

## Summary

- **Team keys** authorize shared resources and team-scoped operations, while **user keys** restrict operations to individual identity and permissions.
- The SDK validates `CURSOR_API_KEY` immediately at startup in [`agent-manager.ts`](https://github.com/cursor/plugins/blob/main/agent-manager.ts) (line 228), throwing specific errors when the variable is missing.
- **HTTP 401** responses indicate invalid tokens, requiring credential verification.
- **HTTP 403** responses indicate insufficient scope, often occurring when a user key attempts to access team-level resources.
- Implement fallback logic to gracefully degrade from team keys to user keys when permissions are insufficient.

## Frequently Asked Questions

### What's the difference between a 401 and 403 error in the Cursor SDK?

A **401 Unauthorized** error means the API key is invalid, expired, or malformed, and the SDK cannot authenticate the request. A **403 Forbidden** error means the key is valid but lacks permission for the specific operation, such as using a user key to access team-wide resources. Check `err.status` to distinguish between these cases and provide appropriate user guidance.

### Can I use team and user keys interchangeably?

No. Team keys grant access to all resources belonging to the team and attribute actions to the team identity, while user keys only access resources visible to that specific user and attribute actions to the individual. Attempting to use a user key for team-level operations results in a 403 permission error, as implemented in the permission checking logic of [`agent-manager.ts`](https://github.com/cursor/plugins/blob/main/agent-manager.ts).

### How do I check if my API key is valid before making expensive API calls?

Call the `client.auth.whoAmI()` method after initializing the Cursor client. This lightweight validation endpoint returns user or team metadata without consuming significant resources. If this call throws a 401 error, the key is invalid; if it succeeds, the client is properly authenticated and ready for further operations.

### Where should I store my CURSOR_API_KEY in production?

Store the key in environment variables only, never committing it to source control. The `cursor/plugins` repository expects the variable via `process.env.CURSOR_API_KEY`, matching the pattern in [`probe-models.ts`](https://github.com/cursor/plugins/blob/main/probe-models.ts) (line 94). For applications requiring both key types, use `CURSOR_TEAM_API_KEY` and `CURSOR_USER_API_KEY` as distinct environment variables and implement fallback logic to select the appropriate credential.