# Authentication: API keys and OAuth 2.1

FreshlyCited accepts two kinds of credentials. Use an **API key** for scripts, CI and MCP clients where you can set a header. Use **OAuth** for connectors like Claude.ai and ChatGPT, where you sign in instead of pasting a secret.

| | API key | OAuth 2.1 |
| --- | --- | --- |
| Best for | Scripts, REST, Claude Code, Cursor, VS Code | Claude.ai, ChatGPT, Claude Desktop connectors, any MCP client that supports OAuth |
| Works on | REST API and MCP | MCP only (`/mcp`) |
| Acts as | The key (its workspace and role) | You, in the workspace you pick on the consent screen |
| Lifetime | Until revoked | Access token 1 hour, refresh token 30 days (rotated) |
| Revoke | **API keys** page | **API keys › Connected apps**, or `/oauth/revoke` |

## API keys

Create a key under **API keys** in the app (admins and owners). The secret starts with `ipk_` and is shown once. Send it as a bearer token:

```bash
curl https://freshlycited.com/api/v1/me -H "Authorization: Bearer ipk_YOUR_KEY"
```

`X-Api-Key: ipk_…` works too. A key belongs to one workspace (your agency or a client) and sees only that workspace and the clients under it. Its role decides what it can do: `client_viewer` keys are read-only. Each key can have its own [rate limits and daily credit cap](https://freshlycited.com/docs/rate-limits).

## OAuth 2.1 for connectors

FreshlyCited implements the MCP authorization spec, so compliant clients discover everything from the server URL:

1. The client calls `https://freshlycited.com/mcp` without a token and gets **401** with `WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource/mcp", scope="mcp"`.
2. It reads the [protected resource metadata](https://freshlycited.com/.well-known/oauth-protected-resource/mcp) (RFC 9728), which names FreshlyCited as the authorization server.
3. It reads the [authorization server metadata](https://freshlycited.com/.well-known/oauth-authorization-server) (RFC 8414).
4. It identifies itself with a **Client ID Metadata Document** (an https `client_id` URL) or registers through **dynamic client registration** at `/oauth/register` (RFC 7591).
5. It sends you to `/oauth/authorize` with **PKCE S256** and `resource=https://freshlycited.com/mcp` (RFC 8707).
6. You sign in with your normal FreshlyCited login and see the consent screen: pick the **workspace** and the **access level**.
7. The client exchanges the code at `/oauth/token` and calls the MCP server with `Authorization: Bearer fca_…`.

### Scopes

| Scope | Access |
| --- | --- |
| `mcp` | Full access with your role in the chosen workspace: push links, run checks (spends credits), manage clients if your role allows |
| `mcp:read` | Read-only: pushes, checks, results, credits and reports. Tools that change anything return 403 |

### Tokens

- Access tokens (`fca_…`) last **1 hour** and only work on `/mcp`; they're bound to that resource and rejected by the REST API.
- Refresh tokens (`fcr_…`) last **30 days** and rotate on every use. If an old refresh token is used again, the whole connection is revoked for safety.
- The authorization response includes `iss` (RFC 9207). Redirect URIs must match exactly, except loopback URIs (`http://127.0.0.1` / `localhost`), where the port may differ.
- Revoke with `POST /oauth/revoke` (RFC 7009), or disconnect the app under **API keys › Connected apps**. Removing you from the workspace also cuts the connection off.

## Errors

| Status | Meaning |
| --- | --- |
| 401 | No credentials, an unknown or revoked key, or an expired access token. MCP responses include `WWW-Authenticate` so OAuth clients can sign in again. |
| 403 | Your role (or a read-only connection) doesn't allow this, or the account is waiting for approval |
| 429 | [Rate limit](https://freshlycited.com/docs/rate-limits) reached: wait for `Retry-After` seconds |


---
Source: https://freshlycited.com/docs/authentication
