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
| Method | Endpoint | Purpose | Rate Limit |
|---|---|---|---|
| POST | /licenses/acquire/ | Acquire license seat | 10/min |
| PATCH | /licenses/sessions/{id}/heartbeat/ | Refresh session TTL | 20/min |
| DELETE | /licenses/sessions/{id}/ | Release seat | None |
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:
| Field | Type | Required | Description |
|---|---|---|---|
license_key | string | Yes | License key in format CODITECT-YYYY-XXXX-XXXX |
machine_id | string | Yes | Unique hardware identifier (hashed MAC address) |
metadata | object | No | Optional 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:
| Field | Type | Description |
|---|---|---|
id | UUID | Session unique identifier |
license_key | string | License key used for acquisition |
started_at | datetime | Session start timestamp (ISO 8601) |
last_heartbeat_at | datetime | Last heartbeat timestamp (ISO 8601) |
expires_at | datetime | Session expiration time (6 min from last heartbeat) |
is_active | boolean | Whether session is currently active |
machine_id | string | Machine hardware identifier |
ip_address | string | Client IP address |
user_agent | string | Client user agent string |
metadata | object | Session 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:
| Parameter | Type | Description |
|---|---|---|
session_id | UUID | Session 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:
| Field | Type | Description |
|---|---|---|
success | boolean | Always true for successful heartbeat |
expires_at | datetime | New expiration time (6 min from now) |
time_remaining | integer | Seconds remaining until expiration |
message | string | Human-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:
| Parameter | Type | Description |
|---|---|---|
session_id | UUID | Session 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
Recommended Usage Pattern
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-Afterheader - Network Errors: Exponential backoff (1s, 2s, 4s, 8s)
3. Graceful Shutdown
- Always call release endpoint on application exit
- Use
atexithandlers 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
- JWT Tokens: Store securely, never log or expose
- Machine IDs: Use secure hardware identifiers (hashed MAC address)
- HTTPS Only: Never use HTTP for license API calls
- License Keys: Treat as sensitive data, don't hardcode in client apps
- Audit Logs: All operations logged for compliance and security monitoring
Support
- API Documentation: https://api.coditect.ai/docs
- Developer Portal: https://developers.coditect.ai
- Support Email: support@coditect.ai
- Status Page: https://status.coditect.ai
Last Updated: December 1, 2025 API Version: 1.0 Owner: AZ1.AI INC