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

# Technical client guide

> OAuth 2.1 + PKCE implementation: remote client, local loopback, DCR, rotating refresh, revocation.

<Tip>
  RFC in practice — remote, loopback, PKCE, rotating refresh. Fast path: [Quick Start](/en/oauth-api/como-funciona/quick-start).
</Tip>

For building the flow **from scratch** or with an OAuth library. Overview: [Building a client](/en/oauth-api/como-funciona/construir-cliente).

Notifique runs **OAuth 2.1** with **mandatory PKCE** on every authorization code exchange, plus **Dynamic Client Registration (DCR)** at `POST /oauth/register`. Issuer: `https://api.notifique.dev`.

Login and consent live in the **Notifique dashboard**. Your app opens the authorize URL in the browser and handles the callback — you do **not** build consent UI.

## Client types

| Type             | Secret | Use when                   | Token endpoint auth          |
| ---------------- | :----: | -------------------------- | ---------------------------- |
| **Public**       |   No   | SPA, mobile, CLI, MCP host | PKCE + `none`                |
| **Confidential** |   Yes  | Web app with backend       | PKCE + `client_secret_basic` |

PKCE is **always** required — even for confidential clients.

## Recommended paths

1. **Fixed registration vs DCR** — pre-register known apps; use DCR at runtime for MCP/CLI when redirect/port is unknown upfront.
2. **Remote vs local** — HTTPS callback with server session vs loopback `http://127.0.0.1:<port>/callback`.

## Scopes

Declare the **minimum** at registration **and** authorize. See [Scopes](/en/oauth-api/como-funciona/escopos).

## Request encoding

| Endpoint               | Preferred Content-Type              |
| ---------------------- | ----------------------------------- |
| `POST /oauth/register` | `application/json`                  |
| `POST /oauth/token`    | `application/x-www-form-urlencoded` |
| `POST /oauth/revoke`   | `application/x-www-form-urlencoded` |

Confidential clients use **HTTP Basic** (`client_id:client_secret`) on token/revoke.

## Generating PKCE and state

```javascript theme={null}
import { createHash, randomBytes } from 'node:crypto';

function base64url(input) {
  return Buffer.from(input).toString('base64url');
}

const codeVerifier = base64url(randomBytes(64));
const codeChallenge = base64url(
  createHash('sha256').update(codeVerifier).digest(),
);
const state = base64url(randomBytes(24));
```

**Remote:** persist `state` and `codeVerifier` in the user session before redirect.

**Local:** keep in memory while the loopback server runs.

***

## Pre-registered remote client

Fixed **HTTPS** redirect, e.g. `https://example.com/oauth/callback`. Register as confidential. Store `client_secret` securely — shown once.

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant C as App backend
    participant B as Browser
    participant AS as api.notifique.dev
    participant D as Notifique dashboard

    Note over C: Generate PKCE + state<br>Store in session

    C->>B: 302 to /oauth/authorize
    B->>AS: GET authorize
    AS-->>B: 302 to consent
    B->>D: User approves
    D-->>B: redirect_uri?code&state
    B->>C: Callback

    C->>AS: POST /oauth/token (Basic + code_verifier)
    AS-->>C: access_token + refresh_token
```

Treat missing `code`, mismatched `state`, or `error` query params as failures.

```bash theme={null}
curl -X POST 'https://api.notifique.dev/oauth/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -u 'CLIENT_ID:CLIENT_SECRET' \
  -d 'grant_type=authorization_code&code=AUTH_CODE&redirect_uri=https%3A%2F%2Fexample.com%2Foauth%2Fcallback&code_verifier=CODE_VERIFIER'
```

***

## Local client (loopback)

**Public** client, bind `127.0.0.1` or `[::1]` — never `0.0.0.0`.

<Warning>
  Do not prefetch `/oauth/authorize` server-side. The **user** must see the consent screen in a browser.
</Warning>

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant C as Local client
    participant B as Browser
    participant AS as api.notifique.dev

    C->>AS: POST /oauth/register
    C->>B: Open authorize URL
    B->>C: Loopback callback code+state
    C->>AS: POST /oauth/token (code_verifier only)
    AS-->>C: tokens
```

```bash theme={null}
curl -X POST 'https://api.notifique.dev/oauth/register' \
  -H 'Content-Type: application/json' \
  -d '{
    "client_name": "My CLI",
    "redirect_uris": ["http://127.0.0.1/oauth/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "token_endpoint_auth_method": "none",
    "scope": "email:send"
  }'
```

Close the loopback server after success or timeout.

***

## Rotating refresh tokens

Each successful refresh returns a **new** `refresh_token`. The old one stops working.

<Warning>
  Serialize refresh per grant. Persist the new refresh **atomically**. Parallel workers refreshing the same old token can revoke the entire grant.
</Warning>

```bash theme={null}
curl -X POST 'https://api.notifique.dev/oauth/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=refresh_token&client_id=CLIENT_ID&refresh_token=rt_...'
```

***

## Call `/v1` with the access token

JWT (\~15 min, EdDSA). Optional offline validation via `GET /.well-known/jwks.json`.

***

## Revoke access

Revoke the **refresh token** — JWT access tokens are not individually revocable.

```bash theme={null}
curl -X POST 'https://api.notifique.dev/oauth/revoke' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -u 'CLIENT_ID:CLIENT_SECRET' \
  -d 'token=rt_...&token_type_hint=refresh_token'
```

Workspace: **Settings → Team → Connected apps**.

## Next steps

* [Quick Start](/en/oauth-api/como-funciona/quick-start)
* [MCP](/en/oauth-api/como-funciona/mcp)
* [OpenAPI](/en/oauth-api/api-reference/openapi-oauth.json)
