Skip to Content
Authentication

Authentication

The Nexus Platform uses OAuth2-style bearer tokens for authenticating requests to the public API. This guide covers how to obtain and use access tokens, manage refresh tokens, and follow security best practices.

All public API endpoints require authentication using bearer tokens via the Authorization header, with the exception of health check and query endpoints, which are publicly accessible.

Overview

The Nexus Platform supports two token-based authentication flows:

  1. Client Credentials Flow - Exchange client credentials for an access token
  2. Refresh Token Flow - Exchange a refresh token for a new access token (optional, must be enabled per client)

Token Types

Token TypePurposeLifetime
Access TokenAuthenticate API requestsConfigurable (default: 24h)
Refresh TokenObtain new access tokens without re-authenticatingConfigurable (default: 30d)

Quick Start

Here’s how to get your first access token and make an authenticated request:

Step 1: Create an Access Token Client

First, create an access token client in your workspace settings:

  1. Navigate to Workspace Settings > Access Tokens

  2. Click Create Client

  3. Configure the client:

    • Name: A descriptive name (e.g., “Production API Client”)
    • Scopes: Select the permissions your client needs (e.g., query:execute, sessions:read)
    • Expiration: Set when the client credentials expire (optional)
    • Access Token TTL: How long each access token remains valid (default: 24h)
    • Enable Refresh Tokens: Check this if you want long-lived sessions (optional)
  4. Save the credentials - The client_secret is only shown once! Store it securely.

Important: Save your client_id and client_secret immediately. The secret cannot be retrieved later.

Step 2: Obtain an Access Token

// Obtain an access token using client credentials
const tokenResponse = await fetch("https://nexus-api.uat.knowbl.com/api/v2/auth/access-tokens", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET,
  }),
});

const tokenData = await tokenResponse.json();
console.log("Access Token:", tokenData.access_token);
console.log("Expires In:", tokenData.expires_in, "seconds");

// Save the token for use in API requests
const accessToken = tokenData.access_token;

Alternative: Using HTTP Basic Authentication:

You can also authenticate using HTTP Basic Auth with the Authorization header:

const tokenResponse = await fetch("https://nexus-api.uat.knowbl.com/api/v2/auth/access-tokens", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    // Base64 encode "client_id:client_secret"
    Authorization: "Basic " + btoa(`${CLIENT_ID}:${CLIENT_SECRET}`),
  },
  body: JSON.stringify({
    // Optional: request specific scopes (defaults to all client scopes)
    scope: "query:execute sessions:read",
  }),
});

if (!tokenResponse.ok) {
  const error = await tokenResponse.json();
  console.error("Authentication failed:", error);
  throw new Error(error.error_description);
}

const { access_token, expires_in, scope } = await tokenResponse.json();

console.log("Access token obtained successfully");
console.log("Expires in:", expires_in, "seconds");
console.log("Scopes:", scope);

Step 3: Make Authenticated Requests

Use the access token to make API requests:

// Use the access token to make an authenticated API request
const response = await fetch("https://nexus-api.uat.knowbl.com/api/v2/query", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    // Include the access token in the Authorization header
    Authorization: `Bearer ${ACCESS_TOKEN}`,
  },
  body: JSON.stringify({
    experienceId: "exp_abc123",
    question: "What is your return policy?",
  }),
});

const data = await response.json();
console.log("Query Response:", data);

Client Credentials Flow

The client credentials flow exchanges your client ID and secret for an access token. This is the primary authentication method for backend services and server-to-server communication.

How It Works

Request Format

// Alternative: Include credentials in request body
const tokenResponse = await fetch("https://nexus-api.uat.knowbl.com/api/v2/auth/access-tokens", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET,
    // Optional: request specific scopes
    scope: "query:execute sessions:read",
  }),
});

if (!tokenResponse.ok) {
  const error = await tokenResponse.json();
  console.error("Authentication failed:", error);
  throw new Error(error.error_description);
}

const { access_token, expires_in } = await tokenResponse.json();

Alternative: HTTP Basic Authentication

You can also authenticate using HTTP Basic Auth with the Authorization header:

const tokenResponse = await fetch("https://nexus-api.uat.knowbl.com/api/v2/auth/access-tokens", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    // Base64 encode "client_id:client_secret"
    Authorization: "Basic " + btoa(`${CLIENT_ID}:${CLIENT_SECRET}`),
  },
  body: JSON.stringify({
    // Optional: request specific scopes (defaults to all client scopes)
    scope: "query:execute sessions:read",
  }),
});

if (!tokenResponse.ok) {
  const error = await tokenResponse.json();
  console.error("Authentication failed:", error);
  throw new Error(error.error_description);
}

const { access_token, expires_in, scope } = await tokenResponse.json();

console.log("Access token obtained successfully");
console.log("Expires in:", expires_in, "seconds");
console.log("Scopes:", scope);

Response

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 86400,
  "scope": "query:execute sessions:read sessions:write"
}

If refresh tokens are enabled:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 86400,
  "scope": "query:execute sessions:read sessions:write",
  "refresh_token": "nxs_refresh_abc123...",
  "refresh_token_expires_in": 2592000
}

Refresh Token Flow

Refresh tokens allow your application to obtain new access tokens without re-authenticating with client credentials. This is useful for long-running applications and reduces credential exposure.

Refresh tokens must be explicitly enabled when creating an access token client. They are disabled by default.

How It Works

Token Rotation

The Nexus Platform implements automatic token rotation for security:

  • Each refresh token can only be used once
  • Using a refresh token returns a new access token and a new refresh token
  • The old refresh token is immediately invalidated
  • If an old refresh token is reused (replay attack), all tokens associated with the client are revoked

Request Format

// Exchange a refresh token for a new access token
const refreshResponse = await fetch("https://nexus-api.uat.knowbl.com/api/v2/auth/refresh", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    refresh_token: REFRESH_TOKEN,
  }),
});

if (!refreshResponse.ok) {
  const error = await refreshResponse.json();

  if (
    error.error === "invalid_token" ||
    error.error === "token_reuse_detected"
  ) {
    // Refresh token is invalid or was reused - re-authenticate with client credentials
    console.error("Refresh failed:", error.error_description);
    console.log("Re-authenticating with client credentials...");
    // Fall back to client credentials flow
  }

  throw new Error(error.error_description);
}

const { access_token, refresh_token, expires_in, refresh_token_expires_in } =
  await refreshResponse.json();

console.log("Token refreshed successfully");
console.log("New access token expires in:", expires_in, "seconds");
console.log(
  "New refresh token expires in:",
  refresh_token_expires_in,
  "seconds",
);

// IMPORTANT: Store the new refresh token and discard the old one
// The old refresh token is now invalid and cannot be reused

Response

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 86400,
  "scope": "query:execute sessions:read sessions:write",
  "refresh_token": "nxs_refresh_xyz789...",
  "refresh_token_expires_in": 2592000
}

Access Token Scopes

Scopes control what operations your access token can perform. When creating a client, select only the scopes you need (principle of least privilege).

Available Scopes

ScopeDescriptionEndpoints
*Full access to all endpointsAll endpoints
query:executeExecute queries and manage sessions/v2/query, /v2/sessions/*
sessions:readRead session dataGET /v2/sessions/*
sessions:writeCreate and update sessionsPOST /v2/sessions, PATCH /v2/sessions/:id/metadata
sessions:completeMark sessions as completePOST /v2/sessions/:id/complete
data-ingestion:readView ingestion jobsGET /v2/data-ingestion/jobs/*
data-ingestion:writeSubmit ingestion jobsPOST /v2/data-ingestion/jobs
data-ingestion:deleteCancel ingestion jobsDELETE /v2/data-ingestion/jobs/:id
analytics:readAccess analytics dataGET /v2/analytics/*

Scope Format

When making a token request, you can optionally request specific scopes:

{
  "scope": "query:execute sessions:read"
}

The returned token will be limited to the intersection of:

  • Scopes requested in the token request
  • Scopes configured on the client

If no scope is specified in the request, the token receives all scopes configured on the client.

Making Authenticated Requests

Once you have an access token, include it in the Authorization header of every API request:

Authorization: Bearer YOUR_ACCESS_TOKEN

Example: Execute a Query

// Execute a query with authentication
const queryResponse = await fetch("https://nexus-api.uat.knowbl.com/api/v2/query", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${ACCESS_TOKEN}`,
  },
  body: JSON.stringify({
    experienceId: "exp_abc123",
    question: "What are your business hours?",
    sessionId: "session_xyz789", // Optional: continue existing session
  }),
});

if (!queryResponse.ok) {
  if (queryResponse.status === 401) {
    console.error("Access token expired or invalid - refresh token needed");
    // Refresh your access token using the refresh token flow
  }
  throw new Error(`Query failed: ${queryResponse.statusText}`);
}

const queryData = await queryResponse.json();
console.log("Answer:", queryData.answer);
console.log("Session ID:", queryData.sessionId);

Example: List Sessions

// List sessions for an experience with authentication
const sessionsResponse = await fetch(
  "https://nexus-api.uat.knowbl.com/api/v2/sessions?experienceId=exp_abc123&limit=10",
  {
    method: "GET",
    headers: {
      Authorization: `Bearer ${ACCESS_TOKEN}`,
    },
  },
);

if (!sessionsResponse.ok) {
  if (sessionsResponse.status === 401) {
    console.error("Access token expired or invalid - refresh token needed");
    // Refresh your access token using the refresh token flow
  } else if (sessionsResponse.status === 403) {
    console.error("Insufficient permissions - token lacks sessions:read scope");
    // Obtain a new token with the required scope
  }
  throw new Error(`Request failed: ${sessionsResponse.statusText}`);
}

const { sessions, pagination } = await sessionsResponse.json();

console.log(`Found ${sessions.length} sessions`);
sessions.forEach((session) => {
  console.log(`- ${session.sessionId}: ${session.turnCount} turns`);
});

Token Lifecycle Management

When to Refresh

Access tokens expire after their configured TTL (default: 24 hours). Your application should:

  1. Track expiration: Store the expires_in value from the token response
  2. Refresh proactively: Refresh the token before it expires (e.g., 5 minutes before expiration)
  3. Handle 401 errors: If you receive a 401 Unauthorized, refresh immediately

Example: Token Manager

class TokenManager {
  constructor(clientId, clientSecret) {
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.accessToken = null;
    this.refreshToken = null;
    this.expiresAt = null;
    this.apiBase = "https://nexus-api.uat.knowbl.com/api";
  }

  async getAccessToken() {
    // Return cached token if still valid (with 5-minute buffer)
    if (this.accessToken && this.expiresAt > Date.now() + 5 * 60 * 1000) {
      return this.accessToken;
    }

    // Try to refresh if we have a refresh token
    if (this.refreshToken) {
      try {
        await this.refreshAccessToken();
        return this.accessToken;
      } catch (error) {
        console.warn("Token refresh failed, re-authenticating:", error.message);
        // Fall through to client credentials flow
      }
    }

    // Authenticate with client credentials
    await this.authenticate();
    return this.accessToken;
  }

  async authenticate() {
    const response = await fetch(`${this.apiBase}/v2/auth/access-tokens`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        client_id: this.clientId,
        client_secret: this.clientSecret,
      }),
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(`Authentication failed: ${error.error_description}`);
    }

    const data = await response.json();
    this.accessToken = data.access_token;
    this.refreshToken = data.refresh_token || null;
    this.expiresAt = Date.now() + data.expires_in * 1000;

    console.log("Authenticated successfully");
    return this.accessToken;
  }

  async refreshAccessToken() {
    if (!this.refreshToken) {
      throw new Error("No refresh token available");
    }

    const response = await fetch(`${this.apiBase}/v2/auth/refresh`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        refresh_token: this.refreshToken,
      }),
    });

    if (!response.ok) {
      const error = await response.json();

      // Clear tokens on refresh failure
      this.accessToken = null;
      this.refreshToken = null;
      this.expiresAt = null;

      throw new Error(`Token refresh failed: ${error.error_description}`);
    }

    const data = await response.json();
    this.accessToken = data.access_token;
    this.refreshToken = data.refresh_token; // New refresh token
    this.expiresAt = Date.now() + data.expires_in * 1000;

    console.log("Token refreshed successfully");
    return this.accessToken;
  }

  async request(url, options = {}) {
    const token = await this.getAccessToken();

    const response = await fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        Authorization: `Bearer ${token}`,
      },
    });

    // Handle 401 by refreshing token and retrying once
    if (response.status === 401) {
      console.log("Received 401, refreshing token and retrying...");

      // Clear current token and get a fresh one
      this.accessToken = null;
      const newToken = await this.getAccessToken();

      // Retry the request with new token
      return fetch(url, {
        ...options,
        headers: {
          ...options.headers,
          Authorization: `Bearer ${newToken}`,
        },
      });
    }

    return response;
  }
}

// Usage example
const tokenManager = new TokenManager(
  process.env.NEXUS_CLIENT_ID,
  process.env.NEXUS_CLIENT_SECRET,
);

// Make authenticated requests
const queryResponse = await tokenManager.request("https://nexus-api.uat.knowbl.com/api/v2/query", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    experienceId: "exp_abc123",
    question: "What is your return policy?",
  }),
});

const data = await queryResponse.json();
console.log("Response:", data);

Error Responses

Authentication Errors

StatusError CodeDescriptionSolution
401invalid_clientInvalid client credentialsVerify your client_id and client_secret
401invalid_clientBase64 contains newlinesUse base64 -w0 to prevent line wrapping
401invalid_clientBase64 contains invalid charactersEnsure single base64 encoding of client_id:client_secret
401invalid_clientMissing colon separatorFormat must be client_id:client_secret before encoding
401invalid_tokenRefresh token is invalid, expired, or already usedObtain a new access token using client credentials
401token_reuse_detectedRefresh token was reused (security violation)Token family revoked. Re-authenticate with client credentials
403insufficient_scopeToken lacks required scope for operationRequest token with appropriate scopes

Example Error Response

{
  "error": "invalid_client",
  "error_description": "Invalid client credentials"
}

Troubleshooting

Common HTTP Basic Auth Issues

When using HTTP Basic Authentication, the most common issues are related to base64 encoding:

Base64 Line Wrapping: Many command-line base64 tools wrap output at 64 or 76 characters by default. This breaks the Authorization header. Always use single-line base64.

Multi-line Base64 (Most Common)

Error: "Base64-encoded credentials contain newline characters..."

Cause: The base64 command on Linux/macOS wraps long output across multiple lines by default.

Solution: Use the -w0 flag to disable line wrapping:

# Wrong - wraps at 76 characters echo -n 'client_id:client_secret' | base64 # Correct - single line output echo -n 'client_id:client_secret' | base64 -w0

Invalid Base64 Characters

Error: "Base64-encoded credentials contain invalid characters..."

Cause: The encoded string contains characters outside the base64 alphabet (A-Z, a-z, 0-9, +, /, =).

Solution: Ensure you’re encoding the raw credentials, not already-encoded data:

# Wrong - double encoding echo -n 'client_id:client_secret' | base64 | base64 # Correct - single encoding echo -n 'client_id:client_secret' | base64 -w0

Missing Colon Separator

Error: "Decoded credentials missing ':' separator..."

Cause: The decoded string doesn’t contain a colon between client_id and client_secret.

Solution: Ensure the format before encoding is client_id:client_secret:

# Wrong - missing colon echo -n 'client_idclient_secret' | base64 -w0 # Correct - colon separator echo -n 'client_id:client_secret' | base64 -w0

cURL Examples

Here’s a complete cURL example with correct base64 encoding:

# Step 1: Create properly encoded credentials CREDENTIALS=$(echo -n "${CLIENT_ID}:${CLIENT_SECRET}" | base64 -w0) # Step 2: Make the request curl -X POST https://api.nexus.knowbl.com/api/v2/auth/access-tokens \ -H "Authorization: Basic ${CREDENTIALS}" \ -H "Content-Type: application/json" \ -d '{"grant_type": "client_credentials"}'

The -n flag in echo -n prevents a trailing newline from being included in the encoded string.

Security Best Practices

Credential Storage

Never commit credentials to version control or expose them in client-side code.

  • Client Secret: Store in environment variables or a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.)
  • Access Tokens: Store in memory when possible. If persistence is needed, use secure encrypted storage.
  • Refresh Tokens: Store in secure, encrypted persistent storage. These are long-lived and should be protected like passwords.

Token Expiration

  • Use short-lived access tokens (24 hours or less) to minimize impact of token compromise
  • Use refresh tokens for long-running applications instead of using long-lived access tokens
  • Set appropriate expirations and rotate client credentials based on your security requirements

Scope Minimization

  • Request only the scopes your application needs (principle of least privilege)
  • Create separate clients for different services/environments with appropriate scope restrictions
  • Audit scope usage regularly and create new clients with reduced scopes to replace over-privileged clients

Network Security

  • Always use HTTPS in production - never send tokens over unencrypted connections
  • Implement rate limiting in your application to prevent token exhaustion
  • Monitor for unusual authentication patterns (many failed attempts, token reuse, etc.)

Token Rotation

  • Implement automatic token rotation before expiration
  • Store refresh tokens securely and never log them
  • Handle token rotation failures gracefully (fall back to client credentials flow)

Revocation

If you suspect credential compromise:

  1. Immediately revoke the client in Workspace Settings > Access Tokens
  2. Generate new client credentials
  3. Update your application configuration
  4. Audit recent API activity for suspicious requests
  5. Report your suspicions to Knowbl so we can monitor for suspicious activity and system compromise

Additional Resources

Support

If you have questions or need assistance with authentication:

  • Check the troubleshooting section above
  • Review your client configuration in Workspace Settings > Access Tokens
  • Contact support with your workspace ID and client ID (never share your client secret)