Skip to main content

Phase 1 Step 6: License Seat Management API - Quick Reference

-------------- 10/min PATCH Refresh session TTL None Field Required ------------------- Yes string Unique hardware identifier... moe_confidence: 0.950 moe_classified: 2025-12-31

Phase 1 Step 6: License Seat Management API - Quick Reference

Version: 1.0 Base URL: https://api.coditect.ai/api/v1 Authentication: JWT Bearer Token


API Endpoints Summary

MethodEndpointPurposeRate Limit
POST/licenses/acquire/Acquire license seat10/min
PATCH/licenses/sessions/{id}/heartbeat/Refresh session TTL20/min
DELETE/licenses/sessions/{id}/Release seatNone

1. Acquire License Seat

Endpoint: POST /api/v1/licenses/acquire/

Description: Atomically acquire a license seat if available. Creates a session with 6-minute TTL.

Authentication: Required (JWT)

Rate Limit: 10 requests/minute per user

Request Headers:

Authorization: Bearer {jwt_token}
Content-Type: application/json

Request Body:

{
"license_key": "CODITECT-2025-A7B3-X9K2",
"machine_id": "hw-id-abc123-xyz789",
"metadata": {
"app_version": "1.0.0",
"os": "Windows 10",
"hostname": "DESKTOP-ABC123"
}
}

Parameters:

FieldTypeRequiredDescription
license_keystringYesLicense key in format CODITECT-YYYY-XXXX-XXXX
machine_idstringYesUnique hardware identifier (hashed MAC address)
metadataobjectNoOptional session metadata (app version, OS, etc.)

Success Response (201 Created):

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"license_key": "CODITECT-2025-A7B3-X9K2",
"started_at": "2025-12-01T12:00:00Z",
"last_heartbeat_at": "2025-12-01T12:00:00Z",
"expires_at": "2025-12-01T12:06:00Z",
"is_active": true,
"machine_id": "hw-id-abc123-xyz789",
"ip_address": "192.168.1.100",
"user_agent": "CODITECT/1.0.0 (Windows 10)",
"metadata": {
"app_version": "1.0.0",
"os": "Windows 10",
"hostname": "DESKTOP-ABC123",
"ip_address": "192.168.1.100",
"user_agent": "CODITECT/1.0.0 (Windows 10)",
"user_id": "user-uuid-123"
}
}

Response Fields:

FieldTypeDescription
idUUIDSession unique identifier
license_keystringLicense key used for acquisition
started_atdatetimeSession start timestamp (ISO 8601)
last_heartbeat_atdatetimeLast heartbeat timestamp (ISO 8601)
expires_atdatetimeSession expiration time (6 min from last heartbeat)
is_activebooleanWhether session is currently active
machine_idstringMachine hardware identifier
ip_addressstringClient IP address
user_agentstringClient user agent string
metadataobjectSession metadata (enriched with client info)

Error Responses:

400 Bad Request - Validation Error:

{
"license_key": ["License expired on 2025-11-01. Please renew."]
}

400 Bad Request - License Expired:

{
"license_key": ["License expired on 2025-11-01T00:00:00Z. Please renew your subscription."]
}

400 Bad Request - License Inactive:

{
"license_key": ["License is inactive. Please contact support."]
}

409 Conflict - All Seats Taken:

{
"error": "license_full",
"message": "All license seats are currently in use",
"max_seats": 5,
"seats_used": 5,
"seats_remaining": 0
}

409 Conflict - Duplicate Session:

{
"machine_id": ["An active session already exists for this machine. Please release the existing session first or wait for it to expire."]
}

429 Too Many Requests:

{
"detail": "Request was throttled. Expected available in 42 seconds."
}

2. Refresh Session Heartbeat

Endpoint: PATCH /api/v1/licenses/sessions/{session_id}/heartbeat/

Description: Refresh session TTL to prevent expiration. Extends session by 6 minutes from current time.

Authentication: Required (JWT)

Rate Limit: 20 requests/minute per user

Request Headers:

Authorization: Bearer {jwt_token}

Path Parameters:

ParameterTypeDescription
session_idUUIDSession unique identifier from acquire response

Request Body: None

Success Response (200 OK):

{
"success": true,
"expires_at": "2025-12-01T12:11:00Z",
"time_remaining": 360,
"message": "Heartbeat received successfully"
}

Response Fields:

FieldTypeDescription
successbooleanAlways true for successful heartbeat
expires_atdatetimeNew expiration time (6 min from now)
time_remainingintegerSeconds remaining until expiration
messagestringHuman-readable success message

Error Responses:

404 Not Found:

{
"detail": "Not found."
}

410 Gone - Session Expired:

{
"error": "session_expired",
"message": "Session expired due to inactivity. Please acquire a new seat.",
"last_heartbeat_at": "2025-12-01T11:54:00Z",
"expired_at": "2025-12-01T12:00:00Z"
}

410 Gone - Session Already Released:

{
"error": "session_expired",
"message": "Session has already been released",
"last_heartbeat_at": "2025-12-01T12:00:00Z",
"expired_at": "2025-12-01T12:05:30Z"
}

429 Too Many Requests:

{
"detail": "Request was throttled. Expected available in 28 seconds."
}

3. Release License Seat

Endpoint: DELETE /api/v1/licenses/sessions/{session_id}/

Description: Explicitly release a license seat (graceful shutdown). Idempotent operation.

Authentication: Required (JWT)

Rate Limit: None (always allow graceful shutdown)

Request Headers:

Authorization: Bearer {jwt_token}

Path Parameters:

ParameterTypeDescription
session_idUUIDSession unique identifier from acquire response

Request Body: None

Success Response (204 No Content):

HTTP/1.1 204 No Content

(No response body)

Error Responses:

404 Not Found:

{
"detail": "Not found."
}

Note: This endpoint is idempotent. If the session has already been released, it will still return 204 No Content (success).


Client Implementation Guide

import requests
import time
import threading

class LicenseSessionManager:
def __init__(self, base_url, jwt_token, license_key, machine_id):
self.base_url = base_url
self.jwt_token = jwt_token
self.license_key = license_key
self.machine_id = machine_id
self.session_id = None
self.heartbeat_thread = None
self.active = False

def acquire(self):
"""Acquire a license seat."""
response = requests.post(
f"{self.base_url}/licenses/acquire/",
headers={"Authorization": f"Bearer {self.jwt_token}"},
json={
"license_key": self.license_key,
"machine_id": self.machine_id,
"metadata": {
"app_version": "1.0.0",
"os": "Windows 10"
}
}
)

if response.status_code == 201:
data = response.json()
self.session_id = data['id']
self.active = True
self._start_heartbeat()
return True
elif response.status_code == 409:
# All seats taken, retry later
print("All seats taken, retrying in 60 seconds...")
return False
else:
response.raise_for_status()

def _start_heartbeat(self):
"""Start background heartbeat thread (every 3 minutes)."""
def heartbeat_loop():
while self.active:
time.sleep(180) # 3 minutes
if self.active:
self._send_heartbeat()

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

def _send_heartbeat(self):
"""Send heartbeat to refresh session TTL."""
if not self.session_id:
return

try:
response = requests.patch(
f"{self.base_url}/licenses/sessions/{self.session_id}/heartbeat/",
headers={"Authorization": f"Bearer {self.jwt_token}"}
)

if response.status_code == 200:
print("Heartbeat sent successfully")
elif response.status_code == 410:
# Session expired, need to re-acquire
print("Session expired, re-acquiring...")
self.active = False
self.acquire()
except Exception as e:
print(f"Heartbeat failed: {e}")

def release(self):
"""Release the license seat (graceful shutdown)."""
if not self.session_id:
return

self.active = False

try:
response = requests.delete(
f"{self.base_url}/licenses/sessions/{self.session_id}/",
headers={"Authorization": f"Bearer {self.jwt_token}"}
)

if response.status_code == 204:
print("Seat released successfully")
self.session_id = None
except Exception as e:
print(f"Release failed: {e}")

# Usage
manager = LicenseSessionManager(
base_url="https://api.coditect.ai/api/v1",
jwt_token="your-jwt-token",
license_key="CODITECT-2025-A7B3-X9K2",
machine_id="hw-id-abc123"
)

# Acquire seat
if manager.acquire():
print("Seat acquired, application running...")

# Application runs here
# Heartbeats are sent automatically every 3 minutes

# Graceful shutdown
manager.release()

Best Practices

1. Heartbeat Frequency

  • Recommended: Send heartbeat every 3-5 minutes
  • Session TTL: 6 minutes
  • Buffer: 1-3 minutes safety margin before expiration

2. Retry Logic

  • All Seats Taken (409): Retry after 60 seconds
  • Rate Limit (429): Respect Retry-After header
  • Network Errors: Exponential backoff (1s, 2s, 4s, 8s)

3. Graceful Shutdown

  • Always call release endpoint on application exit
  • Use atexit handlers or signal handlers to ensure release
  • Release is idempotent, safe to call multiple times

4. Session Expiration Handling

  • Monitor heartbeat response status
  • If heartbeat returns 410 Gone, re-acquire seat immediately
  • Don't rely on expiration alone, use explicit release

5. Error Handling

try:
manager.acquire()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 409:
print("All seats taken, retry later")
elif e.response.status_code == 400:
print(f"Validation error: {e.response.json()}")
else:
raise

Testing with cURL

Acquire Seat

curl -X POST https://api.coditect.ai/api/v1/licenses/acquire/ \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"license_key": "CODITECT-2025-A7B3-X9K2",
"machine_id": "test-machine-123",
"metadata": {"app_version": "1.0.0"}
}'

Send Heartbeat

curl -X PATCH https://api.coditect.ai/api/v1/licenses/sessions/SESSION_ID/heartbeat/ \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

Release Seat

curl -X DELETE https://api.coditect.ai/api/v1/licenses/sessions/SESSION_ID/ \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

Troubleshooting

Problem: 409 Conflict - All Seats Taken

Solution:

  • Wait 60 seconds and retry
  • Check organization seat limit (max_seats)
  • Review active sessions in admin dashboard
  • Consider upgrading subscription for more seats

Problem: 410 Gone - Session Expired

Solution:

  • Heartbeat interval too long (>6 minutes)
  • Network connectivity issues preventing heartbeats
  • Reduce heartbeat interval to 3 minutes
  • Implement automatic re-acquisition on 410 error

Problem: 409 Conflict - Duplicate Session

Solution:

  • Same machine_id already has active session
  • Wait for existing session to expire (6 minutes)
  • Release existing session first
  • Use unique machine_id per device

Problem: 429 Too Many Requests

Solution:

  • Respect rate limits (10/min acquire, 20/min heartbeat)
  • Implement exponential backoff
  • Don't send heartbeats more frequently than every 3 minutes

Security Notes

  1. JWT Tokens: Store securely, never log or expose
  2. Machine IDs: Use secure hardware identifiers (hashed MAC address)
  3. HTTPS Only: Never use HTTP for license API calls
  4. License Keys: Treat as sensitive data, don't hardcode in client apps
  5. Audit Logs: All operations logged for compliance and security monitoring

Support


Last Updated: December 1, 2025 API Version: 1.0 Owner: AZ1.AI INC