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

# Authentication

> OAuth 2.1, PKCE, scopes, tokens, and how partner apps authenticate to the Swapnice API.

The Partner API uses OAuth 2.1 with authorization-code + PKCE. It does not accept consumer-app Firebase tokens, implicit grants, or password grants.

## What you store

| Credential      | Who holds it                                          | Shown                          |
| --------------- | ----------------------------------------------------- | ------------------------------ |
| `app_id`        | Partner backend                                       | After Swapnice creates the app |
| `client_id`     | Partner backend and public clients                    | After create                   |
| `client_secret` | Confidential backend only                             | Once, at create or rotation    |
| Access token    | Short-lived, in memory or a secret store              | From `/oauth/token`            |
| Refresh token   | Confidential backend, if `offline_access` was granted | Rotates on use                 |

Public or browser clients can start a connection session. They must not hold `client_secret`.

## Discovery

```bash theme={null}
curl https://api.sandbox.swapnice.com/.well-known/oauth-authorization-server
```

The document lists `authorization_endpoint`, `token_endpoint`, `introspection_endpoint`, `revocation_endpoint`, supported grants, and `S256` as the only code-challenge method.

## Authorization-code + PKCE

<Steps>
  <Step title="Create a code verifier and S256 challenge">
    The verifier is 43–128 characters of unreserved URL-safe text. The challenge is the Base64URL SHA-256 of that verifier, without padding.
  </Step>

  <Step title="Request an authorization code">
    `POST /oauth/authorize` with `response_type=code`, exact `redirect_uri`, `customer_id`, space-separated `scope`, and `code_challenge_method=S256`.
  </Step>

  <Step title="Exchange the code">
    `POST /oauth/token` with `grant_type=authorization_code`, `code_verifier`, `client_id`, `client_secret`, and the same `redirect_uri`.
  </Step>

  <Step title="Call the API">
    Send `Authorization: Bearer <access_token>`. Access tokens last about 15 minutes. Authorization codes last about 5 minutes and are single-use.
  </Step>
</Steps>

<CodeGroup>
  ```ts TypeScript theme={null}
  const token = await swapnice.oauth.token({
    grant_type: "authorization_code",
    client_id: process.env.SWAPNICE_CLIENT_ID!,
    client_secret: process.env.SWAPNICE_CLIENT_SECRET!,
    code,
    code_verifier: verifier,
    redirect_uri: process.env.SWAPNICE_REDIRECT_URI!,
  });
  ```

  ```bash cURL theme={null}
  curl -X POST "$SWAPNICE_API_BASE_URL/oauth/authorize" \
    -H "Content-Type: application/json" \
    -d '{
      "response_type": "code",
      "client_id": "'"$SWAPNICE_CLIENT_ID"'",
      "redirect_uri": "'"$SWAPNICE_REDIRECT_URI"'",
      "code_challenge": "'"$CODE_CHALLENGE"'",
      "code_challenge_method": "S256",
      "customer_id": "cust_8f2a1c",
      "scope": "connections:write consent:write events:write profile:read claims:read intents:read"
    }'

  curl -X POST "$SWAPNICE_API_BASE_URL/oauth/token" \
    -H "Content-Type: application/json" \
    -d '{
      "grant_type": "authorization_code",
      "client_id": "'"$SWAPNICE_CLIENT_ID"'",
      "client_secret": "'"$SWAPNICE_CLIENT_SECRET"'",
      "code": "'"$AUTH_CODE"'",
      "code_verifier": "'"$CODE_VERIFIER"'",
      "redirect_uri": "'"$SWAPNICE_REDIRECT_URI"'"
    }'
  ```
</CodeGroup>

Example token response:

```json theme={null}
{
  "access_token": "swa_at_...",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "connections:write consent:write events:write profile:read claims:read intents:read"
}
```

<Warning>
  Do not use Postman's built-in OAuth 2.0 helper. Authorize is a JSON `POST` that includes `customer_id`. See the [request walkthrough](/partners/request-walkthrough).
</Warning>

## Introspect and revoke

```bash theme={null}
curl -X POST "$SWAPNICE_API_BASE_URL/oauth/introspect" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "token": "'"$ACCESS_TOKEN"'" }'

curl -X POST "$SWAPNICE_API_BASE_URL/oauth/revoke" \
  -H "Content-Type: application/json" \
  -d '{ "token": "'"$ACCESS_TOKEN"'" }'
```

Introspect requires `tokens:introspect`. Revoke does not send a Bearer header. Tokens are hashed at rest.

## Redirect URIs

* Must be HTTPS in production (localhost is acceptable in sandbox)
* Must match a registered URI **exactly** — no wildcards, no trailing-slash drift
* Browser/public clients also need allowed origins if they call from a web SDK

## Scope templates

Start from a template. Add scopes only when a call needs them.

| Template           | Scopes                                                                                                                                            | Use                            |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
| Ingest only        | `consent:write` `events:write`                                                                                                                    | Send consented events          |
| Connect and ingest | `connections:write` `connections:read` `consent:write` `consent:read` `events:write` `events:read`                                                | Full SDK connect flow          |
| Profile reads      | previous + `profile:read` `claims:read` `intents:read`                                                                                            | Read resolved intelligence     |
| Full partner       | previous + `customers:write` `customers:read` `webhooks:write` `webhooks:read`                                                                    | Production integration         |
| Admin / platform   | `apps:read` `apps:write` `entries:read` `entries:write` `ontologies:read` `ontologies:write` `tokens:introspect` `tokens:revoke` `workflows:read` | Trusted internal or enterprise |

`scope` on authorize is a **space-separated** string. It must be a subset of the app's allowed scopes.

## Common errors

| Code                                   | Cause                                  | Fix                                       |
| -------------------------------------- | -------------------------------------- | ----------------------------------------- |
| `invalid_client` / `invalid_client_id` | Unknown client                         | Confirm sandbox `client_id`               |
| `invalid_grant`                        | Code expired, reused, or PKCE mismatch | Re-run authorize with a fresh verifier    |
| `invalid_scope`                        | Scope not allowed on the app           | Ask Swapnice to add the scope, or drop it |
| `invalid_redirect_uri`                 | URI does not match registration        | Copy the registered value exactly         |
| `missing_bearer_token`                 | No `Authorization` header              | Attach the access token                   |
| `401` on `/v1/*`                       | Token expired or missing scope         | Re-authorize; tokens last \~15 minutes    |

## Security rules the API enforces

* PKCE `S256` only (`plain` is rejected)
* Short-lived access tokens
* Hashed secrets and tokens at rest
* Exact redirect match
* Purpose-checked ingest and reads after you have a token
* Authorization failures stay generic and never include secrets

Next: [consent and account linking](/partners/consent) or the [request walkthrough](/partners/request-walkthrough).
