> ## Documentation Index
> Fetch the complete documentation index at: https://developers.everhour.com/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth 2.1 reference

> Endpoints, parameters, token lifetimes, and security rules for Everhour's OAuth 2.1 authorization flow.

This page documents every OAuth endpoint and its parameters. For a walkthrough of how the pieces fit together, see [OAuth 2.1](/oauth). All endpoints are served from the API base URL:

```
https://api.everhour.com
```

## Discovery

Clients don't hardcode endpoints — they read them from the server's metadata. An unauthenticated request to a protected resource returns `401` with a `WWW-Authenticate` header naming the protected-resource metadata URL.

| Method | Path                                        | Standard                                                  | Returns                                          |
| ------ | ------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------ |
| `GET`  | `/.well-known/oauth-protected-resource`     | [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) | The authorization server for the API             |
| `GET`  | `/.well-known/oauth-protected-resource/mcp` | [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) | The authorization server for the `/mcp` resource |
| `GET`  | `/.well-known/oauth-authorization-server`   | [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414) | Endpoint URLs and supported capabilities         |

The authorization-server metadata tells the client everything it needs:

```json theme={null}
{
  "issuer": "https://api.everhour.com",
  "authorization_endpoint": "https://api.everhour.com/oauth/authorize",
  "token_endpoint": "https://api.everhour.com/oauth/token",
  "registration_endpoint": "https://api.everhour.com/oauth/register",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"]
}
```

## Register a client

Register your app with Dynamic Client Registration ([RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)). The request needs no authentication, and there is nothing to create in Everhour beforehand.

```http theme={null}
POST /oauth/register
Content-Type: application/json
```

| Field           | Type   | Required | Description                                                                                                                                                        |
| --------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `client_name`   | string | Yes      | A human-readable name shown on the consent screen.                                                                                                                 |
| `redirect_uris` | array  | Yes      | One or more redirect URIs. Each must use `https`, `http` on a loopback host (`127.0.0.1` / `localhost`), or a private-use scheme (for example `myapp://callback`). |

The response returns a `client_id`. Everhour issues **public** clients only — there is no `client_secret`.

```json theme={null}
{
  "client_id": "a1b2c3d4-...",
  "client_name": "My integration",
  "redirect_uris": ["https://myapp.example.com/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "none"
}
```

## Authorize

Send the user to the authorization endpoint to sign in and approve access. Generate a PKCE code verifier and its `S256` challenge first — `plain` challenges are rejected.

```http theme={null}
GET /oauth/authorize
```

| Parameter               | Required    | Description                                                                                                                                                                     |
| ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `response_type`         | Yes         | Must be `code`.                                                                                                                                                                 |
| `client_id`             | Yes         | The `client_id` from registration.                                                                                                                                              |
| `redirect_uri`          | Yes         | One of the URIs registered for the client.                                                                                                                                      |
| `code_challenge`        | Yes         | The PKCE challenge derived from your verifier.                                                                                                                                  |
| `code_challenge_method` | Yes         | Must be `S256`.                                                                                                                                                                 |
| `state`                 | Recommended | An opaque value echoed back on the redirect. Verify it to protect against CSRF.                                                                                                 |
| `resource`              | Optional    | Narrows the token's audience ([RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707)). Omit for a full-API token; pass `https://api.everhour.com/mcp` for an MCP-only token. |

After the user clicks **Allow**, Everhour redirects to your `redirect_uri` with a one-time `code` (and your `state`). If the user declines, it returns an `access_denied` error.

## Exchange the code for tokens

Exchange the `code` for tokens at the token endpoint. Because clients are public, no client authentication is sent — the PKCE `code_verifier` proves the request comes from the app that started the flow.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.everhour.com/oauth/token \
    -d grant_type=authorization_code \
    -d code=AUTHORIZATION_CODE \
    -d redirect_uri=https://myapp.example.com/callback \
    -d client_id=YOUR_CLIENT_ID \
    -d code_verifier=YOUR_PKCE_VERIFIER
  ```

  ```http HTTP theme={null}
  POST /oauth/token HTTP/1.1
  Host: api.everhour.com
  Content-Type: application/x-www-form-urlencoded

  grant_type=authorization_code
  &code=AUTHORIZATION_CODE
  &redirect_uri=https://myapp.example.com/callback
  &client_id=YOUR_CLIENT_ID
  &code_verifier=YOUR_PKCE_VERIFIER
  ```
</CodeGroup>

The response contains the access token and a refresh token:

```json theme={null}
{
  "token_type": "Bearer",
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 3600,
  "refresh_token": "def50200a1b2c3d4..."
}
```

## Refresh a token

Access tokens last about an hour. When one expires, use the refresh token to get a new pair. Refresh tokens **rotate** — each refresh returns a new refresh token and invalidates the old one, so always store the latest.

```bash theme={null}
curl https://api.everhour.com/oauth/token \
  -d grant_type=refresh_token \
  -d refresh_token=YOUR_REFRESH_TOKEN \
  -d client_id=YOUR_CLIENT_ID
```

The token keeps the same audience it was first issued for — a refresh can't widen an `/mcp` token to the full API.

## Token lifetimes

| Token              | Lifetime     | Notes                                                                             |
| ------------------ | ------------ | --------------------------------------------------------------------------------- |
| Authorization code | \~10 minutes | Single use, exchanged once at the token endpoint.                                 |
| Access token       | \~1 hour     | Signed JWT (`RS256`). Stateless — validated by signature, not stored server-side. |
| Refresh token      | \~1 month    | Rotates on every use.                                                             |

Because access tokens are stateless, there is no per-token revocation endpoint. To end access, stop using and refreshing the tokens — the access token expires within the hour, and the refresh token lapses once it's no longer used.

## Use a token with the REST API

Send the access token in an `Authorization: Bearer` header. A full-API token works on every REST endpoint, exactly where you'd otherwise send `X-Api-Key`:

```bash theme={null}
curl https://api.everhour.com/users/me \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

When the token expires the API returns `401`; refresh it and retry. A token that was narrowed to `https://api.everhour.com/mcp` is accepted only on `/mcp` — other paths return `401`.

## Security

* **PKCE is required.** Only the `S256` method is accepted; `plain` is rejected.
* **Clients are public.** There is no client secret — never expect or store one.
* **Redirect URIs are matched exactly.** Register only URIs you control, and use `https` (or `http` on loopback for local development).
* **Verify `state`.** Send a random `state` on authorize and check it on the redirect to prevent CSRF.
* **Tokens are short-lived.** Store them securely, prefer memory or an OS keychain over disk, and never commit them.
* **Everything is over HTTPS.** All endpoints require TLS in production.
