# Extralingo agent authentication

This guide is for **MCP** (`POST https://www.extralingo.com/mcp`) and the **REST API** (`https://www.extralingo.com/api/v1`). Student website login is separate; agents use OAuth only.

## 1. Public vs signed-in

**No token:** search schools, destinations, filters, school profiles, live catalogues, price quotes, and `get_trip` by `trip_id`.

**OAuth required:** anything that saves, emails, books, or contacts a school on behalf of a student:

| Scope | MCP tools | REST (examples) |
|-------|-----------|-----------------|
| `trips` | `save_trip`, `email_trip` | `POST /trips`, `POST /trips/{id}/email` |
| `requests` | `request_quote`, `request_discussion`, `ask_school_question` | `POST /quotes`, `/discussions`, `/questions` |
| `bookings` | `create_booking` | `POST /bookings` |

Contact email is always the verified Extralingo account. Never tell the student a trip is confirmed or paid until the school confirms and Extralingo sends payment instructions.

## 2. Discover OAuth metadata

1. Call a gated tool or REST write without a token. You get **HTTP 401** and a **`WWW-Authenticate`** header.
2. Read `resource_metadata` from that header. It points at the protected-resource document for MCP: [https://www.extralingo.com/.well-known/oauth-protected-resource](https://www.extralingo.com/.well-known/oauth-protected-resource).
3. That document lists the authorization server and supported scopes. Fetch authorization-server metadata: [https://www.extralingo.com/.well-known/oauth-authorization-server](https://www.extralingo.com/.well-known/oauth-authorization-server) (token URL, registration, PKCE requirements).

## 3. Register a client

Use **dynamic client registration** (`POST https://www.extralingo.com/oauth/register`) or a **Client ID Metadata Document** if your platform supports it. Request only the scopes you need. Redirect URIs must match your agent exactly.

## 4. Authorize (PKCE + one-time email code)

1. Generate PKCE **code_verifier** and **code_challenge** (S256).
2. Send the user to `GET https://www.extralingo.com/oauth/authorize` with `response_type=code`, your `client_id`, `redirect_uri`, `code_challenge`, `code_challenge_method=S256`, `state`, `scope`, and `resource=https://www.extralingo.com/mcp`.
3. On Extralingo, the student signs in with an **emailed one-time code** (same OTP flow as the website, but on the OAuth consent screen). They accept terms for the requested scopes.
4. Extralingo redirects back with `code` and `state`. Verify `state`.

## 5. Token exchange and refresh

`POST https://www.extralingo.com/oauth/token` with `grant_type=authorization_code`, the `code`, `redirect_uri`, `client_id`, and `code_verifier`. Response includes `access_token`, optional `refresh_token`, and `scope`.

To refresh: `grant_type=refresh_token` with the same `client_id` and `resource` when required. If terms or privacy versions change, reconnect (new authorization) when the API returns an updated-terms error.

## 6. Call tools and REST with Bearer

MCP: JSON-RPC `tools/call` with `Authorization: Bearer {access_token}` (or send the batch in one POST to `/mcp`).

REST: same header on write routes under `https://www.extralingo.com/api/v1`. Optional **`Idempotency-Key`** header (or MCP `idempotency_key`) on create endpoints replays the same response within 24 hours when the payload matches.

## 7. Errors and recovery

| Situation | What to do |
|-----------|------------|
| 401 + `invalid_token` | Token missing, expired, or wrong resource. Repeat authorization or refresh. |
| 403 + `insufficient_scope` | Request more scopes and re-authorize. |
| MCP tool `isError` + `structuredContent.error` | Fix arguments (`field` hints missing params). Retry only when `retryable` is true. |
| JSON-RPC `-32602` on `tools/call` | Unknown tool name or malformed `params`. Fix the request shape. |
| 422 idempotency conflict | Same key was reused with a different body. Use a new key or replay the identical payload. |

Developer overview: [https://www.extralingo.com/en/developers](https://www.extralingo.com/en/developers). OpenAPI: [https://www.extralingo.com/openapi.json](https://www.extralingo.com/openapi.json).