# Authenticating with Wendily

Wendily lets an AI agent write trip itineraries on a user's behalf. Every credential acts for exactly one user, who either approves your client over OAuth or creates a key for you. There is no anonymous access and no service account: an agent always works as the person it is helping.

## Discover

- MCP server: https://wendily.com/mcp. A request that needs a user and has no valid credential gets `401` with `WWW-Authenticate: Bearer realm="wendily", resource_metadata="https://wendily.com/.well-known/oauth-protected-resource/mcp"`.
- Protected resource metadata (RFC 9728): https://wendily.com/.well-known/oauth-protected-resource/mcp
- Authorization server metadata (RFC 8414): https://wendily.com/.well-known/oauth-authorization-server (issuer `https://wendily.com`)
- REST API: https://sb.wendily.com/functions/v1/api, described by https://sb.wendily.com/functions/v1/api/openapi.json. Its `401` body is `{ "error": { "code": "unauthorized", "message": ... } }`.

## Pick a method

1. **OAuth 2.1 authorization code with PKCE**, for MCP clients that can open a browser for the user: chat apps such as ChatGPT and Claude, and desktop clients that listen on a loopback port. This is the right choice whenever your client supports MCP connectors.
2. **A key the user creates**, for everything else: a REST client, a script, or an MCP client without OAuth. The user creates it on the Connections page (https://app.wendily.com/connections) and gives it to you.

Both produce the same kind of credential, a bearer token that reaches the MCP server and the REST API and nothing else. Wendily does not support client credentials, refresh tokens, `agent_auth`, or identity assertions (ID-JAG); an agent cannot obtain a credential without the user.

## Register

OAuth clients register dynamically (RFC 7591), with no approval step:

```http
POST https://wendily.com/oauth/register
Content-Type: application/json

{ "client_name": "Your agent", "redirect_uris": ["http://127.0.0.1:33418/callback"] }
```

The response is `201` with a `client_id`, `token_endpoint_auth_method: "none"` (a public client; PKCE binds the code to you), and `scope: "trips"`. Redirect URIs must be on Wendily's allowlist: `https://` on chatgpt.com, claude.ai, claude.com, agent.meta.ai, muse.ai, or platform.muse.ai, or a loopback address (`localhost`, `127.0.0.1`, `[::1]`). Anything else is refused with `invalid_redirect_uri`; write to team@wendily.com to support another app. Your `client_name` is shown to the user but proves nothing about who you are; the redirect decides who can receive a code.

Key users skip this step.

## Claim

Send the user to the authorization endpoint with PKCE:

```
GET https://wendily.com/oauth/authorize?response_type=code&client_id=…&redirect_uri=…&code_challenge=…&code_challenge_method=S256&state=…
```

`code_challenge_method` must be `S256`. The user signs in to Wendily (by email link) and approves your client by name on a consent page. Wendily then redirects to your `redirect_uri` with `code` and your `state`, or with `error=access_denied` if they decline.

With a key, the user claims it themselves: they add a connection on the Connections page, which shows the key (`trips_sk_` followed by 64 hex characters) once, and paste it to you.

## Exchange

```http
POST https://wendily.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=…&client_id=…&redirect_uri=…&code_verifier=…
```

The response is `{ "access_token": "trips_sk_…", "token_type": "Bearer", "scope": "trips" }`. A code is valid for two minutes after the user approves, and works once. Presenting a code a second time fails and also revokes the token it already produced, since a replayed code means it may have been intercepted. There is no refresh token: the access token does not expire until it is revoked.

## Use the access_token

Send it on every request, to either surface:

```http
Authorization: Bearer trips_sk_…
```

- MCP: https://wendily.com/mcp (Streamable HTTP, JSON-RPC)
- REST: https://sb.wendily.com/functions/v1/api, for example `GET https://sb.wendily.com/functions/v1/api/me` to see whose account the token belongs to

The token can read and write the user's own trips and the trips shared with them, with the role they were given. Start with `POST /trips/start` (the `start_trip` tool) before asking the user about their trip.

## Errors

- `401` from the REST API, or `401` with a `WWW-Authenticate` challenge from the MCP server: the token is missing, malformed, or revoked. Do not retry with the same token; ask the user to reconnect.
- `403 forbidden`: the token is valid but the user's role on that trip does not allow the change (a viewer editing, or a non-owner managing members).
- Token endpoint errors (`400`, `{ "error", "error_description" }`): `invalid_request` for missing or malformed parameters, `invalid_grant` for an unknown, expired, used, or mismatched code or verifier, `unsupported_grant_type` for anything but `authorization_code`.
- Registration errors: `invalid_client_metadata` for a body that is not a JSON object, `invalid_redirect_uri` for a redirect off the allowlist.

## Revocation

The user revokes a credential by disconnecting it on the Connections page (https://app.wendily.com/connections); every OAuth connection and every key appears there by name. Revocation takes effect on the next request. Wendily has no revocation endpoint for clients: if you no longer need access, tell the user they can disconnect you.
