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

# OAuth 2.0

> Interactive OAuth 2.1 + PKCE and server-to-server JWT Bearer flows for partner apps.

Use OAuth 2.0 when you're building a **partner application** — a
Salesforce app, a HubSpot extension, a third-party tool — that acts
on behalf of individual Yotel users rather than your own tenant.

For your own server-to-server automation inside your tenant,
[API keys](/authentication) are simpler. OAuth is for when users
from *other* Yotel tenants install your app.

## Flows

Yotel supports three grant types:

| Grant type                                    | When to use                                                                                                  |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `authorization_code` (with PKCE)              | Interactive — user consents in a browser. SPAs, installed web apps, Salesforce, HubSpot. **Default choice.** |
| `refresh_token`                               | Renew an expired access token without user reauth. Rotated on every refresh.                                 |
| `urn:ietf:params:oauth:grant-type:jwt-bearer` | Server-to-server. No user browser. Partner signs a short-lived JWT with their private key.                   |

## Discovery

Our provider implements [OpenID Connect Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html).
Fetch `/.well-known/openid-configuration` to auto-configure any OIDC
client library:

```bash theme={null}
curl https://api.yotel.in/.well-known/openid-configuration
```

Key endpoints:

| Endpoint                 | Purpose                                            |
| ------------------------ | -------------------------------------------------- |
| `/oauth/authorize`       | Redirect here to start interactive flow            |
| `/oauth/token`           | Exchange code / refresh / JWT assertion for tokens |
| `/oauth/introspect`      | RFC 7662 — check if a token is still active        |
| `/oauth/revoke`          | RFC 7009 — explicitly invalidate a token           |
| `/oauth/userinfo`        | OIDC — fetch the user's profile                    |
| `/.well-known/jwks.json` | RSA public keys for offline JWT verification       |

## Registering a client

Tenant admins register partner apps in the Yotel dashboard
([Connected Apps settings](https://app.yotel.in/settings/connected-apps)).
Each client gets:

* `client_id` — public identifier, safe to ship in app metadata
* `client_secret` — confidential; show **once** at creation time
* An allowlist of `redirect_uris`
* An allowlist of `scopes` this client may request
* (Optional) A registered public key PEM for JWT Bearer

## Authorization Code + PKCE

The interactive flow for SPAs and traditional web apps. PKCE
(RFC 7636) is **required** for all clients — no `plain` method
accepted.

```python theme={null}
from yotel.oauth import AuthorizationCodeFlow, generate_code_verifier

flow = AuthorizationCodeFlow(
    client_id="sfdc_yotel",
    client_secret=os.environ["YOTEL_CLIENT_SECRET"],
    redirect_uri="https://app.your-crm.com/oauth/callback",
    scopes=["campaigns:read", "leads:write", "openid", "offline_access"],
)

# Step 1 — build the URL to redirect the user's browser to
verifier = generate_code_verifier()
url = flow.build_authorize_url(state="csrf-123", code_verifier=verifier)
# (save `verifier` + `state` in the user's session cookie)

# Step 2 — callback handler on your server
tokens = flow.exchange_code(
    code=request.query["code"],
    code_verifier=verifier,
)
# tokens.access_token, tokens.refresh_token, tokens.expires_at
```

Same shape in TypeScript:

```ts theme={null}
import { AuthorizationCodeFlow, generateCodeVerifier } from "@yotel/client";

const flow = new AuthorizationCodeFlow({
  clientId: "sfdc_yotel",
  clientSecret: process.env.YOTEL_CLIENT_SECRET!,
  redirectUri: "https://app.your-crm.com/oauth/callback",
  scopes: ["campaigns:read", "leads:write", "openid", "offline_access"],
});

const verifier = generateCodeVerifier();
const url = await flow.buildAuthorizeUrl({ state: "csrf-123", codeVerifier: verifier });
// ... redirect, callback ...
const tokens = await flow.exchangeCode({ code, codeVerifier: verifier });
```

### Required parameters

| Param                   | Value                                                                |
| ----------------------- | -------------------------------------------------------------------- |
| `response_type`         | `code`                                                               |
| `client_id`             | From Connected Apps                                                  |
| `redirect_uri`          | Must exact-match one of the registered redirect URIs                 |
| `scope`                 | Space-separated scopes — must be subset of client's `allowed_scopes` |
| `state`                 | Your CSRF token. Verify on callback                                  |
| `code_challenge`        | S256 hash of your verifier                                           |
| `code_challenge_method` | `S256`                                                               |
| `nonce`                 | Optional but recommended when using OIDC `openid` scope              |

## JWT Bearer (RFC 7523)

Salesforce's async Apex jobs, batch integrations, any server-to-server
context where a user browser isn't available.

```python theme={null}
from yotel.oauth import JWTBearerFlow

flow = JWTBearerFlow(
    client_id="sfdc_yotel",
    issuer="https://my-salesforce-org.my.salesforce.com",
    subject="user-uuid-to-impersonate",
    tenant_id="tenant-uuid",
    private_key_pem=open("private.pem").read(),
)
tokens = flow.request_token()
# No refresh token issued — re-assert on expiry (5-min TTL default)
```

The assertion is an RS256 JWT with claims:

* `iss` — your registered issuer
* `sub` — the user to impersonate
* `aud` — your `client_id`
* `tenant_id` — the Yotel tenant
* `iat` / `exp` — 5-minute TTL enforced server-side

## Refresh

Tokens default to 1-hour TTL. Refresh **before** expiry using the
`refresh_token` returned with the access token:

```python theme={null}
tokens = tokens.refresh()  # returns a new TokenSet with rotated refresh
```

**Rotation policy:** every refresh invalidates the old `refresh_token`
and issues a new one. Reusing an old refresh token triggers a
cascade-revoke of the entire token family — so you can detect when
a refresh\_token has leaked.

## Token format

Access tokens are RS256-signed JWTs. Claims include:

```json theme={null}
{
  "sub": "user-uuid",
  "tenant_id": "tenant-uuid",
  "scope": "campaigns:read leads:write",
  "iss": "https://api.yotel.in",
  "aud": "your-client-id",
  "exp": 1718000000,
  "iat": 1717996400,
  "jti": "unique-token-id"
}
```

You can verify tokens offline using our JWKS endpoint — no
introspection round-trip needed.

## Scopes

See [Scope reference](/authentication/scopes) for the full catalog.
Common ones:

* `openid profile email` — OIDC identity claims
* `offline_access` — required to receive a refresh\_token
* `campaigns:read`, `campaigns:write`
* `leads:read`, `leads:write`
* `calls:read`, `calls:write`
* `agents:read`

## Security review

Partners embedding our OAuth in their AppExchange / marketplace
listings: our provider satisfies the baseline requirements —

* OAuth 2.1 + PKCE (no `plain` method)
* HTTPS-only endpoints
* Refresh-token rotation with reuse detection
* Per-client allowlisted scopes and redirect URIs
* Token introspection + revocation per RFCs 7662 / 7009
* Pen-test artifact available under NDA
