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

# OAuth 2.0 Flow

> How authentication works on the JTL platform: Cloud OAuth 2.0, OnPremise API keys, and SCX refresh tokens.

The JTL platform uses different authentication mechanisms across its APIs. This page explains how each one works, when to use it, and how tokens are managed.

## Authentication at a Glance

| API                   | Auth mechanism                | Token type               | Lifetime                           |
| --------------------- | ----------------------------- | ------------------------ | ---------------------------------- |
| **JTL Cloud API**     | OAuth 2.0 Client Credentials  | Short-lived JWT          | \~24 hours (refresh before expiry) |
| **OnPremise ERP API** | Proprietary registration flow | Static API key           | Permanent                          |
| **SCX Channel API**   | Refresh token exchange        | Short-lived access token | \~1 hour (refresh before expiry)   |

## Two Types of Tokens (Cloud)

If you're building on the Cloud platform, you'll use two tokens:

| Token                  | How you get it                                   | What it's for                                                             | Who creates it                |
| ---------------------- | ------------------------------------------------ | ------------------------------------------------------------------------- | ----------------------------- |
| **Access token (JWT)** | Client credentials grant > JTL Identity Provider | Authenticating your backend's API calls to JTL                            | Your app's backend            |
| **Session token**      | `appBridge.method.call('getSessionToken')`       | Identifying which merchant (tenant) and user is interacting with your app | JTL App Shell (via AppBridge) |

**Access token**: identifies your app (machine-to-machine).

**Session token**: identifies the tenant and user (from the App Shell).

In a typical Cloud App, both tokens are in play: the session token tells you *who*, and the access token lets you *act* on their behalf.

<Note>
  Check out the [Cloud Apps: Authentication & Login](/guides/cloud-apps/authentication-login) guide to learn more about Cloud Apps authentication (JWKS, AppBridge integration).
</Note>

<Tabs>
  <Tab title="JTL Cloud (OAuth 2.0)">
    ## Cloud Authentication (OAuth 2.0)

    The Cloud API uses the **OAuth 2.0 Client Credentials Flow**. Your app's backend authenticates with a client ID and secret, receives a short-lived JWT, and uses that JWT as a Bearer token for all API requests.

    ### Prerequisites

    Your app must be registered in the [Partner Portal](https://partner.jtl-cloud.com/). Registration creates an OAuth client with a `Client ID` and `Client Secret`.

    <Warning>
      Your `Client Secret` is displayed **only once** immediately after registration. Store it securely.
    </Warning>

    ### How the Flow Works

    ```mermaid theme={null}
    sequenceDiagram
      autonumber
     
      participant appBackend as App Backend
      participant identityProvider as JTL Identity Provider
      participant wawiApi as JTL Cloud API
     
      appBackend->>identityProvider: Request Access Token (client credentials)
      activate identityProvider
      identityProvider-->>appBackend: Return Access Token (JWT)
      deactivate identityProvider
     
      appBackend->>wawiApi: Request resource (Bearer token + Tenant ID)
      activate wawiApi
      wawiApi-->>appBackend: Return resource data
      deactivate wawiApi
    ```

    1. Your backend sends its client credentials to the JTL Identity Provider
    2. The Identity Provider returns a short-lived JWT access token
    3. Your backend includes that token (along with the tenant ID) in every API request

    ### Token Endpoint

    ```
    POST https://auth.jtl-cloud.com/oauth2/token
    ```

    **Request:**

    | Component         | Value                                   |
    | ----------------- | --------------------------------------- |
    | **Method**        | `POST`                                  |
    | **Content-Type**  | `application/x-www-form-urlencoded`     |
    | **Authorization** | `Basic <Base64(clientId:clientSecret)>` |
    | **Body**          | `grant_type=client_credentials`         |

    **Example request:**

    ```bash theme={null}
    curl -X POST https://auth.jtl-cloud.com/oauth2/token \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -H "Authorization: Basic $(echo -n "$CLIENT_ID:$CLIENT_SECRET" | base64)" \
      -d "grant_type=client_credentials"
    ```

    **Response:**

    ```json theme={null}
    {
      "access_token": "eyJhbGciOiJSUzI1NiIs...",
      "token_type": "Bearer",
      "expires_in": 86399
    }
    ```

    | Field          | Description                                                         |
    | -------------- | ------------------------------------------------------------------- |
    | `access_token` | The JWT to use in API requests                                      |
    | `token_type`   | Always `Bearer`                                                     |
    | `expires_in`   | Token lifetime in seconds. Request a new token before this expires. |

    <Note>
      For a full implementation including caching, JWKS verification, and tenant mapping, see [Cloud Apps: Authentication & Login](/guides/cloud-apps/authentication-login).
    </Note>

    ### Making Authenticated API Requests

    Once you have an access token, include it in every API request along with the tenant ID.

    **Base URL:**

    ```
    https://api.jtl-cloud.com/erp/v2/
    ```

    **Required headers:**

    | Header          | Value          | Description                                                                 |
    | --------------- | -------------- | --------------------------------------------------------------------------- |
    | `Authorization` | `Bearer <JWT>` | The access token from the client credentials flow                           |
    | `X-Tenant-ID`   | `<tenantId>`   | The JTL Cloud tenant ID (identifies which merchant's data you're accessing) |

    **Example request:**

    ```bash theme={null}
    curl -X GET https://api.jtl-cloud.com/erp/v2/info \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "X-Tenant-ID: $TENANT_ID"
    ```

    <Note>
      Check out the [Cloud Apps: Authentication & Login](/guides/cloud-apps/authentication-login) guide to see how to get the tenant ID.
    </Note>

    ### Token Lifecycle

    Access tokens are **short-lived** and expire after a set period. Your app needs to handle this:

    * Cache the token and reuse it until it's close to expiry
    * Refresh proactively (e.g. when \< 5 minutes remain)
    * Handle `401 Unauthorized` by refreshing once and retrying

    ### Security Schemes

    Cloud endpoints are secured using one of two schemes:

    | Scheme                                | When it applies                                                                         |
    | ------------------------------------- | --------------------------------------------------------------------------------------- |
    | `Wawi AND oauth2-cloud-id-accessCode` | User-bound access: the request is tied to a specific user's session                     |
    | `Wawi AND oauth2-application`         | Machine-to-machine access: your backend calls the API directly using client credentials |
  </Tab>

  <Tab title="OnPremise (API Key)">
    ## OnPremise Authentication

    The OnPremise API uses a proprietary two-step registration flow that produces a permanent API key (no OAuth).

    This applies when your app integrates directly with a merchant's local JTL-Wawi installation.

    **Base URL:**

    ```
    http://127.0.0.1:<port>/api/eazybusiness/
    ```

    ### How the Flow Works

    ```mermaid theme={null}
    sequenceDiagram
      autonumber
     
      participant app as Your App
      participant wawi as JTL-Wawi (Desktop)
      participant api as JTL-Wawi API (Local)
     
      app->>wawi: 1. Initiate registration (Admin panel)
      wawi-->>api: Registration endpoint ready
     
      app->>api: POST /authentication (app info + challenge code)
      activate api
      api-->>app: Return registration ID
      deactivate api
     
      app->>wawi: User confirms registration in JTL-Wawi
      wawi-->>api: Registration confirmed
     
      app->>api: GET /authentication/{registrationId}
      activate api
      api-->>app: Return permanent API key
      deactivate api
     
      app->>api: API requests with Authorization: Wawi <API-Key>
      api-->>app: API responses
    ```

    ### Registration Steps

    <Steps>
      <Step title="Prepare for Registration in JTL-Wawi">
        In the JTL-Wawi desktop application, navigate to `Admin > App Registration` and complete the required steps. The steps must be completed before your app can send its first API request. The registration endpoint is not available until it's done.
      </Step>

      <Step title="Register your App (POST)">
        Send a `POST` request with your app's information to the authentication endpoint.

        **Endpoint:**

        ```
        POST http://127.0.0.1:<port>/api/eazybusiness/authentication
        ```

        **Required headers:**

        | Header            | Description                                                                                            |
        | ----------------- | ------------------------------------------------------------------------------------------------------ |
        | `api-version`     | Desired API version, e.g. `2.0`                                                                        |
        | `x-challengecode` | A custom value of your choice (max 30 characters). Must be identical across all registration requests. |

        **Required body fields:**

        | Field               | Description                                                                               |
        | ------------------- | ----------------------------------------------------------------------------------------- |
        | `AppName`           | Name of your application                                                                  |
        | `AppVersion`        | Version of your application                                                               |
        | `RequiredApiScopes` | API scopes your app needs                                                                 |
        | `AppIcon`           | Base64-encoded app icon                                                                   |
        | `RegistrationType`  | `0` = OneInstance, `1` = MultiInstance, `2` = PerUserInstance, `3` = PerUserLoginInstance |

        **Example request:**

        ```bash cURL theme={null}
        curl -X POST "http://127.0.0.1:<port>/api/eazybusiness/authentication" \
          -H "Content-Type: application/json" \
          -H "api-version: 2.0" \
          -H "x-challengecode: my-custom-challenge" \
          -d '{
            "AppName": "My App",
            "AppVersion": "1.0.0",
            "RequiredApiScopes": ["<scope1>", "<scope2>"],
            "AppIcon": "iVBORw0KGgoAAAANSUhEUgAA...",
            "RegistrationType": 0
          }'
        ```

        <Note>
          `RequiredApiScopes` follows the `resource.read` and `resource.write` pattern (e.g. `orders.read`, `orders.write`, `products.read`, `products.write`, etc.).
        </Note>

        The API responds with a **registration ID** (token).
      </Step>

      <Step title="Confirm and Retrieve API Key (GET)">
        After receiving the registration ID, the user will be prompted to confirm the registration in JTL-Wawi. Once confirmed, your app sends a `GET` request with the registration ID:

        **Endpoint:**

        ```
        GET http://127.0.0.1:<port>/api/eazybusiness/authentication/{registrationId}
        ```

        **Required headers:**

        | Header            | Description                              |
        | ----------------- | ---------------------------------------- |
        | `api-version`     | Desired API version                      |
        | `x-challengecode` | The same value used in the previous step |

        **Example request:**

        ```bash cURL theme={null}
        curl -X GET "http://127.0.0.1:<port>/api/eazybusiness/authentication/{registrationId}" \
          -H "api-version: 2.0" \
          -H "x-challengecode: my-custom-challenge"
        ```

        On success, the API returns a **permanent API key**.

        <Warning>
          The API key is returned **only once** and cannot be retrieved again. Store it securely.
        </Warning>
      </Step>

      <Step title="Make Authenticated API Requests">
        Include the API key in the `Authorization` header of every request.

        **Required headers:**

        | Header                 | Value                 | Description                                                                   |
        | ---------------------- | --------------------- | ----------------------------------------------------------------------------- |
        | `Authorization`        | `Wawi <API-Key>`      | The permanent API key from registration                                       |
        | `x-appid`              | e.g. `MyApp/1.0.0`    | Name of your application                                                      |
        | `x-appversion`         | e.g. `1.0.0`          | Version of your application                                                   |
        | `api-version`          | e.g. `2.0`            | Desired API version                                                           |
        | `x-challengecode`      | Custom value          | The same value used during registration                                       |
        | `x-runas` *(optional)* | User ID (int or UUID) | Execute request on behalf of a Wawi user. Requires `Application.RunAs` scope. |

        **Example request:**

        ```bash cURL theme={null}
        curl -i -X GET \
          'http://127.0.0.1:<port>/api/eazybusiness/info' \
          -H 'Authorization: Wawi <PERMANENT_API_KEY>' \
          -H 'api-version: 2.0' \
          -H 'x-appid: MyApp/1.0.0' \
          -H 'x-appversion: 1.0.0' \
          -H 'x-challengecode: my-custom-challenge'
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

## JTL Cloud vs. OnPremise Comparison

| Feature                   | Cloud                                            | OnPremise                                    |
| ------------------------- | ------------------------------------------------ | -------------------------------------------- |
| **Auth mechanism**        | OAuth 2.0 (Client Credentials)                   | Proprietary API key flow                     |
| **Token type**            | Short-lived JWT Bearer token                     | Permanent static API key                     |
| **Registration**          | [Partner Portal](https://partner.jtl-cloud.com/) | JTL-Wawi desktop `Admin >> App Registration` |
| **Credentials**           | `ClientId` + `ClientSecret`                      | Registration ID >> API key                   |
| **Tenant identification** | `X-Tenant-ID` header required                    | Not required (local connection)              |
| **Base URL**              | `https://api.jtl-cloud.com/erp/v2/`              | `http://127.0.0.1:<port>/api/eazybusiness/`  |
| **Authorization format**  | `Bearer <JWT>`                                   | `Wawi <PERMANENT_API_KEY>`                   |
| **Token refresh**         | Re-request with client credentials before expiry | Not needed (key is permanent)                |

***

## SCX Channel API Authentication

The SCX Channel API uses the same **client credentials** mechanism as the Cloud-ERP API. You request an access token, use it for API calls, and request a new one before it expires.

This section applies only to [Marketplace Channel](/guides/marketplace-channels/channel-api-overview) integrations.

**Base URL:**

```
https://scx.api.jtl-software.com/v1/
```

### How the Flow Works

```mermaid theme={null}
sequenceDiagram
  autonumber

  participant app as Your App
  participant scx as SCX Channel API

  app->>scx: POST /v1/auth (refreshToken)
  activate scx
  scx-->>app: Return authToken + expiresIn
  deactivate scx

  loop Until token expires
    app->>scx: API request (Bearer authToken)
    scx-->>app: API response
  end

  Note over app,scx: Token near expiry — request a new one

  app->>scx: POST /v1/auth (refreshToken)
  activate scx
  scx-->>app: Return new authToken
  deactivate scx
```

### Requesting an Access Token

**Example request:**

```bash cURL theme={null}
curl -X POST 'https://scx.api.jtl-software.com/v1/auth' \
  --form 'refreshToken=YOUR-REFRESH-TOKEN'
```

**Response:**

```json theme={null}
{
  "scope": "CHANNEL",
  "authToken": "eyJ0eXAi....",
  "tokenExpireAt": "2024-05-14T10:07:42+02:00",
  "expiresIn": 3600
}
```

| Field           | Description                                  |
| --------------- | -------------------------------------------- |
| `scope`         | The scope of the token (e.g. `CHANNEL`)      |
| `authToken`     | The access token for authenticating requests |
| `tokenExpireAt` | Expiration timestamp in ISO 8601 format      |
| `expiresIn`     | Time in seconds until the token expires      |

### Using the Access Token

Include the `authToken` as a Bearer token in all subsequent requests:

```bash cURL theme={null}
curl -X POST 'https://scx.api.jtl-software.com/v1/seller/channel/MYCHANNEL' \
  -H 'Authorization: Bearer eyJ0eXAi....'
```

### Token Lifecycle

SCX access tokens have a **TTL** (Time To Live) of 1 hour. Your app should:

* Cache the token and reuse it until it's close to expiry
* Monitor the `expiresIn` value and refresh proactively (e.g. when less than 5 minutes)
* Refresh tokens before they expire to avoid failures during requests.

## Best Practices

These practices apply regardless of which auth mechanism you're using:

**Credential storage**

* Never hardcode credentials in source code. Use environment variables or a secrets manager.
* Never commit `.env` files to version control.
* Rotate credentials if you suspect they've been compromised.

**Token management**

* Cache tokens and reuse them. Don't request a new token for every API call.
* Refresh proactively before expiry, not after receiving a 401 error.
* On a 401 response, refresh the token and retry the request once.

**Security**

* Always use HTTPS for Cloud and SCX API calls.
* Validate session tokens server-side. Never trust tokens received from the client without verification.
* Request the minimum required scopes. Don't request broader access than your app needs.

## Next Steps

<CardGroup cols={2}>
  <Card title="API Keys & Tokens" icon="key" href="/guides/essentials/authentication/api-keys-tokens">
    Deeper dive into JWT structure, session tokens, and token management
    patterns.
  </Card>

  <Card title="Cloud Apps: Authentication" icon="lock" href="/guides/cloud-apps/authentication-login">
    Implementation guide for authentication in Cloud Apps: AppBridge,
    session tokens, and the setup handshake.
  </Card>

  <Card title="Error Handling" icon="triangle-alert" href="/guides/essentials/common-patterns/error-handling">
    How to handle auth errors, expired tokens, and common failure modes.
  </Card>
</CardGroup>
