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
- Authentication
- Code Examples by Language
- Common Integration Patterns
- License Session Management
- Error Handling
- Rate Limiting
- Webhooks
- SDK Best Practices
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 Type | Expiration | Refresh Strategy |
|---|---|---|
| Access Token | 15 minutes | Use refresh token before expiry |
| Refresh Token | 7 days | Re-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
| Code | Meaning | Action |
|---|---|---|
400 | Bad Request | Check request body/params |
401 | Unauthorized | Refresh token or re-login |
403 | Forbidden | Check permissions |
404 | Not Found | Resource doesn't exist |
409 | Conflict | Resource already exists / No seats |
429 | Rate Limited | Implement backoff |
500 | Server Error | Retry with backoff |
503 | Service Unavailable | Retry 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 Category | Limit | Window |
|---|---|---|
| Authentication | 5 req | 1 minute |
| License Operations | 100 req | 1 minute |
| Heartbeat | 20 req | 1 minute |
| License Validation | 60 req | 1 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:
| Event | Description |
|---|---|
checkout.session.completed | Customer completed checkout |
checkout.session.expired | Checkout session expired |
customer.subscription.created | New subscription created |
customer.subscription.updated | Subscription modified |
customer.subscription.deleted | Subscription canceled |
invoice.payment_succeeded | Payment successful |
invoice.payment_failed | Payment 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
- API Reference: api-reference.md
- OpenAPI Spec:
openapi-schema-generated.yaml - Support Email: support@coditect.ai
- Documentation: https://docs.coditect.ai
Generated: 2026-01-08 Version: 1.0.0