Licencias
Gestiona la validación y activación de licencias y el seguimiento de dispositivos.
429 Too Many Requests con una cabecera Retry-After: 60 hasta que se reinicie la ventana.Validar licencia
Valida y activa una clave de licencia. Este endpoint tiene un límite de 5 solicitudes por minuto por dirección IP.
Endpoint: POST /api/licenses/validate
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
license_key | string | Sí | Clave de licencia que se va a validar |
device_id | string | Sí | Identificador estable y único del dispositivo que se activa. Envía siempre el mismo valor para un mismo dispositivo: es lo que reclama y vuelve a reclamar el puesto de ese dispositivo |
curl -X POST "$BASE_URL/api/licenses/validate" \
-H "Content-Type: application/json" \
-d '{"license_key":"XXXX-XXXX-XXXX-XXXX","device_id":"device_abc123"}'
interface LicenseValidation {
valid: boolean
license: {
id: string
status: string
product: object
activation_date: string
expiration_date: string
current_activations: number
}
}
const result: LicenseValidation = await $fetch('/api/licenses/validate', {
method: 'POST',
body: {
license_key: 'XXXX-XXXX-XXXX-XXXX',
device_id: 'device_abc123'
}
})
import requests
response = requests.post(
"$BASE_URL/api/licenses/validate",
json={
"license_key": "XXXX-XXXX-XXXX-XXXX",
"device_id": "device_abc123"
}
)
result = response.json()
package main
import (
"bytes"
"encoding/json"
"net/http"
)
func validateLicense() {
payload := map[string]string{
"license_key": "XXXX-XXXX-XXXX-XXXX",
"device_id": "device_abc123",
}
body, _ := json.Marshal(payload)
resp, _ := http.Post(
"$BASE_URL/api/licenses/validate",
"application/json",
bytes.NewBuffer(body),
)
defer resp.Body.Close()
}
<?php
$response = file_get_contents(
'$BASE_URL/api/licenses/validate',
false,
stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/json',
'content' => json_encode([
'license_key' => 'XXXX-XXXX-XXXX-XXXX',
'device_id' => 'device_abc123'
])
]
])
);
$result = json_decode($response, true);
use reqwest::Client;
use serde_json::json;
let client = Client::new();
let response = client
.post("$BASE_URL/api/licenses/validate")
.json(&json!({
"license_key": "XXXX-XXXX-XXXX-XXXX",
"device_id": "device_abc123"
}))
.send()
.await?;
Respuesta (éxito):
{
"valid": true,
"license": {
"id": "uuid",
"status": "active",
"product": { "id": "uuid", "name": "studio", "features": ["client_api", "api_keys"] },
"activation_date": "2024-01-01T00:00:00Z",
"expiration_date": "2025-12-31T23:59:59Z",
"current_activations": 1
}
}
Respuesta (error):
{
"statusCode": 400,
"statusMessage": "Invalid licence key or validation failed"
}
| Estado | Cuándo |
|---|---|
400 | Falta license_key o device_id, está vacío o es demasiado largo, o la clave es desconocida, está inactiva o ha caducado |
409 | Todos los puestos de la licencia ya están ocupados por otro dispositivo y ninguno ha estado inactivo el tiempo suficiente para recuperarlo |
429 | Se ha superado el límite de frecuencia, consulta Límites de frecuencia |
400 nunca indica qué campo ha fallado: comprueba que license_key y device_id estén presentes y no vacíos antes de reintentar.Desactivar licencia
Autenticación requeridaRequiere la cookie de sesión session_token del propietario de la licencia (consulta Autenticación): solo puedes desactivar una licencia que te pertenezca.
Desactiva una licencia en un dispositivo concreto o en todos los dispositivos.
Endpoint: POST /api/licenses/deactivate
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
license_key | string | Sí | Clave de licencia que se va a desactivar |
device_id | string | No | Dispositivo concreto que se va a desactivar (omítelo para desactivar todos los dispositivos) |
curl -X POST "$BASE_URL/api/licenses/deactivate" \
-b cookies.txt \
-H "Content-Type: application/json" \
-d '{"license_key":"XXXX-XXXX-XXXX-XXXX","device_id":"device_abc123"}'
const result = await $fetch('/api/licenses/deactivate', {
method: 'POST',
// session cookie (session_token) is sent automatically same-origin
body: {
license_key: 'XXXX-XXXX-XXXX-XXXX',
device_id: 'device_abc123'
}
})
import requests
response = requests.post(
"$BASE_URL/api/licenses/deactivate",
headers={"Cookie": f"session_token={token}"},
json={
"license_key": "XXXX-XXXX-XXXX-XXXX",
"device_id": "device_abc123"
}
)
req, _ := http.NewRequest("POST",
"$BASE_URL/api/licenses/deactivate",
bytes.NewBuffer(body))
req.Header.Set("Cookie", "session_token="+token)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
<?php
$response = file_get_contents(
'$BASE_URL/api/licenses/deactivate',
false,
stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n" .
"Cookie: session_token=$token",
'content' => json_encode([
'license_key' => 'XXXX-XXXX-XXXX-XXXX',
'device_id' => 'device_abc123'
])
]
])
);
let response = client
.post("$BASE_URL/api/licenses/deactivate")
.header("Cookie", format!("session_token={}", token))
.json(&json!({
"license_key": "XXXX-XXXX-XXXX-XXXX",
"device_id": "device_abc123"
}))
.send()
.await?;
Respuesta:
{
"success": true,
"message": "Device deactivated successfully",
"deactivated_count": 1,
"remaining_activations": 4
}
remaining_activations es el número de puestos aún en uso tras la desactivación, es decir, el número de dispositivos distintos que siguen activos en la licencia. No es el número de puestos libres. Por tanto, desactivar todos los dispositivos devuelve 0. Calcula los puestos libres como activation_limit - remaining_activations, usando el activation_limit de GET /api/licenses/status. Sin device_id, el mensaje es «All devices deactivated successfully»; deactivated_count es el número de puestos que ha liberado esta llamada.Estado de la licencia
Autenticación requeridaRequiere la cookie de sesión session_token del propietario de la licencia (consulta Autenticación): solo puedes consultar una licencia que te pertenezca.
Comprueba el estado actual de una licencia. Tiene un límite de 30 solicitudes por minuto por IP.
Endpoint: GET /api/licenses/status
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
license_key | query | Sí | Clave de licencia que se va a comprobar |
curl -X GET "$BASE_URL/api/licenses/status?license_key=XXXX-XXXX-XXXX-XXXX" \
-b cookies.txt
const status = await $fetch('/api/licenses/status', {
// session cookie (session_token) is sent automatically same-origin
query: { license_key: 'XXXX-XXXX-XXXX-XXXX' }
})
import requests
response = requests.get(
"$BASE_URL/api/licenses/status",
headers={"Cookie": f"session_token={token}"},
params={"license_key": "XXXX-XXXX-XXXX-XXXX"}
)
req, _ := http.NewRequest("GET",
"$BASE_URL/api/licenses/status?license_key=XXXX-XXXX-XXXX-XXXX",
nil)
req.Header.Set("Cookie", "session_token="+token)
resp, _ := http.DefaultClient.Do(req)
<?php
$response = file_get_contents(
'$BASE_URL/api/licenses/status?license_key=XXXX-XXXX-XXXX-XXXX',
false,
stream_context_create([
'http' => [
'header' => "Cookie: session_token=$token"
]
])
);
let response = client
.get("$BASE_URL/api/licenses/status")
.header("Cookie", format!("session_token={}", token))
.query(&[("license_key", "XXXX-XXXX-XXXX-XXXX")])
.send()
.await?;
Respuesta:
{
"license": {
"id": "uuid",
"license_key": "XXXX-XXXX-XXXX-XXXX",
"status": "active",
"purchase_date": "2024-01-01T00:00:00Z",
"activation_date": "2024-01-01T00:00:00Z",
"expiration_date": "2025-12-31T23:59:59Z",
"days_until_expiration": 365,
"activation_limit": 5,
"current_activations": 2,
"available_activations": 3,
"is_expired": false,
"is_active": true,
"product": { "id": "uuid", "name": "Studio 8K", "description": "...", "features": [] },
"user": { "id": "uuid", "first_name": "Jane", "last_name": "Doe", "email": "user@example.com" }
},
"active_devices": [
{
"id": "uuid",
"device_id": "device_abc123",
"device_name": "Studio Mac",
"platform": "macos",
"app_version": "3.1.2",
"activated_at": "2024-01-01T00:00:00Z",
"last_seen_at": "2024-01-02T09:00:00Z",
"ip_address": "192.168.1.1"
}
]
}
Ciclo de vida de la licencia
Límites de frecuencia
Los endpoints de licencias tienen un límite de frecuencia por IP en una ventana fija de 60 segundos. Con un 429, se devuelve una cabecera:
| Cabecera | Descripción |
|---|---|
Retry-After | Segundos hasta que se reinicia la ventana (siempre 60) |
Límites por endpoint
| Endpoint | Solicitudes / 60 s | Nivel |
|---|---|---|
/api/licenses/validate | 5 | Estricto |
/api/licenses/status | 30 | Estándar |
/api/licenses/deactivate | 30 | Estándar |
POST /api/licenses/validate aplica el límite Estricto dos veces: una por IP y otra asociada a la license_key enviada, de modo que no se pueda bombardear una misma clave desde muchas IP.
429 Too Many Requests durante el resto de la ventana de 60 segundos. No hay ninguna duración de bloqueo progresiva ni creciente.Tipos de licencia
Cada licencia corresponde a uno de los niveles de plan actuales. El conjunto de funciones y límites de referencia de cada nivel es el que muestra la página de precios: consulta Comparar planes.
| Nivel | Para quién es | Novedades destacadas respecto al nivel inferior |
|---|---|---|
free | Plan gratuito permanente, sin tarjeta y sin caducidad | Un control remoto para presentaciones sencillo: una sala, un presentador, pantalla compartida con marca de agua y Stream Deck; todas las demás funciones están bloqueadas |
presenter | Presentadores | Timer Pro, chat, dibujo, varios anfitriones y NDI |
pro (Studio HD) | Productores en solitario | Llamantes, salas personalizadas, compartir vídeo y lo esencial de Studio |
ultra (Studio 4K) | Usuarios avanzados y streamers | Salas ilimitadas, temas de colores de marca guardados, capas, grabación ISO, repetición y el conjunto ampliado de herramientas de Studio |
studio (Studio 8K) | Equipos de producción | Diseñador de disposición de salas, marca blanca, la Client API, claves de API, dominios personalizados y licencias de equipo compartidas por puesto |
enterprise | Grandes organizaciones y departamentos de compras | SSO, SCIM y roles de equipo personalizados, contratos anuales por puesto |