TusCarrerasTV
Documentación pública

API de TusCarrerasTV

API HTTP en JSON para que cada suscriptor inicie sesión y consuma canales en vivo desde aplicaciones Android para smartphone y Smart TV, reproductores web o cualquier cliente compatible con HTTP.

La API solo expone autenticación de suscriptores, canales y reproducción. La gestión de clientes, vencimientos y credenciales se realiza únicamente desde el panel administrativo.

Base URL https://panel.tuscarrerastv.lat/api/v1
JSON UTF-8 CORS habilitado HTTPS en producción API key + Bearer
Acceso

Sesión de suscriptor

La app se identifica primero con la API key de la aplicación y después inicia sesión con el usuario y la contraseña de la suscripción. La respuesta incluye un token firmado que se envía en la cabecera Authorization.

Paso 1 API key de la aplicación

Se genera una sola vez en Ajustes → API móvil y se comparte con la app. Todas las rutas de /api/v1 excepto /health y la raíz de descubrimiento requieren esta cabecera.

X-API-Key: <api_key>

Si aún no existe una clave, la API responde 503 api_key_not_configured. Si la clave es incorrecta, responde 401 invalid_api_key.

Paso 2 POST/auth/login Requiere API key

Autentica una suscripción y emite el token de acceso.

CampoTipoDescripción
usernamestringUsuario de la suscripción registrado en el panel.
passwordstringContraseña de la suscripción.
POST https://panel.tuscarrerastv.lat/api/v1/auth/login
X-API-Key: TU_API_KEY
Content-Type: application/json

{
  "username": "cliente@rtn.tv",
  "password": "clave-del-cliente"
}
{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 86400,
  "subscription": {
    "id": 12,
    "name": "María López",
    "username": "cliente@rtn.tv",
    "due_date": "2026-12-01",
    "status": "active",
    "status_label": "Activa",
    "days_left": 21
  }
}

Usar el token

Incluye la API key y el token en todas las peticiones protegidas:

X-API-Key: <api_key>

Authorization: Bearer <access_token>

  • · El token vence a las 24 horas; vuelve a iniciar sesión cuando la API responda 401.
  • · Cambiar la contraseña de la suscripción invalida los tokens anteriores.
  • · Si la suscripción vence, el token deja de dar acceso aunque aún no haya expirado.
  • · Tras 5 intentos fallidos desde la misma IP y usuario, el login responde 429 durante 15 minutos.
GET/auth/me API key + Bearer

Devuelve la suscripción asociada al token, útil para validar la sesión al abrir la app.

GET/settings Requiere API key

Branding para la app: nombre del panel, enlaces absolutos del logotipo y del fondo de acceso, y los tres colores configurados. No requiere token de suscripción.

{
  "name": "TusCarrerasTV",
  "logo_url": "https://panel.tuscarrerastv.lat/static/uploads/logo_abc123.png",
  "login_background_url": null,
  "primary_color": "#10b981",
  "secondary_color": "#0a111c",
  "tertiary_color": "#34d399"
}

Los enlaces son null cuando el administrador no ha subido el recurso.

GET/app/version Requiere API key

Devuelve la última versión del APK publicada en Ajustes y su URL absoluta de descarga. No requiere token de suscripción.

{
  "version": "1.4.2",
  "url": "https://panel.tuscarrerastv.lat/static/uploads/app_abc123.apk",
  "available": true
}

Compara version con la versión instalada en el dispositivo. Si available es false, no hay APK publicado: version y url llegan en null.

Guarda el token en almacenamiento seguro del dispositivo. No lo incluyas en URLs ni lo compartas entre usuarios. El endpoint /health y la raíz de descubrimiento son públicos; el resto exige API key.

Canales

Canales y streams en vivo

Todos estos endpoints requieren X-API-Key y Authorization: Bearer <access_token>. Los datos provienen de RTN y el listado se cachea 90 segundos; usa ?refresh=1 para forzar una actualización.

GET/channels

Lista todos los canales con su estado, categoría y metadatos, más un agrupado por categoría.

ParámetroTipoDescripción
refreshbooleanSi es 1, ignora la caché y consulta RTN.
{
  "date": "2026-11-10",
  "count": 2,
  "counts": { "on_air": 1, "upcoming": 1 },
  "categories": [
    {
      "name": "Thoroughbred (United States)",
      "order": 10,
      "counts": { "total": 2, "on_air": 1, "upcoming": 1 },
      "channels": [ /* canales de la categoría */ ]
    }
  ],
  "channels": [
    {
      "key": "abc123",
      "name": "Aqueduct",
      "category": "Thoroughbred (United States)",
      "status": "on_air",
      "stream": "aqueduct",
      "racedate": "20261110",
      "onAirTime": "12:50",
      "duration": "8:00",
      "channelNo": 1
    }
  ]
}
GET/channels/<key>

Devuelve un canal por su clave. Responde 404 not_found si no existe.

GET/catalog

Catálogo completo de pistas disponibles (incluye las que no están en vivo), agrupado por categoría.

GET/stream/<key>

Resuelve el token Millicast y la URL del iframe para reproducir un canal.

ParámetroTipoDescripción
forcebooleanFuerza la renovación del token aunque siga vigente.
{
  "key": "abc123",
  "name": "Aqueduct",
  "stream": "aqueduct",
  "accountId": "ACCOUNT_ID",
  "token": "eyJhbGciOi...",
  "tokenExpires": 1762803600,
  "expiresIn": 1800,
  "millicastUrl": "millicast://ACCOUNT_ID/aqueduct",
  "iframe": "https://stream.robertsstream.com/streamlive.php?...",
  "apiRefresh": "/api/v1/stream/abc123"
}

El token de RTN vence; llama de nuevo al endpoint cuando expiresIn llegue a 0. La API reutiliza el token en caché mientras le queden más de 150 segundos de vigencia.

GET/playlist

Devuelve en una sola respuesta todos los canales actualmente en vivo con su token y URL de iframe. Ideal para una pantalla de inicio tipo parrilla.

Referencia

Errores

Todas las respuestas de error son JSON con un campo error y, cuando aplica, code y fields.

CódigoSignificadoAcción sugerida
400Solicitud inválida o JSON mal formadoRevisa el cuerpo enviado.
401API key inválida, credenciales incorrectas o token ausente/inválidoRevisa X-API-Key e inicia sesión de nuevo si el token caducó.
403Suscripción vencidaRenueva la suscripción desde el panel.
404Canal no encontradoVerifica la clave del canal.
422Validación de campos del loginLee fields y corrige.
429Demasiados intentos de loginEspera el tiempo indicado en Retry-After.
502Error de RTN o del streamReintenta; si persiste, revisa la cuenta RTN.
503API key no configurada en el panelGenera una en Ajustes → API móvil.
{
  "error": "Usuario o contraseña incorrectos.",
  "code": "invalid_credentials"
}
Ejemplos

Ejemplos de uso

Login y canales con cURL

# 0. Branding de la app (solo API key)
curl -s "https://panel.tuscarrerastv.lat/api/v1/settings" \
  -H "X-API-Key: TU_API_KEY"

# 1. Obtén el token
curl -s -X POST "https://panel.tuscarrerastv.lat/api/v1/auth/login" \
  -H "X-API-Key: TU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"username":"cliente@rtn.tv","password":"clave-del-cliente"}'

# 2. Usa access_token en las peticiones protegidas
curl -s "https://panel.tuscarrerastv.lat/api/v1/channels" \
  -H "X-API-Key: TU_API_KEY" \
  -H "Authorization: Bearer TU_ACCESS_TOKEN"

JavaScript (fetch)

const API = "https://panel.tuscarrerastv.lat/api/v1";
const API_KEY = "TU_API_KEY";

async function getBranding() {
  const response = await fetch(`${API}/settings`, {
    headers: { "X-API-Key": API_KEY, Accept: "application/json" },
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

async function login(username, password) {
  const response = await fetch(`${API}/auth/login`, {
    method: "POST",
    headers: {
      "X-API-Key": API_KEY,
      "Content-Type": "application/json",
      Accept: "application/json",
    },
    body: JSON.stringify({ username, password }),
  });
  const data = await response.json().catch(() => ({}));
  if (!response.ok) throw new Error(data.error || `HTTP ${response.status}`);
  return data.access_token;
}

async function getLiveChannels(token) {
  const response = await fetch(`${API}/channels`, {
    headers: {
      "X-API-Key": API_KEY,
      Authorization: `Bearer ${token}`,
      Accept: "application/json",
    },
  });
  if (!response.ok) {
    const problem = await response.json().catch(() => ({}));
    throw new Error(problem.error || `HTTP ${response.status}`);
  }
  const data = await response.json();
  return data.channels.filter((channel) => channel.status === "on_air");
}

async function getStream(token, key) {
  const response = await fetch(`${API}/stream/${encodeURIComponent(key)}`, {
    headers: { "X-API-Key": API_KEY, Authorization: `Bearer ${token}` },
  });
  if (!response.ok) throw new Error("No se pudo resolver el stream");
  return response.json();
}
Clientes

Integración en Android y Smart TV

Identificación: incluye la API key de la aplicación en X-API-Key en cada petición. Es una clave de app, no de usuario: se genera en Ajustes → API móvil y se comparte con la build mediante configuración segura.

Sesión: pide usuario y contraseña al suscriptor, llama a /auth/login con la API key y guarda el token en EncryptedSharedPreferences o DataStore. Al abrir la app, valida con /auth/me; si responde 401, vuelve al login.

Reproducción: RTN entrega un token Millicast y una URL de iframe. El campo iframe puede cargarse en un WebView (Android/Android TV) con JavaScript y autoplay habilitados. El esquema millicast:// no lo reproduce ExoPlayer/Media3 de forma nativa.

Renovación: guarda tokenExpires del stream y vuelve a pedir /stream/<key> unos minutos antes de que venza. No compartas el token entre dispositivos ni lo guardes en texto plano.

Seguridad: en producción usa HTTPS. La API key protege la app a nivel de aplicación; el acceso a canales depende además de la suscripción y puede revocarse venciéndola, cambiando su contraseña o regenerando la API key.

Kotlin + OkHttp (esqueleto)

private const val API_KEY = "TU_API_KEY"
private val client = OkHttpClient()

fun getBranding(): String {
    val request = Request.Builder()
        .url("https://panel.tuscarrerastv.lat/api/v1/settings")
        .addHeader("X-API-Key", API_KEY)
        .addHeader("Accept", "application/json")
        .build()

    client.newCall(request).execute().use { response ->
        if (!response.isSuccessful) error("HTTP ${response.code}")
        return response.body?.string().orEmpty()
    }
}

fun login(username: String, password: String): String {
    val body = """{"username":"$username","password":"$password"}"""
        .toRequestBody("application/json".toMediaType())
    val request = Request.Builder()
        .url("https://panel.tuscarrerastv.lat/api/v1/auth/login")
        .addHeader("X-API-Key", API_KEY)
        .post(body)
        .build()

    client.newCall(request).execute().use { response ->
        if (!response.isSuccessful) error("HTTP ${response.code}")
        val json = JSONObject(response.body?.string().orEmpty())
        return json.getString("access_token")
    }
}

fun getChannels(token: String): String {
    val request = Request.Builder()
        .url("https://panel.tuscarrerastv.lat/api/v1/channels")
        .addHeader("X-API-Key", API_KEY)
        .addHeader("Authorization", "Bearer $token")
        .addHeader("Accept", "application/json")
        .build()

    client.newCall(request).execute().use { response ->
        if (!response.isSuccessful) error("HTTP ${response.code}")
        return response.body?.string().orEmpty()
    }
}

El endpoint /health es público y no requiere token. Úsalo para monitoreo y para verificar conectividad desde la app antes de mostrar el login.

Tabla de endpoints

Todas las rutas son relativas a https://panel.tuscarrerastv.lat/api/v1.

MétodoRutaAutenticaciónDescripción
GET/healthPúblicaEstado del servicio.
GET/settingsAPI keyNombre, logo, fondo de login y colores para la app.
GET/app/versionAPI keyÚltima versión del APK y URL de descarga.
POST/auth/loginAPI keyLogin con credenciales de suscripción.
GET/auth/meAPI key + BearerSuscripción asociada al token.
GET/channelsAPI key + BearerCanales y agrupación por categoría.
GET/channels/{key}API key + BearerDetalle de un canal.
GET/catalogAPI key + BearerCatálogo completo de pistas.
GET/stream/{key}API key + BearerToken e iframe de reproducción.
GET/playlistAPI key + BearerTodos los streams en vivo.

La API no expone listado, creación, edición ni eliminación de suscripciones. Esas operaciones se realizan exclusivamente desde el panel administrativo.