{
 "openapi": "3.1.0",
 "info": {
  "title": "Open adult video API",
  "version": "1.0",
  "description": "Free and open: no signup, no key. Send `X-Site-URL: https://your-site.com` so embeds are signed for your domain and your usage shows per site. An optional `X-API-Key` adds per-key stats. Setup guides: https://pornalizer.app/integrations/"
 },
 "servers": [
  {
   "url": "https://pornalizer.app"
  }
 ],
 "components": {
  "securitySchemes": {
   "optionalKey": {
    "type": "apiKey",
    "in": "header",
    "name": "X-API-Key"
   }
  },
  "parameters": {
   "site": {
    "name": "X-Site-URL",
    "in": "header",
    "required": false,
    "schema": {
     "type": "string",
     "format": "uri"
    }
   }
  },
  "schemas": {
   "Video": {
    "type": "object",
    "properties": {
     "id": {
      "type": "integer"
     },
     "title": {
      "type": "string"
     },
     "slug": {
      "type": "string"
     },
     "description": {
      "type": "string"
     },
     "thumbnail": {
      "type": "string",
      "format": "uri"
     },
     "duration_seconds": {
      "type": "number"
     },
     "resolution": {
      "type": "string"
     },
     "view_count": {
      "type": "integer"
     },
     "like_count": {
      "type": "integer"
     },
     "dislike_count": {
      "type": "integer"
     },
     "rating": {
      "type": "integer"
     },
     "content_type": {
      "type": "string"
     },
     "categories": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "id": {
         "type": "integer"
        },
        "name": {
         "type": "string"
        },
        "slug": {
         "type": "string"
        },
        "video_count": {
         "type": "integer"
        }
       }
      }
     },
     "tags": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "id": {
         "type": "integer"
        },
        "name": {
         "type": "string"
        },
        "slug": {
         "type": "string"
        },
        "video_count": {
         "type": "integer"
        }
       }
      }
     },
     "performers": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "id": {
         "type": "integer"
        },
        "name": {
         "type": "string"
        },
        "slug": {
         "type": "string"
        },
        "video_count": {
         "type": "integer"
        }
       }
      }
     },
     "studio": {
      "type": [
       "object",
       "null"
      ],
      "properties": {
       "id": {
        "type": "integer"
       },
       "name": {
        "type": "string"
       },
       "slug": {
        "type": "string"
       },
       "network_name": {
        "type": "string"
       }
      }
     },
     "tpdb_match_status": {
      "type": "string"
     },
     "tpdb_rating": {
      "type": "number"
     },
     "primary_source_type": {
      "type": "string"
     },
     "published_at": {
      "type": "string",
      "format": "date-time"
     }
    }
   },
   "Term": {
    "type": "object",
    "properties": {
     "id": {
      "type": "integer"
     },
     "name": {
      "type": "string"
     },
     "slug": {
      "type": "string"
     },
     "video_count": {
      "type": "integer"
     }
    }
   }
  }
 },
 "paths": {
  "/api/database/browse/": {
   "get": {
    "summary": "Browse / search / filter videos",
    "operationId": "browse",
    "parameters": [
     {
      "name": "orientation",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "straight",
        "trans",
        "gay",
        "bi",
        "all",
        "mixed"
       ]
      },
      "description": "Browse defaults to straight; feeds/changelog default to all."
     },
     {
      "name": "content_type",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "scene",
        "amateur",
        "mixed"
       ]
      }
     },
     {
      "name": "source_type",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "embed",
        "direct",
        "hls"
       ]
      }
     },
     {
      "name": "categories",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Comma-separated category slugs"
     },
     {
      "name": "tags",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Comma-separated tag slugs"
     },
     {
      "name": "performers",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Comma-separated performer slugs"
     },
     {
      "name": "studios",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Comma-separated studio slugs"
     },
     {
      "name": "search",
      "in": "query",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "sort",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "newest",
        "popular",
        "longest",
        "published",
        "views",
        "likes",
        "trending"
       ],
       "default": "newest"
      }
     },
     {
      "name": "page",
      "in": "query",
      "schema": {
       "type": "integer",
       "minimum": 1
      }
     },
     {
      "name": "page_size",
      "in": "query",
      "schema": {
       "type": "integer",
       "maximum": 100
      }
     },
     {
      "$ref": "#/components/parameters/site"
     }
    ],
    "responses": {
     "200": {
      "description": "Page of videos",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "count": {
           "type": "integer"
          },
          "next": {
           "type": [
            "string",
            "null"
           ]
          },
          "previous": {
           "type": [
            "string",
            "null"
           ]
          },
          "results": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Video"
           }
          }
         }
        }
       }
      }
     }
    }
   }
  },
  "/api/videos/{id}/": {
   "get": {
    "summary": "One video",
    "operationId": "video",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Video",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Video"
        }
       }
      }
     },
     "404": {
      "description": "Not available"
     }
    }
   }
  },
  "/api/videos/{id}/get-embed/": {
   "post": {
    "summary": "Signed embeddable player for your site (2 h); permanent unsigned link: /player/embed/{id}/",
    "operationId": "getEmbed",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "integer"
      }
     },
     {
      "$ref": "#/components/parameters/site"
     }
    ],
    "responses": {
     "200": {
      "description": "Player",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "player_url": {
           "type": "string"
          },
          "video_id": {
           "type": "integer"
          },
          "title": {
           "type": "string"
          },
          "embed_code": {
           "type": "string"
          }
         }
        }
       }
      }
     }
    }
   }
  },
  "/api/videos/status/": {
   "get": {
    "summary": "Which of these videos are servable now (for keeping a library in sync)",
    "operationId": "status",
    "parameters": [
     {
      "name": "ids",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Comma-separated ids, max 500"
     }
    ],
    "responses": {
     "200": {
      "description": "Split",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "servable": {
           "type": "array",
           "items": {
            "type": "integer"
           }
          },
          "unavailable": {
           "type": "array",
           "items": {
            "type": "integer"
           }
          }
         }
        }
       }
      }
     }
    }
   }
  },
  "/api/changelog/": {
   "get": {
    "summary": "What changed since a time: created/updated (fetch + upsert) or deleted (remove)",
    "operationId": "changelog",
    "parameters": [
     {
      "name": "since",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string",
       "format": "date-time"
      }
     },
     {
      "name": "orientation",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "straight",
        "trans",
        "gay",
        "bi",
        "all",
        "mixed"
       ]
      },
      "description": "Browse defaults to straight; feeds/changelog default to all."
     },
     {
      "name": "content_type",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "scene",
        "amateur",
        "mixed"
       ]
      }
     },
     {
      "name": "source_type",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "embed",
        "direct",
        "hls"
       ]
      }
     },
     {
      "name": "categories",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Comma-separated category slugs"
     }
    ],
    "responses": {
     "200": {
      "description": "Changes; repeat with next_since while has_more",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "changes": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "video_id": {
              "type": "integer"
             },
             "change_type": {
              "type": "string",
              "enum": [
               "created",
               "updated",
               "deleted"
              ]
             },
             "timestamp": {
              "type": "string"
             }
            }
           }
          },
          "has_more": {
           "type": "boolean"
          },
          "next_since": {
           "type": "string"
          }
         }
        }
       }
      }
     }
    }
   }
  },
  "/api/database/metadata/": {
   "get": {
    "summary": "Categories, tags and performers that have videos, most used first",
    "operationId": "metadata",
    "parameters": [
     {
      "name": "type",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "all",
        "categories",
        "tags",
        "performers"
       ]
      }
     },
     {
      "name": "limit",
      "in": "query",
      "schema": {
       "type": "integer",
       "maximum": 10000
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Lists"
     }
    }
   }
  },
  "/api/taxonomy/{kind}/": {
   "get": {
    "summary": "Live, paginated list of categories / tags / performers / studios with counts and ready-made browse filters",
    "operationId": "taxonomy",
    "parameters": [
     {
      "name": "kind",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "enum": [
        "categories",
        "tags",
        "performers",
        "studios"
       ]
      }
     },
     {
      "name": "search",
      "in": "query",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "starts_with",
      "in": "query",
      "schema": {
       "type": "string",
       "description": "a-z or 0-9"
      }
     },
     {
      "name": "min_videos",
      "in": "query",
      "schema": {
       "type": "integer",
       "default": 1
      }
     },
     {
      "name": "ordering",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "popular",
        "name",
        "newest"
       ],
       "default": "popular"
      }
     },
     {
      "name": "page",
      "in": "query",
      "schema": {
       "type": "integer",
       "default": 1
      }
     },
     {
      "name": "per_page",
      "in": "query",
      "schema": {
       "type": "integer",
       "default": 100,
       "maximum": 500
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Page of entries",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "list": {
           "type": "string"
          },
          "count": {
           "type": "integer"
          },
          "page": {
           "type": "integer"
          },
          "pages": {
           "type": "integer"
          },
          "next": {
           "type": "string",
           "nullable": true
          },
          "previous": {
           "type": "string",
           "nullable": true
          },
          "filter_param": {
           "type": "string"
          },
          "results": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "id": {
              "type": "integer"
             },
             "name": {
              "type": "string"
             },
             "slug": {
              "type": "string"
             },
             "video_count": {
              "type": "integer"
             },
             "filter": {
              "type": "string"
             },
             "videos_url": {
              "type": "string"
             }
            }
           }
          }
         }
        }
       }
      }
     }
    }
   }
  },
  "/api/feeds/videos.{format}": {
   "get": {
    "summary": "Embed feed for tube scripts: CSV (layout=pornhub|generic), Media RSS (xml) or JSON",
    "operationId": "feed",
    "parameters": [
     {
      "name": "format",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "enum": [
        "csv",
        "xml",
        "json"
       ]
      }
     },
     {
      "name": "layout",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "pornhub",
        "generic"
       ]
      }
     },
     {
      "name": "sep",
      "in": "query",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "since",
      "in": "query",
      "schema": {
       "type": "string",
       "format": "date-time"
      }
     },
     {
      "name": "limit",
      "in": "query",
      "schema": {
       "type": "integer",
       "maximum": 10000
      }
     },
     {
      "name": "offset",
      "in": "query",
      "schema": {
       "type": "integer"
      }
     },
     {
      "name": "page",
      "in": "query",
      "schema": {
       "type": "integer"
      }
     },
     {
      "name": "orientation",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "straight",
        "trans",
        "gay",
        "bi",
        "all",
        "mixed"
       ]
      },
      "description": "Browse defaults to straight; feeds/changelog default to all."
     },
     {
      "name": "content_type",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "scene",
        "amateur",
        "mixed"
       ]
      }
     },
     {
      "name": "source_type",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "embed",
        "direct",
        "hls"
       ]
      }
     },
     {
      "name": "categories",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Comma-separated category slugs"
     },
     {
      "name": "tags",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Comma-separated tag slugs"
     },
     {
      "name": "performers",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Comma-separated performer slugs"
     },
     {
      "name": "studios",
      "in": "query",
      "schema": {
       "type": "string"
      },
      "description": "Comma-separated studio slugs"
     },
     {
      "name": "search",
      "in": "query",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Feed"
     }
    }
   }
  },
  "/api/feeds/dump/{orientation}.{layout}.csv.gz": {
   "get": {
    "summary": "Nightly full dump (redirects to the newest file)",
    "operationId": "dump",
    "parameters": [
     {
      "name": "orientation",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "enum": [
        "all",
        "straight",
        "trans",
        "gay",
        "bi"
       ]
      }
     },
     {
      "name": "layout",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "enum": [
        "pornhub",
        "generic"
       ]
      }
     }
    ],
    "responses": {
     "302": {
      "description": "Location of the gzipped CSV"
     }
    }
   }
  },
  "/api/oembed/": {
   "get": {
    "summary": "oEmbed for a player link",
    "operationId": "oembed",
    "parameters": [
     {
      "name": "url",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "maxwidth",
      "in": "query",
      "schema": {
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "oEmbed video response"
     }
    }
   }
  }
 }
}