Skip to main content

CODITECT Cloud API Integration Guide

Version: 1.0.0 Base URL: https://api.coditect.ai (Production) | http://localhost:8000 (Development) Last Updated: 2026-01-08 Companion: api-reference.md for complete endpoint documentation


Table of Contents


Quick Start

1. Get Your API Credentials

# Register for an account
curl -X POST https://api.coditect.ai/api/v1/auth/register/ \
-H "Content-Type: application/json" \
-d '{"email": "dev@example.com", "password": "SecurePass123!", "full_name": "Developer", "company_name": "My Company"}'

2. Obtain Access Token

# Login to get JWT token
curl -X POST https://api.coditect.ai/api/v1/auth/token/ \
-H "Content-Type: application/json" \
-d '{"email": "dev@example.com", "password": "SecurePass123!"}'

3. Make Authenticated Requests

# Use the token in subsequent requests
curl https://api.coditect.ai/api/v1/licenses/ \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Authentication

JWT Token Flow

┌─────────────┐     POST /auth/token/      ┌─────────────┐
│ Client │ ─────────────────────────► │ Server │
│ │ ◄───────────────────────── │ │
└─────────────┘ {access_token, refresh} └─────────────┘

│ Include in all requests:
│ Authorization: Bearer <access_token>

┌─────────────────────────────────────────────────────────┐
│ GET /api/v1/licenses/ │
│ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI... │
└─────────────────────────────────────────────────────────┘

Token Lifetimes

Token TypeExpirationRefresh Strategy
Access Token15 minutesUse refresh token before expiry
Refresh Token7 daysRe-authenticate when expired

Token Refresh Flow

When your access token expires, use the refresh token to get a new one:

curl -X POST https://api.coditect.ai/api/v1/auth/token/refresh/ \
-H "Content-Type: application/json" \
-d '{"refresh": "YOUR_REFRESH_TOKEN"}'

Code Examples by Language

Python Examples

Setup

import requests
from typing import Optional, Dict, Any

class CoditectClient:
"""CODITECT API Client for Python applications."""

def __init__(self, base_url: str = "https://api.coditect.ai"):
self.base_url = base_url
self.access_token: Optional[str] = None
self.refresh_token: Optional[str] = None
self.session = requests.Session()

def _headers(self) -> Dict[str, str]:
"""Build request headers with authentication."""
headers = {"Content-Type": "application/json"}
if self.access_token:
headers["Authorization"] = f"Bearer {self.access_token}"
return headers

def _request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]:
"""Make an authenticated request with automatic token refresh."""
url = f"{self.base_url}{endpoint}"
response = self.session.request(
method, url, headers=self._headers(), **kwargs
)

# Handle token expiration
if response.status_code == 401 and self.refresh_token:
self._refresh_access_token()
response = self.session.request(
method, url, headers=self._headers(), **kwargs
)

response.raise_for_status()
return response.json()

Authentication

def login(self, email: str, password: str) -> Dict[str, Any]:
"""Authenticate and store tokens."""
response = self.session.post(
f"{self.base_url}/api/v1/auth/token/",
headers={"Content-Type": "application/json"},
json={"email": email, "password": password}
)
response.raise_for_status()
data = response.json()

self.access_token = data["access"]
self.refresh_token = data["refresh"]
return data

def _refresh_access_token(self) -> None:
"""Refresh the access token using refresh token."""
response = self.session.post(
f"{self.base_url}/api/v1/auth/token/refresh/",
headers={"Content-Type": "application/json"},
json={"refresh": self.refresh_token}
)
response.raise_for_status()
self.access_token = response.json()["access"]

def register(self, email: str, password: str, full_name: str,
company_name: str) -> Dict[str, Any]:
"""Register a new user account."""
return self._request(
"POST", "/api/v1/auth/register/",
json={
"email": email,
"password": password,
"full_name": full_name,
"company_name": company_name
}
)

License Management

def list_licenses(self, is_active: bool = None, tier: str = None) -> Dict[str, Any]:
"""List all licenses for the organization."""
params = {}
if is_active is not None:
params["is_active"] = str(is_active).lower()
if tier:
params["tier"] = tier
return self._request("GET", "/api/v1/licenses/", params=params)

def get_license(self, license_id: str) -> Dict[str, Any]:
"""Get detailed information about a specific license."""
return self._request("GET", f"/api/v1/licenses/{license_id}/")

def validate_license(self, license_key: str) -> Dict[str, Any]:
"""Validate a license key (public endpoint)."""
response = self.session.post(
f"{self.base_url}/api/v1/licenses/validate/",
headers={"Content-Type": "application/json"},
json={"key_string": license_key}
)
return response.json()

def download_license(self, license_id: str, output_path: str) -> str:
"""Download a signed license file."""
response = self.session.get(
f"{self.base_url}/api/v1/licenses/{license_id}/download/",
headers=self._headers()
)
response.raise_for_status()

with open(output_path, 'w') as f:
f.write(response.text)
return output_path

License Seat Management

def acquire_seat(self, license_key: str, hardware_id: str,
client_version: str = "1.0.0") -> Dict[str, Any]:
"""Acquire a license seat for this machine."""
return self._request(
"POST", "/api/v1/licenses/acquire/",
json={
"license_key": license_key,
"hardware_id": hardware_id,
"ip_address": self._get_ip_address(),
"client_version": client_version
}
)

def heartbeat(self, session_id: str) -> Dict[str, Any]:
"""Send heartbeat to keep session alive."""
return self._request(
"PATCH", f"/api/v1/licenses/sessions/{session_id}/heartbeat/"
)

def release_seat(self, session_id: str) -> Dict[str, Any]:
"""Release license seat when done."""
return self._request(
"DELETE", f"/api/v1/licenses/sessions/{session_id}/"
)

def _get_ip_address(self) -> str:
"""Get local IP address."""
import socket
return socket.gethostbyname(socket.gethostname())

Subscription Management

def get_plans(self) -> Dict[str, Any]:
"""Get available subscription plans."""
response = self.session.get(
f"{self.base_url}/api/v1/subscriptions/plans/"
)
return response.json()

def get_current_subscription(self) -> Dict[str, Any]:
"""Get current organization subscription."""
return self._request("GET", "/api/v1/subscriptions/current/")

def create_checkout(self, price_id: str, seats: int,
success_url: str, cancel_url: str) -> Dict[str, Any]:
"""Create a Stripe checkout session."""
return self._request(
"POST", "/api/v1/subscriptions/checkout/",
json={
"price_id": price_id,
"seats": seats,
"success_url": success_url,
"cancel_url": cancel_url
}
)

def update_subscription(self, seats: int) -> Dict[str, Any]:
"""Update subscription seat count."""
return self._request(
"POST", "/api/v1/subscriptions/update/",
json={"seats": seats}
)

def cancel_subscription(self, immediately: bool = False) -> Dict[str, Any]:
"""Cancel subscription."""
return self._request(
"POST", "/api/v1/subscriptions/cancel/",
json={"immediately": immediately}
)

Complete Example Usage

# Initialize client
client = CoditectClient()

# Login
client.login("user@example.com", "password123")

# Get licenses
licenses = client.list_licenses(is_active=True)
print(f"Found {licenses['count']} active licenses")

# Acquire a seat
session = client.acquire_seat(
license_key="PROD-2025-ABCD-1234",
hardware_id="abc123def456"
)
session_id = session["session_id"]

# Keep session alive with heartbeat (call every 3-5 minutes)
import time
import threading

def heartbeat_loop():
while True:
try:
client.heartbeat(session_id)
time.sleep(180) # 3 minutes
except Exception as e:
print(f"Heartbeat failed: {e}")
break

heartbeat_thread = threading.Thread(target=heartbeat_loop, daemon=True)
heartbeat_thread.start()

# ... do work ...

# Release seat when done
client.release_seat(session_id)

JavaScript/TypeScript Examples

Setup (TypeScript)

interface TokenResponse {
access: string;
refresh: string;
}

interface License {
id: string;
key_string: string;
tier: string;
is_active: boolean;
expiry_date: string;
}

interface Session {
session_id: string;
license_key: string;
expires_at: string;
is_active: boolean;
seats_remaining: number;
}

class CoditectClient {
private baseUrl: string;
private accessToken: string | null = null;
private refreshToken: string | null = null;

constructor(baseUrl: string = "https://api.coditect.ai") {
this.baseUrl = baseUrl;
}

private async request<T>(
method: string,
endpoint: string,
body?: object
): Promise<T> {
const headers: HeadersInit = {
"Content-Type": "application/json",
};

if (this.accessToken) {
headers["Authorization"] = `Bearer ${this.accessToken}`;
}

let response = await fetch(`${this.baseUrl}${endpoint}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});

// Handle token expiration
if (response.status === 401 && this.refreshToken) {
await this.refreshAccessToken();
headers["Authorization"] = `Bearer ${this.accessToken}`;
response = await fetch(`${this.baseUrl}${endpoint}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
}

if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || `HTTP ${response.status}`);
}

return response.json();
}

Authentication

  async login(email: string, password: string): Promise<TokenResponse> {
const response = await fetch(`${this.baseUrl}/api/v1/auth/token/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});

if (!response.ok) {
throw new Error("Authentication failed");
}

const data: TokenResponse = await response.json();
this.accessToken = data.access;
this.refreshToken = data.refresh;
return data;
}

private async refreshAccessToken(): Promise<void> {
const response = await fetch(
`${this.baseUrl}/api/v1/auth/token/refresh/`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh: this.refreshToken }),
}
);

if (!response.ok) {
throw new Error("Token refresh failed");
}

const data = await response.json();
this.accessToken = data.access;
}

async register(
email: string,
password: string,
fullName: string,
companyName: string
): Promise<object> {
return this.request("POST", "/api/v1/auth/register/", {
email,
password,
full_name: fullName,
company_name: companyName,
});
}

License Management

  async listLicenses(isActive?: boolean, tier?: string): Promise<{ licenses: License[]; count: number }> {
const params = new URLSearchParams();
if (isActive !== undefined) params.append("is_active", String(isActive));
if (tier) params.append("tier", tier);

const query = params.toString() ? `?${params}` : "";
return this.request("GET", `/api/v1/licenses/${query}`);
}

async getLicense(licenseId: string): Promise<License> {
return this.request("GET", `/api/v1/licenses/${licenseId}/`);
}

async validateLicense(licenseKey: string): Promise<{ valid: boolean; license?: License }> {
const response = await fetch(`${this.baseUrl}/api/v1/licenses/validate/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key_string: licenseKey }),
});
return response.json();
}

License Seat Management

  async acquireSeat(
licenseKey: string,
hardwareId: string,
clientVersion: string = "1.0.0"
): Promise<Session> {
return this.request("POST", "/api/v1/licenses/acquire/", {
license_key: licenseKey,
hardware_id: hardwareId,
ip_address: "0.0.0.0", // Will be detected server-side
client_version: clientVersion,
});
}

async heartbeat(sessionId: string): Promise<Session> {
return this.request("PATCH", `/api/v1/licenses/sessions/${sessionId}/heartbeat/`);
}

async releaseSeat(sessionId: string): Promise<{ message: string }> {
return this.request("DELETE", `/api/v1/licenses/sessions/${sessionId}/`);
}
}

React Hook Example

import { useState, useEffect, useCallback } from 'react';

interface UseLicenseSession {
session: Session | null;
isActive: boolean;
error: Error | null;
acquire: (licenseKey: string) => Promise<void>;
release: () => Promise<void>;
}

function useLicenseSession(client: CoditectClient): UseLicenseSession {
const [session, setSession] = useState<Session | null>(null);
const [error, setError] = useState<Error | null>(null);

// Heartbeat interval
useEffect(() => {
if (!session) return;

const interval = setInterval(async () => {
try {
const updated = await client.heartbeat(session.session_id);
setSession(updated);
} catch (err) {
setError(err as Error);
setSession(null);
}
}, 180000); // 3 minutes

return () => clearInterval(interval);
}, [session, client]);

const acquire = useCallback(async (licenseKey: string) => {
try {
const hardwareId = await getHardwareId(); // Implement based on platform
const newSession = await client.acquireSeat(licenseKey, hardwareId);
setSession(newSession);
setError(null);
} catch (err) {
setError(err as Error);
}
}, [client]);

const release = useCallback(async () => {
if (!session) return;
try {
await client.releaseSeat(session.session_id);
setSession(null);
} catch (err) {
setError(err as Error);
}
}, [session, client]);

return {
session,
isActive: session?.is_active ?? false,
error,
acquire,
release,
};
}

Complete Example (Node.js/Browser)

// Initialize client
const client = new CoditectClient();

// Login
await client.login("user@example.com", "password123");

// List licenses
const { licenses, count } = await client.listLicenses(true);
console.log(`Found ${count} active licenses`);

// Acquire seat
const session = await client.acquireSeat(
"PROD-2025-ABCD-1234",
"hardware-fingerprint-123"
);

// Set up heartbeat
const heartbeatInterval = setInterval(async () => {
try {
await client.heartbeat(session.session_id);
} catch (error) {
console.error("Heartbeat failed:", error);
clearInterval(heartbeatInterval);
}
}, 180000); // 3 minutes

// ... do work ...

// Cleanup on exit
process.on("beforeExit", async () => {
clearInterval(heartbeatInterval);
await client.releaseSeat(session.session_id);
});

cURL Examples

Authentication

# Register new user
curl -X POST https://api.coditect.ai/api/v1/auth/register/ \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "SecurePass123!",
"full_name": "John Doe",
"company_name": "Acme Inc"
}'

# Login and get tokens
curl -X POST https://api.coditect.ai/api/v1/auth/token/ \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "SecurePass123!"}'

# Response:
# {"access": "eyJhbGc...", "refresh": "eyJhbGc..."}

# Refresh access token
curl -X POST https://api.coditect.ai/api/v1/auth/token/refresh/ \
-H "Content-Type: application/json" \
-d '{"refresh": "YOUR_REFRESH_TOKEN"}'

# Verify email
curl "https://api.coditect.ai/api/v1/auth/verify-email/?token=VERIFICATION_TOKEN"

# Request password reset
curl -X POST https://api.coditect.ai/api/v1/auth/forgot-password/ \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com"}'

# Reset password
curl -X POST https://api.coditect.ai/api/v1/auth/reset-password/ \
-H "Content-Type: application/json" \
-d '{"token": "RESET_TOKEN", "new_password": "NewSecurePass123!"}'

License Operations

# List all licenses
curl https://api.coditect.ai/api/v1/licenses/ \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# List active licenses only
curl "https://api.coditect.ai/api/v1/licenses/?is_active=true" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Get specific license
curl https://api.coditect.ai/api/v1/licenses/LICENSE_UUID/ \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Download license file
curl https://api.coditect.ai/api/v1/licenses/LICENSE_UUID/download/ \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-o license.json

# Validate license (public endpoint)
curl -X POST https://api.coditect.ai/api/v1/licenses/validate/ \
-H "Content-Type: application/json" \
-d '{"key_string": "PROD-2025-ABCD-1234"}'

License Seat Management

# Acquire a license seat
curl -X POST https://api.coditect.ai/api/v1/licenses/acquire/ \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"license_key": "PROD-2025-ABCD-1234",
"hardware_id": "mac-address-hash-xyz",
"ip_address": "192.168.1.100",
"client_version": "1.0.0"
}'

# Send heartbeat (every 3-5 minutes)
curl -X PATCH https://api.coditect.ai/api/v1/licenses/sessions/SESSION_UUID/heartbeat/ \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Release license seat
curl -X DELETE https://api.coditect.ai/api/v1/licenses/sessions/SESSION_UUID/ \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Subscription Management

# Get available plans (public)
curl https://api.coditect.ai/api/v1/subscriptions/plans/

# Get current subscription
curl https://api.coditect.ai/api/v1/subscriptions/current/ \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Create checkout session
curl -X POST https://api.coditect.ai/api/v1/subscriptions/checkout/ \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"price_id": "price_pro_monthly",
"seats": 5,
"success_url": "https://app.coditect.ai/success",
"cancel_url": "https://app.coditect.ai/pricing"
}'

# Update subscription seats
curl -X POST https://api.coditect.ai/api/v1/subscriptions/update/ \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"seats": 10}'

# Cancel subscription (at period end)
curl -X POST https://api.coditect.ai/api/v1/subscriptions/cancel/ \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"immediately": false}'

Health Checks

# Basic health check
curl https://api.coditect.ai/api/v1/health/

# Readiness check (includes database)
curl https://api.coditect.ai/api/v1/health/ready/

Common Integration Patterns

Pattern 1: Desktop Application License Check

import hashlib
import uuid

def get_hardware_id() -> str:
"""Generate a unique hardware identifier."""
# Combine multiple hardware identifiers
import platform
components = [
platform.node(), # Hostname
platform.machine(), # CPU type
str(uuid.getnode()), # MAC address
]
combined = "-".join(components)
return hashlib.sha256(combined.encode()).hexdigest()[:32]

def initialize_license(client: CoditectClient, license_key: str):
"""Initialize license on application startup."""

# 1. Validate license first (no auth required)
validation = client.validate_license(license_key)
if not validation["valid"]:
raise LicenseError(validation.get("reason", "Invalid license"))

# 2. Try to acquire a seat
hardware_id = get_hardware_id()
try:
session = client.acquire_seat(license_key, hardware_id)
return session
except requests.HTTPError as e:
if e.response.status_code == 409:
raise LicenseError("No seats available")
raise

Pattern 2: Web Application with Token Storage

// Store tokens securely
const TOKEN_KEY = "coditect_tokens";

function saveTokens(access: string, refresh: string): void {
// Use httpOnly cookies in production!
sessionStorage.setItem(TOKEN_KEY, JSON.stringify({ access, refresh }));
}

function loadTokens(): { access: string; refresh: string } | null {
const stored = sessionStorage.getItem(TOKEN_KEY);
return stored ? JSON.parse(stored) : null;
}

// Initialize client with stored tokens
async function initializeClient(): Promise<CoditectClient> {
const client = new CoditectClient();
const tokens = loadTokens();

if (tokens) {
client.setTokens(tokens.access, tokens.refresh);

// Verify token is still valid
try {
await client.getCurrentSubscription();
} catch {
// Token expired, need to re-login
return client;
}
}

return client;
}

Pattern 3: CI/CD License Validation

#!/bin/bash
# validate-license.sh - Use in CI/CD pipeline

LICENSE_KEY="${CODITECT_LICENSE_KEY:?License key required}"
API_URL="${CODITECT_API_URL:-https://api.coditect.ai}"

# Validate license
response=$(curl -s -X POST "$API_URL/api/v1/licenses/validate/" \
-H "Content-Type: application/json" \
-d "{\"key_string\": \"$LICENSE_KEY\"}")

valid=$(echo "$response" | jq -r '.valid')

if [ "$valid" != "true" ]; then
reason=$(echo "$response" | jq -r '.reason // "Unknown error"')
echo "License validation failed: $reason"
exit 1
fi

echo "License valid. Tier: $(echo "$response" | jq -r '.license.tier')"
exit 0

License Session Management

Session Lifecycle

┌────────────────────────────────────────────────────────────────┐
│ LICENSE SESSION LIFECYCLE │
├────────────────────────────────────────────────────────────────┤
│ │
│ 1. ACQUIRE │
│ POST /api/v1/licenses/acquire/ │
│ ├── Validates license key │
│ ├── Checks seat availability │
│ ├── Creates session with 6-minute TTL │
│ └── Returns session_id │
│ │
│ 2. HEARTBEAT (every 3-5 minutes) │
│ PATCH /api/v1/licenses/sessions/{id}/heartbeat/ │
│ ├── Extends session TTL by 6 minutes │
│ └── Returns updated session info │
│ │
│ 3. RELEASE (when done) │
│ DELETE /api/v1/licenses/sessions/{id}/ │
│ ├── Immediately frees the seat │
│ └── Returns seats_remaining count │
│ │
│ AUTOMATIC EXPIRATION │
│ If no heartbeat for 6+ minutes: │
│ └── Session automatically expired, seat freed │
│ │
└────────────────────────────────────────────────────────────────┘

Implementing Robust Heartbeat

import threading
import time
from typing import Optional, Callable

class LicenseSessionManager:
"""Manages license session with automatic heartbeat."""

def __init__(self, client: CoditectClient,
on_session_lost: Optional[Callable] = None):
self.client = client
self.session_id: Optional[str] = None
self._heartbeat_thread: Optional[threading.Thread] = None
self._stop_heartbeat = threading.Event()
self._on_session_lost = on_session_lost
self._retry_count = 0
self._max_retries = 3

def acquire(self, license_key: str, hardware_id: str) -> dict:
"""Acquire a license seat and start heartbeat."""
session = self.client.acquire_seat(license_key, hardware_id)
self.session_id = session["session_id"]
self._start_heartbeat()
return session

def release(self) -> None:
"""Release license seat and stop heartbeat."""
self._stop_heartbeat.set()
if self._heartbeat_thread:
self._heartbeat_thread.join(timeout=5)

if self.session_id:
try:
self.client.release_seat(self.session_id)
except Exception:
pass # Best effort release
self.session_id = None

def _start_heartbeat(self) -> None:
"""Start background heartbeat thread."""
self._stop_heartbeat.clear()
self._heartbeat_thread = threading.Thread(
target=self._heartbeat_loop,
daemon=True
)
self._heartbeat_thread.start()

def _heartbeat_loop(self) -> None:
"""Background heartbeat loop with exponential backoff."""
while not self._stop_heartbeat.wait(180): # 3 minutes
try:
self.client.heartbeat(self.session_id)
self._retry_count = 0 # Reset on success
except Exception as e:
self._retry_count += 1
if self._retry_count >= self._max_retries:
self.session_id = None
if self._on_session_lost:
self._on_session_lost(e)
break
# Exponential backoff: 10s, 20s, 40s
time.sleep(10 * (2 ** (self._retry_count - 1)))

Error Handling

Error Response Format

{
"error": "Error type",
"detail": "Detailed error message"
}

Validation Errors

{
"errors": {
"email": ["Enter a valid email address."],
"password": ["Password must be at least 8 characters."]
}
}

Common HTTP Status Codes

CodeMeaningAction
400Bad RequestCheck request body/params
401UnauthorizedRefresh token or re-login
403ForbiddenCheck permissions
404Not FoundResource doesn't exist
409ConflictResource already exists / No seats
429Rate LimitedImplement backoff
500Server ErrorRetry with backoff
503Service UnavailableRetry later

Python Error Handling

from requests.exceptions import HTTPError, ConnectionError, Timeout

def safe_api_call(client: CoditectClient, operation: str, *args, **kwargs):
"""Wrapper with comprehensive error handling."""
try:
method = getattr(client, operation)
return method(*args, **kwargs)

except HTTPError as e:
status = e.response.status_code

if status == 401:
# Token expired - handled by client automatically
raise AuthenticationError("Please log in again")

elif status == 403:
raise PermissionError("Insufficient permissions")

elif status == 404:
raise NotFoundError(f"Resource not found")

elif status == 409:
body = e.response.json()
if "seats" in body.get("detail", "").lower():
raise NoSeatsAvailableError("All license seats in use")
raise ConflictError(body.get("detail", "Conflict"))

elif status == 429:
retry_after = e.response.headers.get("Retry-After", 60)
raise RateLimitError(f"Rate limited. Retry after {retry_after}s")

elif status >= 500:
raise ServerError("Server error. Please try again later.")

raise

except ConnectionError:
raise NetworkError("Cannot connect to API. Check network.")

except Timeout:
raise NetworkError("Request timed out. Please try again.")

JavaScript Error Handling

class ApiError extends Error {
constructor(
message: string,
public statusCode: number,
public details?: object
) {
super(message);
this.name = "ApiError";
}
}

async function safeRequest<T>(
request: () => Promise<T>,
retries: number = 3
): Promise<T> {
let lastError: Error | null = null;

for (let attempt = 0; attempt < retries; attempt++) {
try {
return await request();
} catch (error) {
lastError = error as Error;

if (error instanceof ApiError) {
// Don't retry client errors (4xx)
if (error.statusCode >= 400 && error.statusCode < 500) {
throw error;
}

// Retry server errors (5xx) with exponential backoff
if (error.statusCode >= 500) {
await sleep(1000 * Math.pow(2, attempt));
continue;
}
}

throw error;
}
}

throw lastError;
}

Rate Limiting

Rate Limits by Endpoint

Endpoint CategoryLimitWindow
Authentication5 req1 minute
License Operations100 req1 minute
Heartbeat20 req1 minute
License Validation60 req1 minute (per IP)

Handling Rate Limits

import time
from functools import wraps

def with_rate_limit_handling(max_retries: int = 3):
"""Decorator to handle rate limiting with exponential backoff."""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except HTTPError as e:
if e.response.status_code == 429:
retry_after = int(
e.response.headers.get("Retry-After", 60)
)
if attempt < max_retries - 1:
time.sleep(retry_after)
continue
raise
return func(*args, **kwargs)
return wrapper
return decorator

# Usage
@with_rate_limit_handling(max_retries=3)
def validate_many_licenses(client, keys):
return [client.validate_license(key) for key in keys]

Webhooks

Stripe Webhook Events

Your application can receive subscription lifecycle events via Stripe webhooks. Configure your webhook endpoint to receive:

EventDescription
checkout.session.completedCustomer completed checkout
checkout.session.expiredCheckout session expired
customer.subscription.createdNew subscription created
customer.subscription.updatedSubscription modified
customer.subscription.deletedSubscription canceled
invoice.payment_succeededPayment successful
invoice.payment_failedPayment failed

Webhook Signature Verification (Python)

import stripe
from flask import Flask, request

app = Flask(__name__)
STRIPE_WEBHOOK_SECRET = "whsec_..."

@app.route("/webhook/stripe", methods=["POST"])
def stripe_webhook():
payload = request.get_data()
sig_header = request.headers.get("Stripe-Signature")

try:
event = stripe.Webhook.construct_event(
payload, sig_header, STRIPE_WEBHOOK_SECRET
)
except ValueError:
return "Invalid payload", 400
except stripe.error.SignatureVerificationError:
return "Invalid signature", 400

# Handle the event
if event["type"] == "customer.subscription.deleted":
subscription = event["data"]["object"]
handle_subscription_canceled(subscription["id"])

return "OK", 200

SDK Best Practices

1. Token Management

  • Store refresh tokens securely (keychain/keyring on desktop, httpOnly cookies on web)
  • Implement automatic token refresh before expiration
  • Handle token invalidation gracefully

2. Connection Handling

  • Use connection pooling for multiple requests
  • Implement timeouts for all requests (recommended: 30s)
  • Handle network failures with retry logic

3. Session Management

  • Always release license seats on application exit
  • Implement graceful degradation if heartbeat fails
  • Store session ID for recovery after crash

4. Error Recovery

  • Implement exponential backoff for retries
  • Log errors with context for debugging
  • Provide user-friendly error messages

5. Testing

  • Use staging environment for integration tests
  • Mock API responses for unit tests
  • Test error handling paths

Environment Configuration

Development

export CODITECT_API_URL="http://localhost:8000"
export CODITECT_DEBUG="true"

Staging

export CODITECT_API_URL="https://staging-api.coditect.ai"

Production

export CODITECT_API_URL="https://api.coditect.ai"

Support


Generated: 2026-01-08 Version: 1.0.0