# Authentication

Every request carries one credential in the `X-API-KEY` header. There are two kinds, issued by two endpoints, and the difference between them is scope.

| Credential | Issued by | Lives | Reaches |
| --- | --- | --- | --- |
| **API Key** | [`POST /auth`](/reference/auth/auth-create) | 2 hours | Every endpoint. Server-side only. |
| **Connect Token** | [`POST /connect_token`](/reference/auth/connect-token-create) | 30 minutes | The Item it was issued for and a reduced view of its Accounts. Safe to hand to a client. |

## API Key

```http
POST https://api.pluggy.ai/auth
Content-Type: application/json

{ "clientId": "…", "clientSecret": "…" }
```

Response `200`:

```json
{ "apiKey": "…" }
```

`clientId` and `clientSecret` come from the [Dashboard](https://dashboard.pluggy.ai). They identify your application, so `POST /auth` belongs on your server and nowhere else. A `401` with `codeDescription` `CLIENT_KEYS_UNAUTHORIZED` means the pair is wrong; `CLIENT_DISABLED` means the application is switched off.

The key expires **2 hours** after it is issued. Reuse it until then — `POST /auth` has its own [rate limit](/reference/rate-limits), and requesting a key per call is the usual way to hit it.

## Connect Token

```http
POST https://api.pluggy.ai/connect_token
X-API-KEY: {apiKey}
Content-Type: application/json

{
  "itemId": "…",
  "options": {
    "clientUserId": "…",
    "webhookUrl": "https://…",
    "oauthRedirectUri": "https://…",
    "avoidDuplicates": true
  }
}
```

Response `200`:

```json
{ "accessToken": "…" }
```

Everything in the body is optional. `itemId` scopes the token to an existing Item, for an update. `options` are applied to every Item created with the token:

| Option | Effect |
| --- | --- |
| `clientUserId` | Your identifier for the end user, stored on the Item and echoed in every `item/*` webhook. |
| `webhookUrl` | Where the events of those Items are delivered. |
| `oauthRedirectUri` | Where the user lands after an OAuth connect flow. |
| `avoidDuplicates` | Do not create a second Item for credentials that already have one. |

<Callout variant="warning" title="Options go inside options">
The only keys read at the root of the body are `itemId` and `options`. A `clientUserId` sent at the root is discarded — the call still returns `200`, and the Items end up with `clientUserId: null`. Backfill an affected Item with [`PATCH /items/{id}`](/reference/items/items-update).
</Callout>

The token expires **30 minutes** after it is issued. Issue one per connection: a new one each time you create or update an Item.

## Scope

A Connect Token is sent exactly like an API Key, in `X-API-KEY`. It can call [`GET /items/{id}`](/reference/items/items-retrieve) for its own Item and [`GET /accounts?itemId=`](/reference/account/accounts-list) with a reduced view of the data; anything else returns `403`. A token issued for one Item cannot read another, including Items created earlier with a different token.

## The flow, end to end

1. Your server calls `POST /auth` with `clientId` and `clientSecret` and keeps the API Key.
2. Your server calls `POST /connect_token` with that key and hands the `accessToken` to your client.
3. Your client — the [Connect Widget](/docs/connect-widget/introduction) or your own UI — creates or updates the Item with the token.
4. Your server reads the Item's data with the API Key.

Read the guide: [Authentication](/docs/authentication).