Authentication Guide

Secure authentication and API key management

Published

November 20, 2025

Overview

Kai uses API key authentication for all API requests. This guide covers how to obtain, manage, and securely use API keys to authenticate your applications and integrations.

ImportantSecurity First

API keys provide full access to your Kai account. Treat them like passwords and never expose them in client-side code or public repositories.

Authentication Methods

Kai supports multiple authentication methods depending on your use case:

Method Use Case Security Level
API Keys Server-to-server, scripts, CI/CD High
OAuth 2.0 Third-party integrations, user delegation Very High
LTI 1.3 LMS integrations (Canvas, Blackboard, etc.) Very High
Session Tokens Web dashboard, temporary access Medium

API Keys

Obtaining an API Key

  1. Log in to your Kai dashboard: dashboard.kaitheai.com
  2. Navigate to SettingsAPI Keys
  3. Click “Generate New API Key”
  4. Configure key settings:
    • Name: Descriptive name (e.g., “Production Server”, “Development”)
    • Permissions: Select required scopes
    • Expiration: Set expiration date (or never)
  5. Copy the key: It will only be shown once
WarningSave Your Key

API keys are only displayed once during creation. Store it securely immediately. If lost, you must generate a new key.

API Key Format

Kai API keys follow this format:

kai_<environment>_<32-character-random-string>

Examples:

kai_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6  # Production
kai_test_x9y8z7w6v5u4t3s2r1q0p9o8n7m6l5k4  # Test/sandbox

Key Prefixes: - kai_live_: Production environment - kai_test_: Test/sandbox environment

Using API Keys

Include your API key in the Authorization header of every API request:

HTTP Header:

Authorization: Bearer kai_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

Example Request:

curl https://chi2api.com/v1/courses \
  -H "Authorization: Bearer kai_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
import requests

API_KEY = "kai_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

headers = {
    "Authorization": f"Bearer {API_KEY}"
}

response = requests.get(
    "https://chi2api.com/v1/courses",
    headers=headers
)
const API_KEY = "kai_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6";

const response = await fetch("https://chi2api.com/v1/courses", {
  headers: {
    "Authorization": `Bearer ${API_KEY}`
  }
});
import java.net.http.*;

String API_KEY = "kai_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6";

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://chi2api.com/v1/courses"))
    .header("Authorization", "Bearer " + API_KEY)
    .build();

HttpResponse<String> response = client.send(request,
    HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'uri'

api_key = "kai_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

uri = URI.parse("https://chi2api.com/v1/courses")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{api_key}"

response = Net::HTTP.start(uri.hostname, uri.port,  true) do |http|
  http.request(request)
end

API Key Permissions (Scopes)

Control what each API key can access using scopes:

Scope Description Risk Level
courses:read View course information Low
courses:write Create/update courses Medium
students:read View student data Medium
students:write Modify student records High
grading:read View grades Medium
grading:write Submit grades High
analytics:read Access analytics Low
feedback:read View feedback responses Medium
feedback:write Request feedback Low
admin:* Full administrative access Very High

Best Practice: Principle of Least Privilege

Only grant the minimum scopes needed for each key:

✅ Good: Integration key with only required scopes
   Scopes: courses:read, grading:write

❌ Bad: Everything gets admin:*
   Risk: Compromised key = full account access

Managing API Keys

List All Keys

View all active API keys:

GET /api-keys

Response:

{
  "keys": [
    {
      "key_id": "key_abc123",
      "name": "Production Server",
      "prefix": "kai_live_a1b2...",
      "scopes": ["courses:read", "grading:write"],
      "created": "2024-01-01T00:00:00Z",
      "last_used": "2024-01-15T14:30:00Z",
      "expires": null
    }
  ]
}

Rotate API Keys

When to rotate: - ⏰ Periodically (every 90 days recommended) - 🚨 After suspected compromise - 👤 When team member leaves - 🔄 After major system changes

Rotation process:

  1. Generate new key with same permissions
  2. Update all integrations to use new key
  3. Test thoroughly in staging/development
  4. Deploy to production
  5. Revoke old key after confirming new key works
TipZero-Downtime Rotation

Create the new key before revoking the old one. Both keys work simultaneously during transition period.

Revoke API Keys

Immediately disable a compromised key:

DELETE /api-keys/{key_id}

Dashboard: 1. Settings → API Keys 2. Find the key to revoke 3. Click “Revoke” 4. Confirm action

WarningImmediate Effect

Revoking a key takes effect immediately. Any requests using that key will fail.

OAuth 2.0

For third-party integrations or user-delegated access, use OAuth 2.0.

OAuth Flow

sequenceDiagram
    participant User
    participant ThirdParty as Third-Party App
    participant Kai as Kai Authorization Server
    participant API as Kai API

    User->>ThirdParty: Clicks "Connect with Kai"
    ThirdParty->>Kai: Redirects to authorization URL
    Kai->>User: Shows consent screen
    User->>Kai: Grants permission
    Kai->>ThirdParty: Redirects with authorization code
    ThirdParty->>Kai: Exchanges code for access token
    Kai->>ThirdParty: Returns access token
    ThirdParty->>API: Makes API request with token
    API->>ThirdParty: Returns data

Registering an OAuth Application

  1. DashboardSettingsOAuth Applications
  2. Click “New Application”
  3. Configure settings:
    • Application Name: Your app’s name
    • Redirect URIs: Where to send users after authorization
    • Scopes: Permissions your app needs
  4. Save to receive Client ID and Client Secret

Authorization Request

Redirect users to:

GET https://chi2api.com/oauth/authorize?
  response_type=code&
  client_id=YOUR_CLIENT_ID&
  redirect_uri=https://your-app.com/callback&
  scope=courses:read%20grading:write&
  state=random_string

Parameters:

Parameter Required Description
response_type Yes Always code for authorization code flow
client_id Yes Your application’s client ID
redirect_uri Yes Must match registered redirect URI
scope Yes Space-separated list of scopes
state Recommended Random string to prevent CSRF

Token Exchange

Exchange authorization code for access token:

POST https://chi2api.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=AUTHORIZATION_CODE&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET&
redirect_uri=https://your-app.com/callback

Response:

{
  "access_token": "kai_oauth_abc123...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "kai_refresh_xyz789...",
  "scope": "courses:read grading:write"
}

Using Access Tokens

Use access tokens the same way as API keys:

Authorization: Bearer kai_oauth_abc123...

Refreshing Tokens

Access tokens expire after 1 hour. Use refresh token to get new access token:

POST https://chi2api.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&
refresh_token=kai_refresh_xyz789...&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET

LTI 1.3 Authentication

For LMS integrations, Kai supports LTI 1.3 (Learning Tools Interoperability).

LTI Configuration

Required Information:

Field Value
OpenID Connect Login URL https://chi2api.com/lti/login
Target Link URI https://chi2api.com/lti/launch
Public JWK URL https://chi2api.com/.well-known/jwks.json
Redirect URIs https://chi2api.com/lti/callback

Required Claims:

  • sub - User identifier
  • https://purl.imsglobal.org/spec/lti/claim/context - Course context
  • https://purl.imsglobal.org/spec/lti/claim/roles - User roles

Canvas LTI Setup

See Installation Guide - Canvas Integration for detailed instructions.

Blackboard LTI Setup

See Installation Guide - Blackboard Integration for detailed instructions.

Security Best Practices

Storing API Keys

Never Do This

# ❌ Hardcoded in source code
API_KEY = "kai_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

# ❌ Committed to Git
# config.py
API_KEY = "kai_live_..."

# ❌ In client-side JavaScript
const API_KEY = "kai_live_...";
TipBest Practices
# ✅ Environment variables
import os
API_KEY = os.environ.get("KAI_API_KEY")

# ✅ Secret management service
from secret_manager import get_secret
API_KEY = get_secret("kai-api-key")

# ✅ Configuration files (gitignored)
import configparser
config = configparser.ConfigParser()
config.read("config.ini")  # .gitignore includes config.ini
API_KEY = config["kai"]["api_key"]

Environment Variables

Development:

# .env file (add to .gitignore)
KAI_API_KEY=kai_test_x9y8z7w6v5u4t3s2r1q0p9o8n7m6l5k4

Production:

# Set in deployment environment
export KAI_API_KEY=kai_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

Docker:

# docker-compose.yml
services:
  app:
    environment:
      - KAI_API_KEY=${KAI_API_KEY}

Secret Management Services

AWS Secrets Manager:

import boto3
import json

client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId='kai-api-key')
API_KEY = json.loads(response['SecretString'])['api_key']

HashiCorp Vault:

import hvac

client = hvac.Client(url='https://vault.example.com')
client.auth.approle.login(role_id='...', secret_id='...')
API_KEY = client.secrets.kv.v2.read_secret_version(
    path='kai-api-key'
)['data']['data']['key']

Google Secret Manager:

from google.cloud import secretmanager

client = secretmanager.SecretManagerServiceClient()
name = "projects/PROJECT_ID/secrets/kai-api-key/versions/latest"
response = client.access_secret_version(request={"name": name})
API_KEY = response.payload.data.decode("UTF-8")

Network Security

Use HTTPS Only:

# ✅ Always HTTPS
response = requests.get("https://chi2api.com/v1/courses")

# ❌ Never HTTP
response = requests.get("http://chi2api.com/v1/courses")

Verify SSL Certificates:

# ✅ Verify certificates (default)
response = requests.get(url, verify=True)

# ❌ Don't disable verification
response = requests.get(url, verify=False)  # Dangerous!

IP Allowlisting

Restrict API key usage to specific IP addresses:

Dashboard Configuration: 1. Settings → API Keys → [Your Key] → IP Restrictions 2. Add allowed IP addresses or CIDR ranges 3. Save changes

Example:

Allowed IPs:
- 203.0.113.0/24 (Office network)
- 198.51.100.42 (Production server)

Monitoring and Alerts

Enable alerts for: - Unusual API usage patterns - Failed authentication attempts - API key used from new IP address - Rate limit approaching

Configure in: Dashboard → Settings → Security → Alerts

Testing Authentication

Verify API Key

Test your API key is working:

curl https://chi2api.com/v1/auth/verify \
  -H "Authorization: Bearer YOUR_API_KEY"

Success Response:

{
  "valid": true,
  "key_id": "key_abc123",
  "scopes": ["courses:read", "grading:write"],
  "expires": null,
  "rate_limit": {
    "limit": 60,
    "remaining": 58,
    "reset": 1642262400
  }
}

Failure Response:

{
  "error": {
    "code": "invalid_api_key",
    "message": "The provided API key is invalid or has been revoked"
  }
}

Test OAuth Flow

Use Kai’s OAuth testing tool:

  1. Dashboard → Settings → OAuth Applications → [Your App] → Test
  2. Click “Start OAuth Flow”
  3. Complete authorization
  4. Verify token received

Troubleshooting

Common Authentication Errors

“Invalid API Key”

Possible causes: - Key was revoked - Key contains typos or extra spaces - Using test key in production (or vice versa)

Solution: - Verify key in dashboard - Regenerate if needed - Check environment variables

“Insufficient Permissions”

Possible causes: - API key lacks required scope - Trying to access resource outside permissions

Solution: - Check required scopes in Endpoints Reference - Generate new key with appropriate scopes

“Rate Limit Exceeded”

Possible causes: - Too many requests in short period - Multiple services sharing one key

Solution: - Implement exponential backoff - Create separate keys for different services - Upgrade plan for higher limits

Getting Help


For integration examples and workflows, see the Integration Guide.