# Authenticating to Palisade as an agent

> Palisade has two credentials. The REST API at https://api.palisade.email takes an API key, created by a person in the app and sent as HTTP Basic. The MCP server at https://api.palisade.email/mcp takes an OAuth access token from Palisade's authorization server, obtained with authorization code and PKCE through Palisade's public connector client. Neither is minted from an agent identity assertion.

## Discover

- Protected-resource metadata (RFC 9728): https://api.palisade.email/.well-known/oauth-protected-resource. It names the resource (`https://api.palisade.email/mcp`), the authorization server (`https://accounts.palisade.email/`) and every scope the server understands.
- Authorization-server metadata (RFC 8414): https://accounts.palisade.email/.well-known/oauth-authorization-server. It carries the authorization, token and revocation endpoints and `code_challenge_methods_supported` (S256).
- An unauthenticated call to either surface answers `401` with a `WWW-Authenticate` header: `Bearer resource_metadata="https://api.palisade.email/.well-known/oauth-protected-resource"` on MCP, and `Basic realm="palisade", Bearer realm="palisade"` on REST.
- The authorization-server metadata publishes no `agent_auth` block. There is no `identity_endpoint`, no `identity_types_supported` list, and neither the `identity_assertion` nor the `service_auth` identity type is offered; the ID-JAG grant (`urn:ietf:params:oauth:token-type:id-jag`) is not accepted at the token endpoint. Do not mint an assertion for Palisade; use one of the two methods below.

## Pick a method

| You want to | Use | Credential |
| --- | --- | --- |
| Call the REST API (domains, DNS records, reports, tasks, webhooks) | API key | `Authorization: Basic base64(<api_key>:)` plus `Palisade-Version` on every request |
| Drive Palisade from an MCP client (Claude, ChatGPT, Codex, Cursor and others) | OAuth, authorization code with PKCE | `Authorization: Bearer <access_token>` |
| Audit any domain's public DNS with no account | Nothing | `https://api.palisade.email/mcp/public` or `GET https://www.palisade.email/api/dns?domain=example.com` |

API keys are refused on the MCP endpoint, and OAuth tokens are not the credential for the REST API. A client that registers its own OAuth client, through dynamic registration or a client-ID metadata document, is refused with a `403` that names the client to use instead.

## Register

- **REST.** There is nothing for an agent to register. A signed-in person creates an API key under API and MCP at https://app.palisade.email/api-keys; the key is shown once. Headless signup exists for provisioning a new organization and its first key over the API: see https://developer.palisade.email/docs/guide#signup.
- **MCP.** There is nothing to register either. Every client uses Palisade's public connector client, id `ryKtuiPypMeYMoL1Cmhxtz6BYrEYQbLV`, which has no secret. Claude Code passes it with `--client-id` and a fixed callback port of 8765; stdio-only clients run `npx -y @palisadeemail/mcp`, which carries both.

## Claim

The claim step is the sign-in itself. The browser opens Palisade's sign-in, the person authenticates and picks an organization, and the resulting token is scoped to that organization and to the person's role in it, so a viewer cannot write through MCP. An API key is scoped the same way at creation: it belongs to one organization and carries an access level.

## Exchange

- **MCP.** Standard authorization code with PKCE (S256) against the endpoints in https://accounts.palisade.email/.well-known/oauth-authorization-server. The token's audience is `https://api.palisade.email/mcp`. Refresh tokens are issued to the connector client; the bridge and the major clients handle refresh themselves.
- **REST.** No exchange. The API key is the credential.

## Use the access_token

- **MCP.** `Authorization: Bearer <access_token>` on every request to `https://api.palisade.email/mcp` (Streamable HTTP).
- **REST.** `Authorization: Basic base64(<api_key>:)` (the key as the username, empty password) and `Palisade-Version: 2025-04-30` on every request to `https://api.palisade.email`. Rate limits are per key and reported in `RateLimit-*` headers. The OpenAPI document is at `https://api.palisade.email/swagger.json`.

## Errors

- `401` with a JSON body `{ "code": "unauthorized", "message": ..., "details": { "documentation": ... } }` and a `WWW-Authenticate` challenge means no credential, or a key that is invalid, rotated or revoked. Send `Accept: text/plain` to get the legacy status line instead.
- `403` on MCP means the token carries no organization (a self-registered client) or the account has no active organization; the message says which and where to fix it.
- `429` carries `Retry-After` and a JSON body naming the bucket that was exceeded.
- Every documented error shape is in the guide: https://developer.palisade.email/docs/guide#errors.

## Revocation

- **MCP.** Every authorised connection is listed under API and MCP at https://app.palisade.email/api-keys, where a person can revoke it; the authorization server also exposes `https://accounts.palisade.email/oauth/revoke` for a client to revoke its own token. A revoked connection answers `401` on its next call.
- **REST.** Keys are rotated or deleted at https://app.palisade.email/api-keys or over the API (`POST /api-keys/{id}/rotate`, `DELETE /api-keys/{id}`). A deleted or rotated key answers `401` immediately.
