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

# Introduction to Authentication

> Get your access token in minutes with OAuth 2.0

ClearLine uses **OAuth 2.0** for secure API authentication. Choose your authentication flow below and get your first access token in minutes.

<Info>
  **New to OAuth2?** No worries! Just follow the code examples for your integration type - most developers are authenticated within 5 minutes.
</Info>

## Which Authentication Flow Do I Need?

```mermaid theme={null}
graph TD
    A[What are you building?] --> B{User-facing app?}
    B -->|Yes| C{Users login with<br/>ClearLine credentials?}
    B -->|No| D[POS terminal or<br/>background service?]

    C -->|Yes| E[Authorization Code<br/>Flow with PKCE]
    C -->|No| F{Running on<br/>secure server?}

    F -->|Yes| G[Client Credentials<br/>Flow]
    F -->|No| E

    D -->|Yes| G

    E -.->|Use this flow| H[Mobile apps<br/>Web apps<br/>Third-party integrations]
    G -.->|Use this flow| I[POS systems<br/>Server-to-server<br/>Automated services]

    style E fill:#4CAF50,color:#fff
    style G fill:#2196F3,color:#fff
    style H fill:#E8F5E9
    style I fill:#E3F2FD
```

<Warning>
  **Security Rule:** Never use Client Credentials Flow in mobile or browser apps. Client secrets must remain on secure servers only.
</Warning>

***

## Get Your Access Token

<Tabs>
  <Tab title="User Applications">
    ### Authorization Code Flow with PKCE

    **Best for:** Mobile apps, web applications, any app where users log in with their ClearLine credentials

    <Steps>
      <Step title="Generate PKCE Challenge" icon="code">
        Create a code verifier and challenge for security.

        <CodeGroup>
          ```javascript JavaScript theme={null}
          // Generate code_verifier (43-128 characters)
          const codeVerifier = generateRandomString(64);

          // Generate code_challenge
          const codeChallenge = base64UrlEncode(sha256(codeVerifier));

          function generateRandomString(length) {
            const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
            let result = '';
            const randomValues = crypto.getRandomValues(new Uint8Array(length));
            for (let i = 0; i < length; i++) {
              result += chars[randomValues[i] % chars.length];
            }
            return result;
          }
          ```

          ```python Python theme={null}
          import secrets
          import hashlib
          import base64

          # Generate code_verifier
          code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode('utf-8').rstrip('=')

          # Generate code_challenge
          challenge = hashlib.sha256(code_verifier.encode('utf-8')).digest()
          code_challenge = base64.urlsafe_b64encode(challenge).decode('utf-8').rstrip('=')
          ```

          ```csharp C# theme={null}
          using System.Security.Cryptography;
          using System.Text;

          // Generate code_verifier
          var bytes = new byte[32];
          RandomNumberGenerator.Fill(bytes);
          var codeVerifier = Convert.ToBase64String(bytes)
              .TrimEnd('=').Replace('+', '-').Replace('/', '_');

          // Generate code_challenge
          using var sha256 = SHA256.Create();
          var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
          var codeChallenge = Convert.ToBase64String(challengeBytes)
              .TrimEnd('=').Replace('+', '-').Replace('/', '_');
          ```
        </CodeGroup>

        <Tip>
          **Important:** Save the `code_verifier`! You'll need it in Step 3 to exchange the authorization code for tokens.
        </Tip>
      </Step>

      <Step title="Redirect User to Login" icon="arrow-right">
        Send the user to ClearLine's login page using the [GET /Account/Login](/api-reference/authentication/user-login--authorization-code-flow-with-pkce) endpoint.

        ```javascript theme={null}
        const authUrl = new URL('https://logintest.clearline.me/Account/Login');
        authUrl.searchParams.append('ReturnUrl', '/connect/authorize/callback');
        authUrl.searchParams.append('response_type', 'code');
        authUrl.searchParams.append('client_id', 'your-client-id');
        authUrl.searchParams.append('scope', 'clearline_api openid profile');
        authUrl.searchParams.append('redirect_uri', 'myapp://auth-callback');
        authUrl.searchParams.append('code_challenge', codeChallenge);
        authUrl.searchParams.append('code_challenge_method', 'S256');
        authUrl.searchParams.append('state', generateRandomString(32)); // CSRF protection

        // Redirect user
        window.location.href = authUrl.toString();
        ```

        The user logs in and authorizes your app. ClearLine redirects back to your `redirect_uri` with an authorization code:

        ```
        myapp://auth-callback?code=AUTH_CODE_HERE&state=...
        ```

        <Info>
          [View full API reference for /Account/Login →](/api-reference/authentication/user-login--authorization-code-flow-with-pkce)
        </Info>
      </Step>

      <Step title="Exchange Code for Access Token" icon="key">
        Trade the authorization code for an access token using the [POST /connect/token](/api-reference/authentication/get-access-token) endpoint.

        <CodeGroup>
          ```bash cURL theme={null}
          curl -X POST https://logintest.clearline.me/connect/token \
            -H "Content-Type: application/x-www-form-urlencoded" \
            -H "Authorization: Basic $(echo -n 'client_id:client_secret' | base64)" \
            -d "grant_type=authorization_code" \
            -d "code=AUTH_CODE_FROM_CALLBACK" \
            -d "redirect_uri=myapp://auth-callback" \
            -d "code_verifier=YOUR_CODE_VERIFIER"
          ```

          ```javascript JavaScript theme={null}
          const credentials = btoa(`${clientId}:${clientSecret}`);

          const response = await fetch('https://logintest.clearline.me/connect/token', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/x-www-form-urlencoded',
              'Authorization': `Basic ${credentials}`
            },
            body: new URLSearchParams({
              grant_type: 'authorization_code',
              code: authorizationCode,
              redirect_uri: 'myapp://auth-callback',
              code_verifier: codeVerifier
            })
          });

          const tokens = await response.json();
          ```

          ```python Python theme={null}
          import requests
          import base64

          credentials = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()

          response = requests.post(
              'https://logintest.clearline.me/connect/token',
              headers={
                  'Content-Type': 'application/x-www-form-urlencoded',
                  'Authorization': f'Basic {credentials}'
              },
              data={
                  'grant_type': 'authorization_code',
                  'code': authorization_code,
                  'redirect_uri': 'myapp://auth-callback',
                  'code_verifier': code_verifier
              }
          )

          tokens = response.json()
          ```

          ```csharp C# theme={null}
          using System.Net.Http;
          using System.Text;

          var client = new HttpClient();
          var credentials = Convert.ToBase64String(
              Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}")
          );

          client.DefaultRequestHeaders.Authorization = 
              new AuthenticationHeaderValue("Basic", credentials);

          var content = new FormUrlEncodedContent(new[]
          {
              new KeyValuePair<string, string>("grant_type", "authorization_code"),
              new KeyValuePair<string, string>("code", authorizationCode),
              new KeyValuePair<string, string>("redirect_uri", "myapp://auth-callback"),
              new KeyValuePair<string, string>("code_verifier", codeVerifier)
          });

          var response = await client.PostAsync(
              "https://logintest.clearline.me/connect/token", 
              content
          );
          var tokens = await response.Content.ReadAsStringAsync();
          ```
        </CodeGroup>

        **Response:**

        <ResponseField name="access_token" type="string" required>
          JWT access token - include in `Authorization: Bearer {token}` header for all API requests
        </ResponseField>

        <ResponseField name="token_type" type="string" required>
          Always "Bearer"
        </ResponseField>

        <ResponseField name="expires_in" type="integer" required>
          Token lifetime in seconds (typically 3600 = 1 hour)
        </ResponseField>

        <ResponseField name="refresh_token" type="string">
          Use to obtain new access tokens without re-authenticating the user
        </ResponseField>

        <ResponseField name="scope" type="string" required>
          Granted scopes (e.g., "clearline\_api openid profile")
        </ResponseField>

        <Info>
          [View full API reference for /connect/token →](/api-reference/authentication/get-access-token)
        </Info>
      </Step>
    </Steps>

    <Check>
      **You're authenticated!** Use the `access_token` in your API requests. See [Using Your Access Token](#using-your-access-token) below.
    </Check>
  </Tab>

  <Tab title="POS & Server Systems">
    ### Client Credentials Flow

    **Best for:** POS terminals, server-to-server integrations, automated services

    <Steps>
      <Step title="Request Access Token" icon="key">
        One simple API call to get your access token using the [POST /connect/token](/api-reference/authentication/get-access-token) endpoint.

        <CodeGroup>
          ```bash cURL theme={null}
          curl -X POST https://logintest.clearline.me/connect/token \
            -H "Content-Type: application/x-www-form-urlencoded" \
            -H "Authorization: Basic $(echo -n 'client_id:client_secret' | base64)" \
            -d "grant_type=client_credentials" \
            -d "scope=pos_integration"
          ```

          ```javascript JavaScript theme={null}
          const credentials = btoa(`${clientId}:${clientSecret}`);

          const response = await fetch('https://logintest.clearline.me/connect/token', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/x-www-form-urlencoded',
              'Authorization': `Basic ${credentials}`
            },
            body: new URLSearchParams({
              grant_type: 'client_credentials',
              scope: 'pos_integration'
            })
          });

          const tokens = await response.json();
          ```

          ```python Python theme={null}
          import requests
          import base64

          credentials = base64.b64encode(
              f"{client_id}:{client_secret}".encode()
          ).decode()

          response = requests.post(
              'https://logintest.clearline.me/connect/token',
              headers={
                  'Content-Type': 'application/x-www-form-urlencoded',
                  'Authorization': f'Basic {credentials}'
              },
              data={
                  'grant_type': 'client_credentials',
                  'scope': 'pos_integration'
              }
          )

          tokens = response.json()
          ```

          ```csharp C# theme={null}
          using System.Net.Http;
          using System.Text;

          var client = new HttpClient();
          var credentials = Convert.ToBase64String(
              Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}")
          );

          client.DefaultRequestHeaders.Authorization = 
              new AuthenticationHeaderValue("Basic", credentials);

          var content = new FormUrlEncodedContent(new[]
          {
              new KeyValuePair<string, string>("grant_type", "client_credentials"),
              new KeyValuePair<string, string>("scope", "pos_integration")
          });

          var response = await client.PostAsync(
              "https://logintest.clearline.me/connect/token", 
              content
          );
          var tokens = await response.Content.ReadAsStringAsync();
          ```
        </CodeGroup>

        **Response:**

        <ResponseField name="access_token" type="string" required>
          JWT access token for API requests
        </ResponseField>

        <ResponseField name="token_type" type="string" required>
          Always "Bearer"
        </ResponseField>

        <ResponseField name="expires_in" type="integer" required>
          Token lifetime in seconds (typically 3600)
        </ResponseField>

        <ResponseField name="scope" type="string" required>
          Granted scopes (e.g., "pos\_integration")
        </ResponseField>

        <Note>
          Client Credentials flow does **not** return a refresh token. Request a new token when the current one expires.
        </Note>

        <Info>
          [View full API reference for /connect/token →](/api-reference/authentication/get-access-token)
        </Info>
      </Step>
    </Steps>

    <Check>
      **You're authenticated!** That's it - one API call. See [Using Your Access Token](#using-your-access-token) below.
    </Check>
  </Tab>
</Tabs>

***

## Using Your Access Token

Once you have an access token, include it in the `Authorization` header of all API requests:

```bash theme={null}
GET https://public-api-demo.clearline.me/v2/pos/{posSystemId}/settings/lookup
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
```

### Token Lifecycle

<Accordion title="Active Token (0-3600 seconds)" defaultOpen icon="check">
  Use your token normally for API requests. It's valid and working.
</Accordion>

<Accordion title="Expiring Soon (< 5 minutes remaining)" icon="clock">
  **Best practice:** Refresh your token proactively before it expires.

  ```javascript theme={null}
  if (tokenExpiresAt < Date.now() + 300000) {
    // Refresh 5 minutes before expiry
    await refreshAccessToken();
  }
  ```
</Accordion>

<Accordion title="Expired Token (> 3600 seconds)" icon="triangle-exclamation">
  **Authorization Code Flow:** Use your `refresh_token` to get a new access token without re-authenticating the user.

  **Client Credentials Flow:** Request a new token using the same process as before.
</Accordion>

<Accordion title="Invalid Token (any time)" icon="xmark">
  If you receive a `401 Unauthorized` error, your token is invalid. Re-authenticate from the beginning.

  Common causes: token manually revoked, credentials changed, or security policy violation.
</Accordion>

***

## OAuth Scopes

Request the appropriate scopes based on your integration needs:

| Scope                 | Description                                           | Typical Use      |
| --------------------- | ----------------------------------------------------- | ---------------- |
| `clearline_api`       | General API access for user applications              | User-facing apps |
| `pos_integration`     | Full POS integration (transactions, loyalty, coupons) | POS terminals    |
| `coupons_integration` | Coupon-specific integration access                    | Coupon providers |
| `openid`              | User identity information (required for user auth)    | User-facing apps |
| `profile`             | User profile data (name, email, etc.)                 | User-facing apps |

**Request multiple scopes** by separating them with spaces:

```
scope=clearline_api openid profile
```

***

## Environments

ClearLine provides three environments for different stages of development:

| Environment    | Auth Base URL            | Purpose                             |
| -------------- | ------------------------ | ----------------------------------- |
| **Test**       | `logintest.clearline.me` | Development and integration testing |
| **Demo**       | `logindemo.clearline.me` | Client demonstrations and UAT       |
| **Production** | `login.clearline.me`     | Live production environment         |

<Warning>
  **Don't mix up Auth and API URLs!**

  * **Authentication/Token requests:** `login*.clearline.me` (OAuth endpoints)
  * **API business requests:** `public-api-*.clearline.me` (with your Bearer token)

  See [API Environments](/api-reference/environments) for complete environment configuration details.
</Warning>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="invalid_grant: The authorization code is invalid or expired" icon="triangle-exclamation">
    **Cause:** Authorization codes expire quickly (typically 5 minutes) or have already been used.

    **Solution:**

    * Request a new authorization code by redirecting the user to login again
    * Ensure you exchange the code immediately after receiving it
    * Verify you're not reusing the same code (codes are single-use only)
  </Accordion>

  <Accordion title="invalid_client: Client authentication failed" icon="key">
    **Cause:** Your `client_id` or `client_secret` is incorrect, or the Basic Auth header is malformed.

    **Solution:**

    * Verify credentials in the ClearLine Admin Portal
    * Ensure Basic Auth header format: `Basic {base64(client_id:client_secret)}`
    * Check for spaces or special characters in your credentials
    * Test your base64 encoding: `client_id:client_secret` should encode properly
  </Accordion>

  <Accordion title="invalid_request: code_verifier is required" icon="code">
    **Cause:** Missing `code_verifier` parameter when exchanging authorization code (PKCE flow).

    **Solution:**

    * Save the `code_verifier` before redirecting to login
    * Include it in the token request body
    * The verifier must exactly match the one used to generate `code_challenge`
  </Accordion>

  <Accordion title="unauthorized_client: The client is not authorized to use this grant type" icon="ban">
    **Cause:** Your client is not configured for the grant type you're trying to use.

    **Solution:**

    * Contact ClearLine support to verify your client configuration
    * Ensure you're using the correct flow for your client type:
      * User apps → Authorization Code Flow
      * POS/server apps → Client Credentials Flow
  </Accordion>

  <Accordion title="invalid_scope: The requested scope is invalid" icon="list">
    **Cause:** You're requesting a scope that doesn't exist or your client isn't authorized for.

    **Solution:**

    * Use valid scopes: `clearline_api`, `pos_integration`, `coupons_integration`, `openid`, `profile`
    * Check scope spelling and formatting (lowercase, underscore-separated)
    * Verify your client has permission for the requested scopes in Admin Portal
  </Accordion>
</AccordionGroup>

***

## Security Best Practices

<CardGroup cols={2}>
  <Card title="Protect Client Secrets" icon="lock" color="#F44336">
    **Never expose** `client_secret` in:

    * Mobile app code
    * Browser JavaScript
    * Public repositories
    * Client-side storage

    **Always store** secrets only on secure servers with proper encryption.
  </Card>

  <Card title="Validate State Parameter" icon="shield-check" color="#4CAF50">
    Always validate the `state` parameter in OAuth callbacks to prevent CSRF attacks.

    ```javascript theme={null}
    if (callbackState !== originalState) {
      throw new Error('CSRF attack detected');
    }
    ```
  </Card>

  <Card title="Use HTTPS Only" icon="globe" color="#2196F3">
    **Always use HTTPS** for:

    * Redirect URIs
    * Token endpoints
    * All API requests

    HTTP is not supported in production environments.
  </Card>

  <Card title="Implement Proactive Token Refresh" icon="arrows-rotate" color="#FF9800">
    Don't wait for 401 errors - refresh tokens before they expire:

    ```javascript theme={null}
    // Refresh 5 min before expiry
    if (expiresAt < Date.now() + 300000) {
      await refreshToken();
    }
    ```
  </Card>
</CardGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Detailed Implementation Guides" icon="book-open" href="/pos-integration">
    Step-by-step guides with complete code examples and advanced scenarios
  </Card>

  {" "}

  <Card title="Quick Start Tutorial" icon="rocket" href="/api-reference/quick-start">
    Make your first authenticated API request in 5 minutes
  </Card>

  {" "}

  <Card title="API Environments" icon="globe" href="/api-reference/environments">
    Learn about test, demo, and production environment configurations
  </Card>

  <Card title="POS Integration Workflows" icon="store" href="/pos-integration">
    Explore all available POS integration patterns and use cases
  </Card>
</CardGroup>
