Rest Api

EPGOAT Documentation - AI Reference (Educational)

REST API Specification

Status: Reference Last Updated: 2025-11-02 Related Docs: API Integration, System Architecture Code Location: Future implementation - Cloudflare Workers


Project: EPGOAT SaaS Platform Component: REST API & EPG Access Endpoints Version: 1.0.0 Original Date: 2025-10-30 Planning Status: Planning Owner: Backend Team Format: OpenAPI 3.0


Table of Contents

  1. Executive Summary
  2. API Overview
  3. Authentication
  4. Rate Limiting
  5. Error Handling
  6. Consumer API
  7. Reseller API
  8. Admin API
  9. EPG Access API
  10. Webhook Endpoints
  11. OpenAPI Specification

Executive Summary

The EPGOAT API is a RESTful service built on Cloudflare Workers, providing:

  • Consumer API: Subscription management, EPG key access, profile settings
  • Reseller API: Bulk key generation, usage analytics, customer management
  • Admin API: Provider management, user administration, system monitoring
  • EPG Access API: Public endpoint for serving XMLTV files via unique keys
  • Webhook API: Stripe payment webhooks, system notifications

Base URLs

Environment Base URL
Production https://api.epgoat.tv
Staging https://api-staging.epgoat.tv
EPG Access https://epgo.at

Key Features

  • JWT Authentication: Auth0-issued tokens for all authenticated endpoints
  • Key-Based Access: UUID keys for EPG file access (no auth required)
  • Rate Limiting: 60 req/min (authenticated), 1 req/min (EPG keys)
  • CORS: Enabled for app.epgoat.tv and admin.epgoat.tv
  • Versioning: URL-based (/v1/...)
  • Content Type: application/json (except XMLTV: application/xml)

API Overview

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  Client Applications                     β”‚
β”‚  (Consumer Portal, Reseller Portal, Admin Dashboard)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
                     β”‚ HTTPS + JWT
                     β”‚
                     β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚   Cloudflare Workers       β”‚
        β”‚   (api.epgoat.tv)          β”‚
        β”‚                            β”‚
        β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
        β”‚  β”‚ API Router           β”‚  β”‚
        β”‚  β”‚ - /v1/auth/*         β”‚  β”‚
        β”‚  β”‚ - /v1/subscriptions/*β”‚  β”‚
        β”‚  β”‚ - /v1/keys/*         β”‚  β”‚
        β”‚  β”‚ - /v1/admin/*        β”‚  β”‚
        β”‚  β”‚ - /webhooks/*        β”‚  β”‚
        β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
        β”‚             β”‚              β”‚
        β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
        β”‚  β”‚ Middleware Layer     β”‚  β”‚
        β”‚  β”‚ - JWT Validation     β”‚  β”‚
        β”‚  β”‚ - CORS Handling      β”‚  β”‚
        β”‚  β”‚ - Rate Limiting      β”‚  β”‚
        β”‚  β”‚ - Error Handling     β”‚  β”‚
        β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚           β”‚           β”‚
          β–Ό           β–Ό           β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚Cloudflareβ”‚ β”‚ Stripe  β”‚ β”‚  Auth0  β”‚
    β”‚    D1    β”‚ β”‚   API   β”‚ β”‚   API   β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Endpoint Categories

Category Base Path Auth Required Rate Limit
Authentication /v1/auth No (registration), Yes (profile) 10/min
Subscriptions /v1/subscriptions Yes 60/min
Keys (Consumer) /v1/keys Yes 60/min
Keys (Reseller) /v1/reseller/keys Yes (reseller role) 120/min
Admin /v1/admin Yes (admin role) 120/min
EPG Access /<key>/<provider>.xml No (key-based) 1/min per key
Webhooks /webhooks No (signature verification) N/A

Authentication

JWT Token Structure

Header:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "auth0_key_id"
}

Payload:

{
  "iss": "https://epgoat.us.auth0.com/",
  "sub": "auth0|123456",
  "aud": "https://api.epgoat.tv",
  "iat": 1698710400,
  "exp": 1698796800,
  "azp": "client_id",
  "scope": "openid profile email",
  "https://epgoat.tv/role": "consumer",
  "https://epgoat.tv/epgoat_user_id": "uuid",
  "https://epgoat.tv/subscription_status": "active"
}

Authorization Header

All authenticated requests must include:

Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

Token Validation (Cloudflare Worker)

import jwt from '@tsndr/cloudflare-worker-jwt';

async function validateJWT(request) {
  const authHeader = request.headers.get('Authorization');

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return { valid: false, error: 'Missing authorization header' };
  }

  const token = authHeader.substring(7);

  try {
    const isValid = await jwt.verify(token, AUTH0_PUBLIC_KEY, {
      algorithm: 'RS256',
      issuer: 'https://epgoat.us.auth0.com/',
      audience: 'https://api.epgoat.tv'
    });

    if (!isValid) {
      return { valid: false, error: 'Invalid token' };
    }

    const decoded = jwt.decode(token);

    return {
      valid: true,
      user: {
        auth0_id: decoded.payload.sub,
        epgoat_user_id: decoded.payload['https://epgoat.tv/epgoat_user_id'],
        role: decoded.payload['https://epgoat.tv/role'],
        subscription_status: decoded.payload['https://epgoat.tv/subscription_status']
      }
    };
  } catch (error) {
    return { valid: false, error: error.message };
  }
}

Rate Limiting

Rate Limit Headers

All responses include:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1698710460

Rate Limit Implementation

class RateLimiter {
  constructor(limit, window) {
    this.limit = limit;
    this.window = window; // seconds
  }

  async check(key, kv) {
    const now = Math.floor(Date.now() / 1000);
    const windowStart = now - this.window;

    // Get request count from KV
    const data = await kv.get(`ratelimit:${key}`, { type: 'json' }) || { count: 0, reset: now + this.window };

    if (data.reset < now) {
      // Window expired, reset
      data.count = 1;
      data.reset = now + this.window;
    } else {
      data.count += 1;
    }

    // Store updated count
    await kv.put(`ratelimit:${key}`, JSON.stringify(data), {
      expirationTtl: this.window
    });

    return {
      allowed: data.count <= this.limit,
      limit: this.limit,
      remaining: Math.max(0, this.limit - data.count),
      reset: data.reset
    };
  }
}

Rate Limit Tiers

User Type Limit Window
Anonymous 10 req 1 minute
Consumer 60 req 1 minute
Reseller 120 req 1 minute
Admin 120 req 1 minute
EPG Key 1 req 1 minute

Error Handling

Standard Error Response

{
  "success": false,
  "error": {
    "code": "SUBSCRIPTION_NOT_FOUND",
    "message": "Subscription not found",
    "details": {
      "user_id": "uuid"
    },
    "timestamp": "2025-10-30T12:00:00Z",
    "request_id": "req_abc123"
  }
}

HTTP Status Codes

Code Meaning When to Use
200 OK Successful GET, PUT, DELETE
201 Created Successful POST creating new resource
204 No Content Successful DELETE with no response body
400 Bad Request Invalid request parameters
401 Unauthorized Missing or invalid JWT token
403 Forbidden Valid token but insufficient permissions
404 Not Found Resource not found
409 Conflict Duplicate resource (e.g., email already exists)
422 Unprocessable Entity Validation failed
429 Too Many Requests Rate limit exceeded
500 Internal Server Error Server error
503 Service Unavailable Temporary outage

Error Codes

Code HTTP Status Description
UNAUTHORIZED 401 Missing or invalid token
FORBIDDEN 403 Insufficient permissions
NOT_FOUND 404 Resource not found
VALIDATION_ERROR 422 Input validation failed
RATE_LIMIT_EXCEEDED 429 Too many requests
SUBSCRIPTION_NOT_FOUND 404 User has no subscription
SUBSCRIPTION_INACTIVE 403 Subscription not active
PAYMENT_REQUIRED 402 Payment method required
KEY_NOT_FOUND 404 EPG key not found
KEY_EXPIRED 403 EPG key has expired
KEY_SUSPENDED 403 EPG key suspended for abuse
PROVIDER_NOT_FOUND 404 Provider not found
INTERNAL_ERROR 500 Internal server error

Consumer API

Base Path: /v1

Authentication Endpoints

POST /v1/auth/register

Description: Create new user after Auth0 signup (called by Auth0 Action)

Authentication: Auth0 Management API token

Request:

{
  "auth0_user_id": "auth0|123456",
  "email": "user@example.com",
  "name": "John Doe",
  "role": "consumer"
}

Response (201):

{
  "success": true,
  "data": {
    "user_id": "uuid",
    "access_key": "uuid",
    "epg_url": "https://epgo.at/uuid/tps.xml",
    "trial_end": "2025-11-29T23:59:59Z"
  }
}

GET /v1/auth/me

Description: Get current user profile

Authentication: Required

Response (200):

{
  "success": true,
  "data": {
    "user_id": "uuid",
    "email": "user@example.com",
    "name": "John Doe",
    "role": "consumer",
    "subscription_status": "active",
    "created_at": "2025-10-30T12:00:00Z"
  }
}

PUT /v1/auth/me

Description: Update user profile

Authentication: Required

Request:

{
  "name": "John Smith",
  "timezone": "America/New_York"
}

Response (200):

{
  "success": true,
  "data": {
    "user_id": "uuid",
    "name": "John Smith",
    "timezone": "America/New_York",
    "updated_at": "2025-10-30T12:00:00Z"
  }
}

Subscription Endpoints

GET /v1/subscriptions/current

Description: Get current user's subscription

Authentication: Required

Response (200):

{
  "success": true,
  "data": {
    "subscription_id": "uuid",
    "plan_type": "consumer_annual",
    "status": "active",
    "trial_start": null,
    "trial_end": null,
    "current_period_start": "2025-10-30",
    "current_period_end": "2026-10-30",
    "will_auto_charge": false,
    "will_cancel_at": null,
    "stripe_subscription_id": "sub_123",
    "last_payment": {
      "amount": 1000,
      "currency": "usd",
      "paid_at": "2025-10-30T12:00:00Z"
    }
  }
}

POST /v1/subscriptions/create

Description: Create new subscription (via Stripe Checkout)

Authentication: Required

Request:

{
  "plan": "consumer_annual",
  "success_url": "https://app.epgoat.tv/subscription/success",
  "cancel_url": "https://app.epgoat.tv/subscription"
}

Response (201):

{
  "success": true,
  "data": {
    "checkout_url": "https://checkout.stripe.com/c/pay/cs_test_abc123"
  }
}

POST /v1/subscriptions/cancel

Description: Cancel subscription (effective at period end)

Authentication: Required

Response (200):

{
  "success": true,
  "data": {
    "subscription_id": "uuid",
    "status": "cancel_at_end",
    "will_cancel_at": "2026-10-30",
    "message": "Your subscription will remain active until 2026-10-30"
  }
}

POST /v1/subscriptions/reactivate

Description: Reactivate canceled subscription

Authentication: Required

Response (200):

{
  "success": true,
  "data": {
    "subscription_id": "uuid",
    "status": "active",
    "message": "Your subscription has been reactivated"
  }
}

POST /v1/subscriptions/upgrade

Description: Upgrade subscription plan

Authentication: Required

Request:

{
  "new_plan": "consumer_annual"
}

Response (200):

{
  "success": true,
  "data": {
    "subscription_id": "uuid",
    "old_plan": "consumer_monthly",
    "new_plan": "consumer_annual",
    "proration_credit": 350,
    "next_billing_date": "2026-10-30"
  }
}

EPG Key Endpoints (Consumer)

GET /v1/keys/my

Description: Get consumer's EPG access key

Authentication: Required

Response (200):

{
  "success": true,
  "data": {
    "key_id": "uuid",
    "epg_url": "https://epgo.at/uuid/tps.xml",
    "status": "active",
    "created_at": "2025-10-30T12:00:00Z",
    "expires_at": "2026-10-30T23:59:59Z",
    "provider": {
      "id": 1,
      "name": "TPS",
      "logo_url": "https://files.epgoat.tv/logos/tps.png"
    }
  }
}

GET /v1/keys/my/stats

Description: Get usage statistics for consumer's key

Authentication: Required

Response (200):

{
  "success": true,
  "data": {
    "key_id": "uuid",
    "access_count": 1234,
    "last_accessed_at": "2025-10-30T11:45:00Z",
    "last_ip": "192.168.1.1",
    "unique_ips": 2,
    "access_history": [
      {
        "date": "2025-10-30",
        "count": 24
      },
      {
        "date": "2025-10-29",
        "count": 24
      }
    ]
  }
}

POST /v1/keys/my/regenerate

Description: Regenerate EPG key (invalidates old key)

Authentication: Required

Response (200):

{
  "success": true,
  "data": {
    "old_key_id": "old-uuid",
    "new_key_id": "new-uuid",
    "epg_url": "https://epgo.at/new-uuid/tps.xml",
    "warning": "Your old EPG URL will stop working immediately"
  }
}

Invoice Endpoints

GET /v1/invoices

Description: List user's invoices

Authentication: Required

Query Parameters: - limit (number, default: 10, max: 100) - offset (number, default: 0)

Response (200):

{
  "success": true,
  "data": {
    "invoices": [
      {
        "invoice_id": "in_123",
        "amount_due": 1000,
        "amount_paid": 1000,
        "currency": "usd",
        "status": "paid",
        "period_start": "2025-10-30",
        "period_end": "2026-10-30",
        "invoice_url": "https://invoice.stripe.com/i/...",
        "invoice_pdf": "https://pay.stripe.com/invoice/.../pdf",
        "created_at": "2025-10-30T12:00:00Z"
      }
    ],
    "total_count": 1,
    "has_more": false
  }
}

Reseller API

Base Path: /v1/reseller

Key Management

GET /v1/reseller/keys

Description: List all generated keys

Authentication: Required (reseller role)

Query Parameters: - provider_id (number, optional) - status (string, optional: "active", "inactive", "expired") - search (string, optional: search by customer email/name) - limit (number, default: 50, max: 200) - offset (number, default: 0)

Response (200):

{
  "success": true,
  "data": {
    "keys": [
      {
        "key_id": "uuid",
        "customer_name": "Customer Name",
        "customer_email": "customer@example.com",
        "provider": {
          "id": 1,
          "name": "TPS"
        },
        "status": "active",
        "created_at": "2025-10-30T12:00:00Z",
        "expires_at": "2026-10-30T23:59:59Z",
        "last_accessed_at": "2025-10-30T11:45:00Z",
        "access_count": 456,
        "notes": "VIP customer"
      }
    ],
    "total_count": 25,
    "keys_remaining": 25
  }
}

POST /v1/reseller/keys

Description: Generate new EPG key

Authentication: Required (reseller role)

Request:

{
  "provider_id": 1,
  "customer_name": "Customer Name",
  "customer_email": "customer@example.com",
  "expires_at": "2026-10-30",
  "notes": "VIP customer"
}

Response (201):

{
  "success": true,
  "data": {
    "key_id": "uuid",
    "epg_url": "https://epgo.at/uuid/tps.xml",
    "customer_name": "Customer Name",
    "customer_email": "customer@example.com",
    "status": "active",
    "created_at": "2025-10-30T12:00:00Z",
    "expires_at": "2026-10-30T23:59:59Z"
  }
}

GET /v1/reseller/keys/:id

Description: Get key details

Authentication: Required (reseller role)

Response (200):

{
  "success": true,
  "data": {
    "key_id": "uuid",
    "epg_url": "https://epgo.at/uuid/tps.xml",
    "customer_name": "Customer Name",
    "customer_email": "customer@example.com",
    "provider": {
      "id": 1,
      "name": "TPS"
    },
    "status": "active",
    "created_at": "2025-10-30T12:00:00Z",
    "expires_at": "2026-10-30T23:59:59Z",
    "last_accessed_at": "2025-10-30T11:45:00Z",
    "access_count": 456,
    "unique_ips": 3,
    "abuse_flagged": false,
    "notes": "VIP customer"
  }
}

PUT /v1/reseller/keys/:id

Description: Update key (deactivate, add notes, etc.)

Authentication: Required (reseller role)

Request:

{
  "status": "inactive",
  "notes": "Customer canceled"
}

Response (200):

{
  "success": true,
  "data": {
    "key_id": "uuid",
    "status": "inactive",
    "notes": "Customer canceled",
    "updated_at": "2025-10-30T12:00:00Z"
  }
}

GET /v1/reseller/keys/:id/logs

Description: Get access logs for key

Authentication: Required (reseller role)

Query Parameters: - limit (number, default: 100, max: 500) - offset (number, default: 0)

Response (200):

{
  "success": true,
  "data": {
    "logs": [
      {
        "accessed_at": "2025-10-30T11:45:00Z",
        "ip_address": "192.168.1.1",
        "user_agent": "VLC/3.0.16",
        "provider": "TPS",
        "file_requested": "tps.xml",
        "status": 200,
        "bytes_transferred": 123456
      }
    ],
    "total_count": 456
  }
}

POST /v1/reseller/keys/export

Description: Export all keys to CSV

Authentication: Required (reseller role)

Request:

{
  "provider_id": 1,
  "status": "active"
}

Response (200):

key_id,customer_name,customer_email,provider,status,created_at,expires_at,access_count
uuid-1,Customer 1,customer1@example.com,TPS,active,2025-10-30,2026-10-30,456
uuid-2,Customer 2,customer2@example.com,TPS,active,2025-10-29,2026-10-29,789

Analytics

GET /v1/reseller/analytics/usage

Description: Get usage analytics

Authentication: Required (reseller role)

Query Parameters: - provider_id (number, optional) - start_date (string, YYYY-MM-DD) - end_date (string, YYYY-MM-DD)

Response (200):

{
  "success": true,
  "data": {
    "total_keys": 50,
    "active_keys": 45,
    "total_accesses": 45678,
    "usage_by_day": [
      {
        "date": "2025-10-30",
        "accesses": 1234
      }
    ],
    "top_keys": [
      {
        "key_id": "uuid",
        "customer_name": "Customer Name",
        "accesses": 456
      }
    ]
  }
}

Admin API

Base Path: /v1/admin

Provider Management

GET /v1/admin/providers

Description: List all providers

Authentication: Required (admin role)

Response (200):

{
  "success": true,
  "data": {
    "providers": [
      {
        "id": 1,
        "name": "TPS",
        "description": "Premium sports provider",
        "type": "sports",
        "status": "active",
        "logo_url": "https://files.epgoat.tv/logos/tps.png",
        "channel_count": 1100,
        "match_rate": 96.5,
        "last_updated_at": "2025-10-30T06:00:00Z",
        "created_at": "2025-10-01T12:00:00Z"
      }
    ]
  }
}

POST /v1/admin/providers

Description: Create new provider

Authentication: Required (admin role)

Request:

{
  "name": "New Provider",
  "description": "Description",
  "type": "sports",
  "status": "active",
  "m3u_url": "https://provider.com/playlist.m3u",
  "xmltv_filename": "newprovider.xml",
  "config": {
    "timezone": "America/New_York",
    "update_frequency": "4x",
    "family_mappings_file": "universal.yml"
  }
}

Response (201):

{
  "success": true,
  "data": {
    "id": 11,
    "name": "New Provider",
    "status": "active",
    "created_at": "2025-10-30T12:00:00Z"
  }
}

User Management

GET /v1/admin/users

Description: List all users

Authentication: Required (admin role)

Query Parameters: - role (string, optional: "consumer", "reseller", "admin") - status (string, optional: "trial", "active", "canceled", "expired") - search (string, optional) - limit (number, default: 50, max: 200) - offset (number, default: 0)

Response (200):

{
  "success": true,
  "data": {
    "users": [
      {
        "user_id": "uuid",
        "email": "user@example.com",
        "name": "User Name",
        "role": "consumer",
        "subscription_status": "active",
        "keys_count": 1,
        "created_at": "2025-10-01T12:00:00Z"
      }
    ],
    "total_count": 620
  }
}

Key Management (Admin)

GET /v1/admin/keys

Description: List all keys (across all users)

Authentication: Required (admin role)

Query Parameters: - user_id (string, optional) - provider_id (number, optional) - status (string, optional) - abuse_flagged (boolean, optional) - limit (number, default: 100, max: 500) - offset (number, default: 0)

Response (200):

{
  "success": true,
  "data": {
    "keys": [
      {
        "key_id": "uuid",
        "user_email": "user@example.com",
        "user_role": "consumer",
        "provider": "TPS",
        "status": "active",
        "access_count": 1234,
        "abuse_flagged": false,
        "created_at": "2025-10-01T12:00:00Z"
      }
    ],
    "total_count": 620
  }
}

PUT /v1/admin/keys/:id/suspend

Description: Suspend key for abuse

Authentication: Required (admin role)

Request:

{
  "reason": "Detected sharing across >3 IPs"
}

Response (200):

{
  "success": true,
  "data": {
    "key_id": "uuid",
    "status": "suspended",
    "reason": "Detected sharing across >3 IPs",
    "suspended_at": "2025-10-30T12:00:00Z"
  }
}

System Metrics

GET /v1/admin/metrics

Description: Get system metrics

Authentication: Required (admin role)

Response (200):

{
  "success": true,
  "data": {
    "total_users": 620,
    "active_subscriptions": 465,
    "total_keys": 670,
    "match_rate": 96.5,
    "llm_cost_mtd": 8.45,
    "user_growth": [
      { "date": "2025-10-30", "count": 620 }
    ],
    "revenue": [
      { "date": "2025-10", "mrr": 582 }
    ]
  }
}

EPG Access API

Base Path: https://epgo.at

GET /:key/:provider.xml

Description: Serve EPG XMLTV file for valid key

Authentication: None (key-based access)

Rate Limit: 1 request/minute per key

Parameters: - key (path): UUID access key - provider (path): Provider name (e.g., "tps", "sportztv")

Example:

GET https://epgo.at/550e8400-e29b-41d4-a716-446655440000/tps.xml

Response (200):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tv SYSTEM "xmltv.dtd">
<tv generator-info-name="EPGOAT" generator-info-url="https://epgoat.tv">
  <channel id="nba1">
    <display-name>NBA 01</display-name>
    <icon src="https://files.epgoat.tv/logos/nba.png" />
  </channel>

  <programme start="20251030190000 -0400" stop="20251030220000 -0400" channel="nba1">
    <title lang="en">Los Angeles Lakers vs Boston Celtics</title>
    <sub-title lang="en">NBA Regular Season</sub-title>
    <desc lang="en">Lakers take on Celtics in Eastern Conference showdown</desc>
    <category lang="en">Sports</category>
    <category lang="en">Basketball</category>
    <icon src="https://files.epgoat.tv/events/12345.jpg" />
  </programme>
</tv>

Response Headers:

Content-Type: application/xml
Cache-Control: max-age=3600
X-EPG-Generated: 2025-10-30T06:00:00Z
X-EPG-Version: 1.0
X-Key-Status: active
X-RateLimit-Limit: 1
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1698710520

Error Responses:

404 - Key Not Found:

<?xml version="1.0" encoding="UTF-8"?>
<error>
  <code>KEY_NOT_FOUND</code>
  <message>EPG access key not found</message>
</error>

403 - Key Expired:

<?xml version="1.0" encoding="UTF-8"?>
<error>
  <code>KEY_EXPIRED</code>
  <message>Your EPG access has expired. Please renew your subscription at https://app.epgoat.tv</message>
  <expired_at>2025-10-23T23:59:59Z</expired_at>
</error>

403 - Key Suspended:

<?xml version="1.0" encoding="UTF-8"?>
<error>
  <code>KEY_SUSPENDED</code>
  <message>This EPG key has been suspended due to abuse detection</message>
  <contact>support@epgoat.tv</contact>
</error>

429 - Rate Limit Exceeded:

<?xml version="1.0" encoding="UTF-8"?>
<error>
  <code>RATE_LIMIT_EXCEEDED</code>
  <message>Rate limit exceeded. Please try again in 60 seconds.</message>
  <retry_after>60</retry_after>
</error>

Webhook Endpoints

POST /webhooks/stripe

Description: Handle Stripe webhook events

Authentication: Stripe signature verification

Headers:

Stripe-Signature: t=1698710400,v1=abc123...

Request Body: (varies by event type)

Event Types Handled: - invoice.paid - invoice.payment_failed - customer.subscription.created - customer.subscription.updated - customer.subscription.deleted - checkout.session.completed - charge.refunded

Response (200):

{
  "received": true
}

OpenAPI Specification

File: openapi.yaml

openapi: 3.0.3
info:
  title: EPGOAT API
  description: REST API for EPGOAT SaaS Platform
  version: 1.0.0
  contact:
    name: EPGOAT Support
    email: support@epgoat.tv
    url: https://epgoat.tv

servers:
  - url: https://api.epgoat.tv/v1
    description: Production
  - url: https://api-staging.epgoat.tv/v1
    description: Staging

security:
  - BearerAuth: []

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Auth0 JWT token

  schemas:
    Error:
      type: object
      required:
        - success
        - error
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              example: SUBSCRIPTION_NOT_FOUND
            message:
              type: string
              example: Subscription not found
            details:
              type: object
            timestamp:
              type: string
              format: date-time
            request_id:
              type: string

    User:
      type: object
      properties:
        user_id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        name:
          type: string
        role:
          type: string
          enum: [consumer, reseller, admin]
        subscription_status:
          type: string
          enum: [trial, trial_with_cc, active, past_due, canceled, expired]
        created_at:
          type: string
          format: date-time

    Subscription:
      type: object
      properties:
        subscription_id:
          type: string
          format: uuid
        plan_type:
          type: string
          enum: [consumer_trial, consumer_monthly, consumer_annual, reseller_annual]
        status:
          type: string
          enum: [trial, trial_with_cc, active, past_due, cancel_at_end, canceled, expired]
        current_period_start:
          type: string
          format: date
        current_period_end:
          type: string
          format: date
        will_cancel_at:
          type: string
          format: date
          nullable: true
        stripe_subscription_id:
          type: string
          nullable: true

    AccessKey:
      type: object
      properties:
        key_id:
          type: string
          format: uuid
        epg_url:
          type: string
          format: uri
        status:
          type: string
          enum: [active, inactive, expired, suspended]
        provider:
          $ref: '#/components/schemas/Provider'
        created_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time

    Provider:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        logo_url:
          type: string
          format: uri

paths:
  /auth/me:
    get:
      summary: Get current user profile
      tags: [Authentication]
      security:
        - BearerAuth: []
      responses:
        '200':
          description: User profile
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/User'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /subscriptions/current:
    get:
      summary: Get current subscription
      tags: [Subscriptions]
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Current subscription
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/Subscription'
        '404':
          description: No subscription found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /subscriptions/create:
    post:
      summary: Create subscription (Stripe Checkout)
      tags: [Subscriptions]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - plan
                - success_url
                - cancel_url
              properties:
                plan:
                  type: string
                  enum: [consumer_monthly, consumer_annual, reseller_annual]
                success_url:
                  type: string
                  format: uri
                cancel_url:
                  type: string
                  format: uri
      responses:
        '201':
          description: Checkout session created
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      checkout_url:
                        type: string
                        format: uri

  /keys/my:
    get:
      summary: Get my EPG key
      tags: [Keys]
      security:
        - BearerAuth: []
      responses:
        '200':
          description: EPG access key
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/AccessKey'

  /reseller/keys:
    get:
      summary: List all generated keys
      tags: [Reseller]
      security:
        - BearerAuth: []
      parameters:
        - name: provider_id
          in: query
          schema:
            type: integer
        - name: status
          in: query
          schema:
            type: string
            enum: [active, inactive, expired]
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: List of keys
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      keys:
                        type: array
                        items:
                          $ref: '#/components/schemas/AccessKey'
                      total_count:
                        type: integer
                      keys_remaining:
                        type: integer

    post:
      summary: Generate new EPG key
      tags: [Reseller]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - provider_id
              properties:
                provider_id:
                  type: integer
                customer_name:
                  type: string
                customer_email:
                  type: string
                  format: email
                expires_at:
                  type: string
                  format: date
                notes:
                  type: string
      responses:
        '201':
          description: Key created
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/AccessKey'

  /admin/users:
    get:
      summary: List all users
      tags: [Admin]
      security:
        - BearerAuth: []
      parameters:
        - name: role
          in: query
          schema:
            type: string
            enum: [consumer, reseller, admin]
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
      responses:
        '200':
          description: List of users
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      users:
                        type: array
                        items:
                          $ref: '#/components/schemas/User'
                      total_count:
                        type: integer

Success Criteria

  • [x] OpenAPI 3.0 specification complete
  • [x] All consumer endpoints documented
  • [x] All reseller endpoints documented
  • [x] All admin endpoints documented
  • [x] EPG access endpoint documented
  • [x] Webhook endpoints documented
  • [x] Authentication flow documented
  • [x] Rate limiting specified
  • [x] Error codes standardized
  • [x] Request/response examples provided

END OF SPECIFICATION