Home / Documentation

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

bash
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

typescript
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');
}
That's it! You now have software licensing in your app. Check out the full API reference for more features.

Authentication

All API requests require an API key. You can find your API keys in the dashboard.

http
Authorization: Bearer to_live_xxxxxxxxxxxxx

API Key Types

Type Prefix Use Case
Live to_live_ Production applications
Test to_test_ Development and testing
Never expose your API keys in client-side code. Use a backend proxy for sensitive operations.

Validate License

Verify that a license key is valid and active.

POST /api/v1/validate

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

json
{
  "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.

Official SDK available! Install via npm: npm install @traffic-orchestrator/client

Validate a License

typescript
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

typescript
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

StatusDescription
activeLicense is valid and usable
expiredSubscription or trial has expired
suspendedTemporarily disabled by admin
revokedPermanently disabled

Activations

Activations track which devices or machines are using a license. Each license has a configurable maximum number of activations.

How Activation Works

  1. Generate a unique machine ID from the user's device
  2. Call the activate endpoint with the license key and machine ID
  3. On subsequent app launches, validate the license with the same machine ID
  4. When a user switches devices, deactivate the old machine first
Use 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

FieldTypeDescription
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

EventDescription
license.createdA new license was created
license.activatedA license was activated on a device
license.deactivatedA license was deactivated
license.expiredA license has expired
license.suspendedA license was suspended
validation.failedA validation attempt failed

Webhook Payload

json
{
  "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:

HeaderDescription
X-TO-SignatureHMAC-SHA256 signature of the request body
X-TO-TimestampUnix timestamp when the webhook was sent
X-TO-EventThe event type (e.g., license.activated)
X-TO-Delivery-IDUnique 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.

javascript
// 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
# 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
// 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:

1st retry after 30s
2nd retry after 5m
3rd retry after 30m
Final after 4h
Important: Use the 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.

POST /api/v1/licenses

Creates a new license and returns the generated key.

REQUEST BODY

ParameterTypeRequiredDescription
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)
email string Customer email to associate
expires_at string ISO 8601 expiry date
metadata object Custom key-value pairs

Response

json
{
  "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.

POST /api/v1/activate

Registers a device against a license key, consuming one activation slot.

REQUEST BODY

ParameterTypeRequiredDescription
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.

POST /api/v1/deactivate

Removes a machine from a license's activation list.

REQUEST BODY

ParameterTypeRequiredDescription
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.

GET /api/v1/licenses

Returns a paginated list of licenses with optional filtering.

QUERY PARAMETERS

ParameterTypeDescription
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.

Official SDK available! Install via pip: pip install traffic-orchestrator

Usage

python
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.

Official SDK coming soon. Use the REST API directly with net/http.

Usage

go
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.

Official SDK coming soon. Use the REST API directly with cURL or Guzzle.

Usage

php
<?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.

Official SDK coming soon. Use the REST API directly with Net::HTTP or Faraday.

Usage

ruby
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.

Official SDK coming soon. Use the REST API directly with HttpClient.

Usage

csharp
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.

Official SDK available! Install via cargo: cargo add traffic-orchestrator

Usage

rust
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.

typescript
// 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.

typescript
// 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.

typescript
// 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

  1. Customer completes a Stripe checkout session
  2. Stripe sends a checkout.session.completed webhook to your server
  3. Your server calls Traffic Orchestrator to create a license
  4. License key is emailed to the customer automatically
typescript
// 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 });
});
You can also configure the Stripe integration directly in the dashboard without writing any code. Go to Settings → Integrations → Stripe.

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:

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:

429 Response
{
  "error": "Rate limit exceeded",
  "code": "RATE_LIMITED",
  "retryAfter": 23
}
Best practice: Use exponential backoff when receiving 429 responses. Wait 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 Response Body
{
  "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
Always check the 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

Paginated Response
{
  "data": [/* array of items */],
  "pagination": {
    "page": 2,
    "limit": 20,
    "total": 156,
    "totalPages": 8,
    "hasMore": true
  }
}

Example

Bash
# 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.

API Key Auth
# 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
Security tip: Use test keys (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

✓ Backward Compatible
New fields may be added to responses but existing fields will not be removed or renamed.
🔔 Deprecation Notices
Deprecated endpoints return a Sunset header with the removal date.
🕑 Migration Window
Breaking changes are introduced only in new major versions with at least 90 days notice.

Version Header

Optionally include the version header to pin your integration to a specific API behavior:

Request Header
# Pin to v1 behavior (recommended for production)
X-API-Version: 2026-01-01
Subscribe to the changelog to be notified of API updates, deprecations, and new features.

OpenAPI Specification

The Traffic Orchestrator API provides a complete OpenAPI 3.1 specification and interactive documentation powered by Scalar.

Interactive API Docs
Try endpoints directly in your browser
OpenAPI 3.1 JSON
Import into Postman, Insomnia, or any tool
Download OpenAPI Spec
# 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
The OpenAPI spec is auto-generated from the live API and stays in sync with all endpoints, request schemas, and response formats.

API Playground

Try the API right here — no setup needed ⚡ Sandbox Mode

POST https://api.trafficorchestrator.com/api/v1/validate
Response
Click "Send" to make a request...

Need help? Contact support or get in touch