{
  "openapi": "3.1.0",
  "info": {
    "title": "Grade",
    "version": "b41e01a0",
    "description": "Catálogo IPTV + biblioteca pessoal com feeds."
  },
  "servers": [
    {
      "url": "https://gradetv.pages.dev"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "ipt_… (guest) ou iptk_… (API key)"
      }
    }
  },
  "paths": {
    "/api/": {
      "get": {
        "summary": "Índice auto-descrito.",
        "description": "Devolve: { name, endpoints[] }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ name, endpoints[] }"
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "summary": "Liveness e build.",
        "description": "Devolve: { ok, app, build, ts }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ ok, app, build, ts }"
          }
        }
      }
    },
    "/api/channels": {
      "get": {
        "summary": "Busca paginada do catálogo (q, country, category, language, playable, nsfw).",
        "description": "Devolve: { items[], limit, offset, next_offset, filters }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ items[], limit, offset, next_offset, filters }"
          }
        }
      }
    },
    "/api/channels/{id}": {
      "get": {
        "summary": "Canal + streams + URLs absolutas.",
        "description": "Devolve: { id, name, streams[], api, _links }",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ id, name, streams[], api, _links }"
          }
        }
      }
    },
    "/api/countries": {
      "get": {
        "summary": "Facetas de país.",
        "description": "Devolve: { items[] }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ items[] }"
          }
        }
      }
    },
    "/api/categories": {
      "get": {
        "summary": "Facetas de categoria iptv-org.",
        "description": "Devolve: { items[] }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ items[] }"
          }
        }
      },
      "post": {
        "summary": "Cria tab principal.",
        "description": "Devolve: { id, …library }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "name": "Notícias"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ id, …library }"
          }
        }
      }
    },
    "/api/languages": {
      "get": {
        "summary": "Facetas de idioma.",
        "description": "Devolve: { items[] }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ items[] }"
          }
        }
      }
    },
    "/api/guest": {
      "post": {
        "summary": "Cria guest `ipt_…`.",
        "description": "Devolve: { token }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ token }"
          }
        }
      }
    },
    "/api/keys": {
      "post": {
        "summary": "Mint API key `iptk_…` (texto 1×).",
        "description": "Devolve: { id, token, masked }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ id, token, masked }"
          }
        }
      },
      "get": {
        "summary": "Lista chaves mascaradas do dono.",
        "description": "Devolve: { keys[] }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ keys[] }"
          }
        }
      }
    },
    "/api/keys/{id}": {
      "delete": {
        "summary": "Revoga uma key.",
        "description": "Devolve: { ok }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ ok }"
          }
        }
      }
    },
    "/api/library": {
      "get": {
        "summary": "Árvore categoria → sub-aba → canais + URLs de feed.",
        "description": "Devolve: { owner, feeds, categories[] }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{ owner, feeds, categories[] }"
          }
        }
      }
    },
    "/api/categories/{id}": {
      "patch": {
        "summary": "Renomeia categoria.",
        "description": "Devolve: { …library }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "name": "…"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ …library }"
          }
        }
      },
      "delete": {
        "summary": "Apaga categoria e o que tem dentro.",
        "description": "Devolve: { …library }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ …library }"
          }
        }
      }
    },
    "/api/groups": {
      "post": {
        "summary": "Cria sub-aba.",
        "description": "Devolve: { id, …library }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "category_id": "cat_…",
                "name": "Manchete"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ id, …library }"
          }
        }
      }
    },
    "/api/groups/{id}": {
      "delete": {
        "summary": "Apaga sub-aba.",
        "description": "Devolve: { …library }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ …library }"
          }
        }
      }
    },
    "/api/items": {
      "post": {
        "summary": "Põe um canal na sub-aba.",
        "description": "Devolve: { id, …library }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "group_id": "grp_…",
                "channel_id": "Globo.br"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ id, …library }"
          }
        }
      }
    },
    "/api/items/{id}": {
      "delete": {
        "summary": "Remove canal da sub-aba.",
        "description": "Devolve: { …library }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ …library }"
          }
        }
      }
    },
    "/api/items/{id}/play": {
      "post": {
        "summary": "Marca último canal tocado da sub-aba.",
        "description": "Devolve: { …library }",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ …library }"
          }
        }
      }
    },
    "/api/billing": {
      "get": {
        "summary": "Preço do contato e tetos da galeria.",
        "description": "Devolve: { prices, limits, network }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ prices, limits, network }"
          }
        }
      }
    },
    "/api/contact": {
      "post": {
        "summary": "Contato: Turnstile (humano) ou x402 $0.10 (agente).",
        "description": "Devolve: { ok } | 402",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "example": {
                "name": "…",
                "email": "a@example.com",
                "message": "…",
                "form_ts": 0
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ ok } | 402"
          }
        }
      }
    },
    "/api/visit": {
      "post": {
        "summary": "Cada ping da UI incrementa o contador do dia. Sem janela.",
        "description": "Devolve: { ok, counted }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ ok, counted }"
          }
        }
      }
    },
    "/api/metrics": {
      "get": {
        "summary": "Métricas 7d. Sem token: visitas/uso/contas. Com METRICS_TOKEN: + payments (só Base mainnet).",
        "description": "Devolve: { app, today_visits, days[], usage, accounts [, payments] }",
        "security": [],
        "responses": {
          "200": {
            "description": "{ app, today_visits, days[], usage, accounts [, payments] }"
          }
        }
      }
    }
  }
}