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

# Authentication

> Implement OAuth 2.1 to authorize Vendaze users in your app.

The Vendaze API uses **OAuth 2.1 Authorization Code flow** with `client_secret`. This is a server-side integration: all token exchanges must happen on your back-end. The `client_secret` must never be exposed in browser code, mobile app binaries, or any environment the end user can inspect.

Before continuing, you need a `client_id` and `client_secret`. See [Register your app](/en/guides/register-app).

## How it works

<Steps>
  <Step title="Redirect the user to the authorization endpoint">
    Your server builds the authorization URL and redirects the user's browser to it.
  </Step>

  <Step title="User logs in and approves">
    The user selects a workspace and reviews the requested scopes on the Vendaze consent screen.
    They can only approve or deny access in full, with no option to select individual scopes.
  </Step>

  <Step title="Receive the authorization code">
    Vendaze redirects the browser back to your `redirect_uri` with a short-lived `code`.
  </Step>

  <Step title="Exchange the code for tokens">
    Your server exchanges the code for an `access_token` and `refresh_token` via a back-end request.
  </Step>

  <Step title="Make API calls">Include the `access_token` as a Bearer token in every request.</Step>

  <Step title="Refresh when expired">
    Access tokens expire after 1 hour. Use the `refresh_token` to get a new one without user
    interaction.
  </Step>
</Steps>

## Step 1 - Redirect the user

Build the authorization URL and redirect the user's **browser** to it. This must be a browser navigation, not a server-side HTTP request. Do not use `fetch`, `axios`, `curl`, or any HTTP client to call this endpoint. The user must be redirected so they can log in and approve access on the Vendaze consent screen.

```
GET https://api.vendaze.com/oauth/authorize
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/oauth/callback
  &response_type=code
  &state=RANDOM_CSRF_TOKEN
```

Example redirect in a web app:

```js theme={null}
const params = new URLSearchParams({
  client_id: 'YOUR_CLIENT_ID',
  redirect_uri: 'https://yourapp.com/oauth/callback',
  response_type: 'code',
  state: crypto.randomUUID(),
});

// Store state for CSRF verification on callback
sessionStorage.setItem('oauth_state', params.get('state'));

// Navigate the browser. Never use fetch/axios here.
window.location.href = `https://api.vendaze.com/oauth/authorize?${params}`;
```

| Parameter       | Required | Description                                                                           |
| --------------- | -------- | ------------------------------------------------------------------------------------- |
| `client_id`     | Yes      | Your app's client ID.                                                                 |
| `redirect_uri`  | Yes      | Must exactly match a registered URI.                                                  |
| `response_type` | Yes      | Must be `code`.                                                                       |
| `state`         | No       | Random string you generate. Returned unchanged. Strongly recommended to prevent CSRF. |

<Warning>
  Always include `state` and verify it on the callback. Reject any callback where the received
  `state` does not match what you sent. This protects against CSRF attacks.
</Warning>

## Step 2 - User authorizes

The user selects a workspace and reviews the requested scopes on the Vendaze consent screen. They can only approve or deny access in full, with no option to select individual scopes. No action required on your side during this step.

## Step 3 - Handle the callback

After approval, the user is redirected to your `redirect_uri`:

```
https://yourapp.com/oauth/callback?code=AUTH_CODE&state=YOUR_STATE
```

Verify `state` matches what you stored, then proceed to exchange the code. If the user denies authorization:

```
https://yourapp.com/oauth/callback?error=access_denied&state=YOUR_STATE
```

Handle the `error` parameter and show an appropriate message to the user.

## Step 4 - Exchange code for tokens

<Warning>
  Authorization codes expire in 10 minutes and can only be used once. Exchange immediately after
  receiving the callback.
</Warning>

This request must be made from your server, never from the browser.

```bash theme={null}
curl -X POST https://api.vendaze.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code&code=AUTH_CODE_FROM_CALLBACK&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET"
```

**Response (200):**

```json theme={null}
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "workspace_slug": "acme-corp"
}
```

| Field            | Description                                                                                        |
| ---------------- | -------------------------------------------------------------------------------------------------- |
| `access_token`   | Use this in every API request. Expires in 1 hour.                                                  |
| `refresh_token`  | Use this to get a new access token. Valid for 60 days if used regularly.                           |
| `token_type`     | Always `Bearer`.                                                                                   |
| `expires_in`     | Seconds until the access token expires. Always 3600.                                               |
| `workspace_slug` | Slug of the workspace the user authorized. Use this to identify the connected account in your app. |

## Token storage

Store both tokens securely on your server. The following practices apply:

* **`access_token`:** short-lived (1 hour). Store in memory or a fast cache. Never in localStorage or cookies accessible to JavaScript.
* **`refresh_token`:** long-lived (60 days). Store encrypted in your database, associated with the user and workspace. This is the credential that allows you to maintain access without user interaction.
* Never log either token. Never include them in URLs or query strings.
* Never expose them in API responses to your own front-end. Your front-end should call your back-end, which calls Vendaze.

## Step 5 - Make API calls

Include the `access_token` in every request as a Bearer token:

```bash theme={null}
curl https://api.vendaze.com/v1/people \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
```

The workspace context is resolved automatically from the token. You never send a workspace identifier in requests.

## Step 6 - Refresh the access token

Access tokens expire after 1 hour. Refresh them using the refresh token before the expiry, or reactively when you receive a `token_expired` error.

```bash theme={null}
curl -X POST https://api.vendaze.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET"
```

The response is identical to Step 4. Replace both tokens on your side. A new `refresh_token` is issued on every refresh and the previous one is immediately invalidated.

<Note>
  Refresh tokens expire if unused for 60 days. Each time you use a refresh token, the 60-day window
  resets. If a refresh token expires, the user must go through the full authorization flow again.
</Note>

## Revoking access

To disconnect a workspace from your app, revoke the access token:

```bash theme={null}
curl -X POST https://api.vendaze.com/oauth/revoke \
  -H "Content-Type: application/json" \
  -d '{
    "token": "ACCESS_TOKEN",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET"
  }'
```

<Warning>
  Always send the `access_token`, not the `refresh_token`. The revoke endpoint requires a JWT to
  identify the user and remove the authorization grant.
</Warning>

After revoking, delete both tokens from your storage. The user will need to re-authorize if they want to reconnect.

***

Next: [Make your first request](/en/guides/first-request) to use your access token in a real API call.
