{
  "openapi": "3.1.0",
  "info": {
    "title": "tv-cleaner",
    "version": "1.0.1",
    "description": "The endpoint catalogue for smart televisions: the blocklists it renders in six dialects, the per-platform profiles, and the anonymised view of what sets actually resolve."
  },
  "servers": [
    {
      "url": "/tv",
      "description": "zpe-tv on zpe.vc"
    }
  ],
  "paths": {
    "/_/api/v1/blocks": {
      "get": {
        "operationId": "getBlocks",
        "summary": "A blocklist for one brand, in the dialect a blocker reads.",
        "tags": [
          "blocks"
        ],
        "parameters": [
          {
            "name": "brand",
            "in": "query",
            "description": "One of samsung, lg, vizio, roku, amazon, hisense, sony, philips, xiaomi, panasonic. Default samsung.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dialect",
            "in": "query",
            "description": "One of pihole, adguard, dnsmasq, hosts, nextdns, unbound. Default pihole.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tier",
            "in": "query",
            "description": "acr (the fingerprinting endpoints only) or default (acr, ads and telemetry).",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "shared",
            "in": "query",
            "description": "0 drops the cross-platform measurement trackers. Default includes them. Anything but 0 or 1 is refused.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The list as text, ready to subscribe to.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "brand, dialect, tier or shared is a value this route does not understand.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "This app failed while answering",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/_/api/v1/catalog": {
      "get": {
        "operationId": "getCatalog",
        "summary": "Every rule with its class, confidence, sources and evidence, plus the artifact sha.",
        "tags": [
          "catalog"
        ],
        "responses": {
          "200": {
            "description": "The catalogue.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "500": {
            "description": "This app failed while answering",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/_/api/v1/brands": {
      "get": {
        "operationId": "getBrands",
        "summary": "The platform profiles, including whether the on-device opt-out was found to work.",
        "tags": [
          "brands"
        ],
        "responses": {
          "200": {
            "description": "One profile per brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "500": {
            "description": "This app failed while answering",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/_/api/v1/dataset": {
      "get": {
        "operationId": "getDataset",
        "summary": "The anonymised aggregates, floored so no single submission can publish a host.",
        "tags": [
          "dataset"
        ],
        "parameters": [
          {
            "name": "weeks",
            "in": "query",
            "description": "How far back to aggregate, an integer 1 to 52. Default 8. Anything else is refused.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Aggregates, the window, and how many hosts were withheld by the floor.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "weeks is not an integer from 1 to 52.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "This app failed while answering",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "This deployment has no dataset database, so there is nothing to aggregate.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postDataset",
        "summary": "Submit one anonymous observation: a rotating pseudonym, the platform, and hostnames with bucketed counts.",
        "tags": [
          "dataset"
        ],
        "requestBody": {
          "required": true,
          "description": "Never carries an address, an account, a model string or a free-text field.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "v",
                  "brand",
                  "os",
                  "week",
                  "pseudonym",
                  "hosts"
                ],
                "properties": {
                  "v": {
                    "type": "integer",
                    "enum": [
                      1
                    ]
                  },
                  "brand": {
                    "type": "string",
                    "enum": [
                      "samsung",
                      "lg",
                      "vizio",
                      "roku",
                      "amazon",
                      "hisense",
                      "sony",
                      "philips",
                      "xiaomi",
                      "panasonic"
                    ]
                  },
                  "os": {
                    "type": "string",
                    "enum": [
                      "tizen",
                      "webos",
                      "smartcast",
                      "roku-os",
                      "fire-os",
                      "vidaa",
                      "google-tv",
                      "titan-os",
                      "mi-tv",
                      "viera",
                      "unknown"
                    ]
                  },
                  "week": {
                    "type": "string",
                    "description": "ISO week, this week or last, e.g. 2026-W44."
                  },
                  "pseudonym": {
                    "type": "string",
                    "description": "32 hex characters, derived per week and thrown away."
                  },
                  "hosts": {
                    "type": "array",
                    "maxItems": 200,
                    "items": {
                      "type": "object",
                      "required": [
                        "host",
                        "n"
                      ],
                      "properties": {
                        "host": {
                          "type": "string"
                        },
                        "n": {
                          "type": "string",
                          "enum": [
                            "1",
                            "2-5",
                            "6-20",
                            "21-99",
                            "100+"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Stored. Whether it reaches the published dataset depends on other machines.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "The request could not be read",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing or does not open this account",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "This account may not do that",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Nothing is here",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Body over 64KB.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "More than 20 submissions an hour from one address.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key minted on the account's settings page at zpe.vc/home/settings. Send it as `Authorization: Bearer <key>`."
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem+json: `type`, `title`, `status`, `code` and `instance`, plus `detail` when a person should read a sentence.",
        "required": [
          "type",
          "title",
          "status",
          "code",
          "instance"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "code": {
            "type": "string",
            "enum": [
              "bad-request",
              "unauthenticated",
              "malformed",
              "unknown",
              "mismatch",
              "revoked",
              "expired",
              "another-account",
              "scope-refused",
              "grant-refused",
              "forbidden",
              "not-found",
              "method-not-allowed",
              "conflict",
              "gone",
              "payload-too-large",
              "unsupported-media-type",
              "unprocessable",
              "rate-limited",
              "payment-required",
              "not-open",
              "internal",
              "unavailable"
            ]
          },
          "instance": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          }
        }
      }
    }
  },
  "x-surface": {
    "app": "zpe-tv",
    "base": "/_/api/v1",
    "document": "/_/api/v1/openapi.json",
    "generated_by": "zpe-kit machineDocument"
  },
  "x-zpe-routes": 5,
  "x-zpe-documented": 5,
  "x-zpe-internal": []
}
