Traffic Orchestrator API
Everything you need to add software licensing to your application. Validate licenses, manage activations, and handle subscriptions with our simple REST API.
Introduction
Traffic Orchestrator provides a complete license management solution for software developers. Whether you're building a desktop app, SaaS product, or mobile application, our API makes it easy to protect your software with license keys.
Key Features
- License Validation - Verify license keys in real-time with low latency
- Activation Management - Control how many devices can use a license
- Offline Support - Grace period for when devices are offline
- Usage Tracking - Monitor license usage and analytics
- Webhooks - Get notified about important events
- White-labeling - Customize the experience for your brand
Quick Start
Get up and running with Traffic Orchestrator in 5 minutes.
1. Get Your API Key
Sign up at trafficorchestrator.com and grab your API key from the dashboard.
2. Make Your First API Call
curl -X POST https://api.trafficorchestrator.com/api/v1/validate -H "Authorization: Bearer to_live_xxxxxxxxxxxxx" -H "Content-Type: application/json" -d '{"key": "XXXX-XXXX-XXXX-XXXX", "domain": "example.com"}'
3. Handle the Response
const res = await fetch('https://api.trafficorchestrator.com/api/v1/validate', { method: 'POST', headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ key: 'XXXX-XXXX-XXXX-XXXX' }) }); const { valid, license } = await res.json(); if (valid) { console.log('License is valid!', license.status); // Enable premium features } else { console.error('License validation failed'); }
Authentication
All API requests require an API key. You can find your API keys in the dashboard.
Authorization: Bearer to_live_xxxxxxxxxxxxx
API Key Types
| Type | Prefix | Use Case |
|---|---|---|
| Live | to_live_ | Production applications |
| Test | to_test_ | Development and testing |
Validate License
Verify that a license key is valid and active.
Validates a license key and returns its status and details.
REQUEST BODY
| Parameter | Type | Required | Description |
|---|---|---|---|
| license_key | string | Required | The license key to validate |
| product_id | string | Validate for specific product | |
| machine_id | string | Device fingerprint | |
| domain | string | Domain for web apps |
Response
{
"valid": true,
"license": {
"id": "lic_1234567890",
"key": "XXXX-XXXX-XXXX-XXXX",
"status": "active",
"type": "perpetual",
"activations": 2,
"maxActivations": 5,
"expiresAt": null,
"metadata": {}
},
"validatedAt": "2024-01-15T12:00:00Z"
}
JavaScript/TypeScript
Use the official SDK or the REST API directly from any JavaScript or TypeScript application.
npm install @traffic-orchestrator/client
Validate a License
const API_KEY = 'to_live_xxxxxxxxxxxxx'; const BASE = 'https://api.trafficorchestrator.com'; // Validate a license const res = await fetch(BASE + '/api/v1/validate', { method: 'POST', headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ key: 'XXXX-XXXX-XXXX-XXXX', domain: 'example.com' }) }); const { valid, license } = await res.json(); console.log(valid, license);
Activate on a Device
const activation = await fetch(BASE + '/api/v1/activate', { method: 'POST', headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ license_key: 'XXXX-XXXX-XXXX-XXXX', machine_id: 'unique-device-fingerprint' }) }).then(r => r.json());
Licenses
Licenses are the core resource in Traffic Orchestrator. Each license represents a right to use your software, associated with a specific product and customer.
License Types
| Type | Description | Expiry |
|---|---|---|
| perpetual | Never expires once activated | None |
| subscription | Valid for a billing period, auto-renews | Monthly/Yearly |
| trial | Time-limited evaluation license | 7-30 days |
| floating | Shared pool of concurrent seats | Configurable |
License Statuses
| Status | Description |
|---|---|
| active | License is valid and usable |
| expired | Subscription or trial has expired |
| suspended | Temporarily disabled by admin |
| revoked | Permanently disabled |
Activations
Activations track which devices or machines are using a license. Each license has a configurable maximum number of activations.
How Activation Works
- Generate a unique machine ID from the user's device
- Call the activate endpoint with the license key and machine ID
- On subsequent app launches, validate the license with the same machine ID
- When a user switches devices, deactivate the old machine first
TrafficOrchestrator.generateMachineId() in the SDK to create a consistent,
privacy-respecting device fingerprint.
Products
Products represent your software applications. Each product can have multiple license types and pricing tiers. Create products in the dashboard or via the API.
Product Properties
| Field | Type | Description |
|---|---|---|
| id | string | Unique product identifier (prod_xxx) |
| name | string | Human-readable product name |
| platforms | string[] | Supported platforms (windows, mac, linux, web) |
| metadata | object | Custom key-value pairs for your use |
Webhooks
Webhooks notify your server in real-time when important events occur. Configure webhook endpoints in the dashboard.
Available Events
| Event | Description |
|---|---|
| license.created | A new license was created |
| license.activated | A license was activated on a device |
| license.deactivated | A license was deactivated |
| license.expired | A license has expired |
| license.suspended | A license was suspended |
| validation.failed | A validation attempt failed |
Webhook Payload
{
"event": "license.activated",
"timestamp": "2024-01-15T12:00:00Z",
"data": {
"licenseId": "lic_1234567890",
"machineId": "mach_abcdef",
"productId": "prod_123"
}
}
Webhook Headers
Every webhook request includes these headers for verification:
| Header | Description |
|---|---|
| X-TO-Signature | HMAC-SHA256 signature of the request body |
| X-TO-Timestamp | Unix timestamp when the webhook was sent |
| X-TO-Event | The event type (e.g., license.activated) |
| X-TO-Delivery-ID | Unique ID for this delivery (use for deduplication) |
Verifying Signatures
Verify the X-TO-Signature header matches the HMAC-SHA256 hash of:
timestamp.body using your webhook signing secret from the dashboard.
// Node.js / Express import crypto from 'crypto' const verifyWebhook = (req, secret) => { const signature = req.headers['x-to-signature'] const timestamp = req.headers['x-to-timestamp'] const body = JSON.stringify(req.body) // Reject if timestamp is older than 5 minutes if (Date.now() / 1000 - Number(timestamp) > 300) { return false } const expected = crypto .createHmac('sha256', secret) .update(timestamp + '.' + body) .digest('hex') return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ) } app.post('/webhooks/to', (req, res) => { if (!verifyWebhook(req, process.env.WEBHOOK_SECRET)) { return res.status(401).json({ error: 'Invalid signature' }) } // Process the webhook event console.log('Event:', req.body.event) res.status(200).json({ received: true }) })
# Python / Flask import hmac, hashlib, time from flask import Flask, request, jsonify app = Flask(__name__) def verify_webhook(req, secret): signature = req.headers.get('X-TO-Signature', '') timestamp = req.headers.get('X-TO-Timestamp', '0') # Reject if older than 5 minutes if time.time() - int(timestamp) > 300: return False expected = hmac.new( secret.encode(), f"{timestamp}.{req.get_data(as_text=True)}".encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected) @app.route('/webhooks/to', methods=['POST']) def handle_webhook(): if not verify_webhook(request, WEBHOOK_SECRET): return jsonify(error='Invalid signature'), 401 event = request.json print(f"Event: {event['event']}") return jsonify(received=True), 200
// PHP $secret = getenv('WEBHOOK_SECRET'); $signature = $_SERVER['HTTP_X_TO_SIGNATURE'] ?? ''; $timestamp = $_SERVER['HTTP_X_TO_TIMESTAMP'] ?? '0'; $body = file_get_contents('php://input'); // Reject if older than 5 minutes if (time() - (int)$timestamp > 300) { http_response_code(401); exit(json_encode(['error' => 'Timestamp expired'])); } $expected = hash_hmac('sha256', "{$timestamp}.{$body}", $secret); if (!hash_equals($expected, $signature)) { http_response_code(401); exit(json_encode(['error' => 'Invalid signature'])); } $event = json_decode($body, true); error_log("Event: " . $event['event']); http_response_code(200); echo json_encode(['received' => true]);
Retry Behavior
If your endpoint returns a non-2xx status code or times out, the system retries with exponential backoff:
X-TO-Delivery-ID header for idempotency — your
endpoint may receive the same event more than once during retries.
Create License
Create a new license key programmatically.
Creates a new license and returns the generated key.
REQUEST BODY
| Parameter | Type | Required | Description |
|---|---|---|---|
| product_id | string | Required | Product to create the license for |
| type | string | Required | License type: perpetual, subscription, trial, floating |
| max_activations | number | Max simultaneous activations (default: 1) | |
| string | Customer email to associate | ||
| expires_at | string | ISO 8601 expiry date | |
| metadata | object | Custom key-value pairs |
Response
{
"license": {
"id": "lic_abc123def456",
"key": "ABCD-EFGH-IJKL-MNOP",
"productId": "prod_123",
"type": "subscription",
"status": "active",
"maxActivations": 3,
"createdAt": "2024-01-15T12:00:00Z"
}
}
Activate License
Activate a license on a specific device or machine.
Registers a device against a license key, consuming one activation slot.
REQUEST BODY
| Parameter | Type | Required | Description |
|---|---|---|---|
| license_key | string | Required | The license key to activate |
| machine_id | string | Required | Unique device identifier |
| label | string | Human-readable device label |
Deactivate License
Remove a device activation from a license, freeing up an activation slot.
Removes a machine from a license's activation list.
REQUEST BODY
| Parameter | Type | Required | Description |
|---|---|---|---|
| license_key | string | Required | The license key to deactivate from |
| machine_id | string | Required | Device identifier to remove |
List Licenses
Retrieve a paginated list of licenses for your account.
Returns a paginated list of licenses with optional filtering.
QUERY PARAMETERS
| Parameter | Type | Description |
|---|---|---|
| status | string | Filter by status (active, expired, suspended) |
| product_id | string | Filter by product |
| page | number | Page number (default: 1) |
| limit | number | Items per page (default: 25, max: 100) |
Python
Use the official SDK or validate licenses directly with any HTTP client.
pip install traffic-orchestrator
Usage
import requests API_KEY = "to_live_xxxxxxxxxxxxx" BASE = "https://api.trafficorchestrator.com" # Validate a license response = requests.post( f"{BASE}/api/v1/validate", headers={"Authorization": f"Bearer {API_KEY}"}, json={"key": "XXXX-XXXX-XXXX-XXXX", "domain": "example.com"} ) data = response.json() if data["valid"]: print("License is valid!") else: print("Validation failed")
Go
Validate licenses from Go using the standard net/http package.
net/http.
Usage
package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]string{ "key": "XXXX-XXXX-XXXX-XXXX", "domain": "example.com", }) req, _ := http.NewRequest("POST", "https://api.trafficorchestrator.com/api/v1/validate", bytes.NewBuffer(body)) req.Header.Set("Authorization", "Bearer to_live_xxxxxxxxxxxxx") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return } defer resp.Body.Close() var result map[string]interface{} json.NewDecoder(resp.Body).Decode(&result) fmt.Println("Valid:", result["valid"]) }
PHP
Validate licenses from PHP using the built-in cURL extension.
cURL or Guzzle.
Usage
<?php $ch = curl_init('https://api.trafficorchestrator.com/api/v1/validate'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer to_live_xxxxxxxxxxxxx', 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'key' => 'XXXX-XXXX-XXXX-XXXX', 'domain' => 'example.com', ]), ]); $response = curl_exec($ch); curl_close($ch); $result = json_decode($response, true); echo 'Valid: ' . ($result['valid'] ? 'yes' : 'no');
Ruby
Validate licenses from Ruby using the standard net/http library.
Net::HTTP or Faraday.
Usage
require 'net/http' require 'json' uri = URI('https://api.trafficorchestrator.com/api/v1/validate') req = Net::HTTP::Post.new(uri) req['Authorization'] = 'Bearer to_live_xxxxxxxxxxxxx' req['Content-Type'] = 'application/json' req.body = { key: 'XXXX-XXXX-XXXX-XXXX', domain: 'example.com' }.to_json res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } result = JSON.parse(res.body) puts "Valid: #{result['valid']}"
C# / .NET
Validate licenses from C# using HttpClient.
HttpClient.
Usage
using System.Net.Http; using System.Text; using System.Text.Json; var client = new HttpClient(); client.DefaultRequestHeaders.Add("Authorization", "Bearer to_live_xxxxxxxxxxxxx"); var body = new StringContent( JsonSerializer.Serialize(new { key = "XXXX-XXXX-XXXX-XXXX", domain = "example.com" }), Encoding.UTF8, "application/json"); var response = await client.PostAsync( "https://api.trafficorchestrator.com/api/v1/validate", body); var json = await response.Content.ReadAsStringAsync(); var result = JsonSerializer.Deserialize<JsonElement>(json); Console.WriteLine($"Valid: {result.GetProperty("valid")}");
Rust
Use the official SDK or validate licenses directly with any HTTP client.
cargo add traffic-orchestrator
Usage
use reqwest::Client; use serde_json::json; let client = Client::new(); let res = client .post("https://api.trafficorchestrator.com/api/v1/validate") .header("Authorization", "Bearer to_live_xxxxxxxxxxxxx") .json(&json!({ "key": "XXXX-XXXX-XXXX-XXXX", "domain": "example.com" })) .send() .await? .json::<serde_json::Value>() .await?; println!("Valid: {}", res["valid"]);
Electron Integration
Protect your Electron desktop apps with license validation at startup.
// main.ts — Electron main process import { app, BrowserWindow, dialog } from 'electron'; const API_KEY = 'to_live_xxxxxxxxxxxxx'; app.on('ready', async () => { const key = store.get('licenseKey'); const res = await fetch( 'https://api.trafficorchestrator.com/api/v1/validate', { method: 'POST', headers: { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ key }) }); const { valid } = await res.json(); if (!valid) { dialog.showErrorBox('License Required', 'Please enter a valid license.'); return; } // License valid — launch main window const win = new BrowserWindow({ width: 1200, height: 800 }); win.loadFile('index.html'); });
React Integration
Add license-gated features to your React application with a custom hook.
// hooks/useLicense.ts import { useState, useEffect } from 'react'; export function useLicense(licenseKey: string) { const [isValid, setIsValid] = useState(false); const [loading, setLoading] = useState(true); useEffect(() => { fetch('/api/validate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ license_key: licenseKey }) }) .then(r => r.json()) .then(data => { setIsValid(data.valid); setLoading(false); }); }, [licenseKey]); return { isValid, loading }; } // Usage in a component function PremiumFeature() { const { isValid, loading } = useLicense('XXXX-XXXX-XXXX-XXXX'); if (loading) return <Spinner />; if (!isValid) return <UpgradePrompt />; return <div>Premium content here</div>; }
Next.js Integration
Server-side license validation with Next.js API routes and middleware.
// app/api/validate/route.ts import { NextResponse } from 'next/server'; export async function POST(req: Request) { const { licenseKey, domain } = await req.json(); const res = await fetch( 'https://api.trafficorchestrator.com/api/v1/validate', { method: 'POST', headers: { 'Authorization': 'Bearer ' + process.env.TO_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ key: licenseKey, domain }) }); const result = await res.json(); return NextResponse.json(result); }
Stripe Integration
Automatically create and manage licenses when customers purchase through Stripe.
How It Works
- Customer completes a Stripe checkout session
- Stripe sends a
checkout.session.completedwebhook to your server - Your server calls Traffic Orchestrator to create a license
- License key is emailed to the customer automatically
// Stripe webhook handler import Stripe from 'stripe'; const TO_API_KEY = process.env.TO_API_KEY; app.post('/webhook', async (req, res) => { const event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], webhookSecret ); if (event.type === 'checkout.session.completed') { const session = event.data.object; // Create license via REST API await fetch('https://api.trafficorchestrator.com/api/v1/portal/licenses', { method: 'POST', headers: { 'Authorization': 'Bearer ' + TO_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ product: session.metadata.product_id, plan: 'pro', customer_email: session.customer_email, metadata: { stripeCustomerId: session.customer, stripeSubscriptionId: session.subscription } }) }); } res.json({ received: true }); });
Rate Limits
All API endpoints enforce rate limits based on your subscription plan. Limits are applied per API key on a sliding 1-minute window.
| Plan | Rate Limit | Window |
|---|---|---|
| Builder (free) | 10 req/min | 1 minute |
| Starter ($29/mo) | 100 req/min | 1 minute |
| Professional ($99/mo) | 1,000 req/min | 1 minute |
| Business ($299/mo) | 10,000 req/min | 1 minute |
| Enterprise (custom) | 50,000 req/min | 1 minute |
Rate Limit Headers
Every API response includes rate limit information in the response headers:
// Included in every API response
X-RateLimit-Limit: 1000 // Max requests per window
X-RateLimit-Remaining: 847 // Requests remaining
X-RateLimit-Reset: 1711234567 // Unix timestamp when window resets
Exceeding the Limit
When you exceed your rate limit, the API returns a 429 Too Many Requests response:
{
"error": "Rate limit exceeded",
"code": "RATE_LIMITED",
"retryAfter": 23
}
retryAfter seconds before retrying. Need higher limits? Upgrade your plan or contact us for custom Enterprise limits.
Error Responses
All API errors follow a consistent JSON format with a human-readable message, a machine-readable error code, and an optional help link.
Error Format
{
"error": "Human-readable error description",
"code": "MACHINE_READABLE_CODE",
"help": "/docs#errors"
}
Common Error Codes
| HTTP | Code | Description |
|---|---|---|
| 400 | MISSING_FIELDS | Required fields are missing from the request body |
| 400 | VALIDATION_ERROR | Request body failed schema validation |
| 401 | AUTH_REQUIRED | No authentication token provided |
| 401 | INVALID_API_KEY | API key is invalid, expired, or revoked |
| 403 | INVALID_TOKEN | JWT token is malformed or expired |
| 403 | DOMAIN_MISMATCH | License is not valid for the requested domain |
| 404 | NOT_FOUND | Requested resource does not exist |
| 429 | RATE_LIMITED | Too many requests — see Rate Limits |
| 500 | INTERNAL_ERROR | Server error — retry with exponential backoff |
code field for programmatic error handling — the error message may change, but error codes are stable across API versions.
Pagination
All list endpoints return paginated results. Use query parameters to control page size and navigation.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number (1-indexed) |
| limit | integer | 20 | Items per page (max 100) |
| offset | integer | 0 | Skip N items (alternative to page) |
Response Format
{
"data": [/* array of items */],
"pagination": {
"page": 2,
"limit": 20,
"total": 156,
"totalPages": 8,
"hasMore": true
}
}
Example
# Fetch page 2 with 50 items per page
curl https://api.trafficorchestrator.com/api/v1/portal/licenses?page=2&limit=50 -H "Authorization: Bearer to_live_xxxxxxxxxxxxx"
Authentication & Tokens
The API supports two authentication methods depending on the use case.
API Keys
Best for server-to-server integrations. API keys are long-lived and can be managed from the dashboard.
# Include in the Authorization header
Authorization: Bearer to_live_xxxxxxxxxxxxx
# API keys use the prefix:
# to_live_ — production keys
# to_test_ — test/sandbox keys
JWT Tokens
Best for browser-based sessions and the customer portal. Tokens are issued during sign-in and have a limited lifetime.
| Property | Value |
|---|---|
| Algorithm | HS256 |
| Token Lifetime | 24 hours |
| Refresh | Automatic via session cookie |
| Token Location | Authorization: Bearer <JWT> |
Scopes & Permissions
| Scope | Access |
|---|---|
| public | License validation, health checks — no auth required |
| portal | Customer portal — manage own licenses, billing, settings |
| admin | Admin operations — manage all users, licenses, system config |
to_test_) during development.
Never expose live API keys in client-side code — enforce license validation server-side.
API Versioning
The API uses URL-based versioning. The current version is v1,
accessible at /api/v1/*.
Stability Promise
Sunset header with the removal date.Version Header
Optionally include the version header to pin your integration to a specific API behavior:
# Pin to v1 behavior (recommended for production)
X-API-Version: 2026-01-01
OpenAPI Specification
The Traffic Orchestrator API provides a complete OpenAPI 3.1 specification and interactive documentation powered by Scalar.
# Download the full OpenAPI 3.1 specification
curl -o openapi.json https://api.trafficorchestrator.com/api/v1/openapi.json
# Import into Postman
# File → Import → Select openapi.json
API Playground
Try the API right here — no setup needed ⚡ Sandbox Mode
Click "Send" to make a request...
Need help? Contact support or get in touch