{
  "openapi": "3.1.0",
  "info": {
    "title": "htmlpub API",
    "description": "Publish HTML pages to the web instantly. The htmlpub API lets you create, update, and manage web pages, assets, sites, and custom domains programmatically. API access requires a Pro plan ($15/mo).",
    "version": "1.0.0",
    "contact": {
      "name": "htmlpub Support",
      "email": "support@htmlpub.com",
      "url": "https://htmlpub.com"
    },
    "termsOfService": "https://htmlpub.com/terms",
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://htmlpub.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/api/pages/{slug}": {
      "put": {
        "operationId": "createOrUpdatePage",
        "summary": "Create or update a page",
        "description": "Create a new page with a custom slug, or update an existing page you own. Returns 201 for new pages, 200 for updates. If the slug is taken by another user, returns 409 Conflict. Requires API key authentication (Pro plan).",
        "tags": ["Pages"],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "URL slug for the page (3-64 chars, lowercase alphanumeric and hyphens, must start/end with alphanumeric)",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
              "minLength": 3,
              "maxLength": 64
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["html"],
                "properties": {
                  "html": {
                    "type": "string",
                    "description": "Complete HTML content for the page. Max 5MB.",
                    "maxLength": 5242880
                  },
                  "siteId": {
                    "type": ["string", "null"],
                    "description": "Optional site ID to assign this page to. Must be a site you own."
                  }
                }
              },
              "example": {
                "html": "<!DOCTYPE html><html><head><title>My Page</title></head><body><h1>Hello World</h1></body></html>"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Page created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PageResponse" }
              }
            }
          },
          "200": {
            "description": "Page updated",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PageResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "409": { "$ref": "#/components/responses/Conflict" }
        }
      },
      "patch": {
        "operationId": "updatePage",
        "summary": "Update a page",
        "description": "Update an existing page's HTML content, title, or site assignment. You can update just the title, just the siteId, or the full HTML content.",
        "tags": ["Pages"],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "html": {
                    "type": "string",
                    "description": "Updated HTML content. Max 5MB."
                  },
                  "title": {
                    "type": "string",
                    "description": "Updated page title"
                  },
                  "siteId": {
                    "type": ["string", "null"],
                    "description": "Site ID to assign/unassign. Set to null to remove from site."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Page updated",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PageResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "operationId": "deletePage",
        "summary": "Delete a page",
        "description": "Permanently delete a page you own.",
        "tags": ["Pages"],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Page deleted",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SuccessResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/pages": {
      "get": {
        "operationId": "listPages",
        "summary": "List all pages",
        "description": "List all pages for your account. Returns pages sorted by creation date.",
        "tags": ["Pages"],
        "responses": {
          "200": {
            "description": "List of pages",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "pages": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/PageListItem" }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/pages/{slug}/raw": {
      "get": {
        "operationId": "getPageRawHtml",
        "summary": "Get raw HTML content",
        "description": "Returns the raw HTML content of a published page. No authentication required for public pages.",
        "tags": ["Pages"],
        "security": [],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Raw HTML content",
            "content": {
              "text/html": {
                "schema": { "type": "string" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/pages/{slug}/assets": {
      "get": {
        "operationId": "listAssets",
        "summary": "List page assets",
        "description": "List all assets (images, CSS, JS, fonts) uploaded to a page.",
        "tags": ["Assets"],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "List of assets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "assets": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/AssetMetadata" }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "post": {
        "operationId": "uploadAsset",
        "summary": "Upload an asset",
        "description": "Upload a file (image, CSS, JS, or font) to a page. Accepted types: png, jpg, gif, svg, webp, css, js, woff, woff2, ttf. Size limits depend on your plan.",
        "tags": ["Assets"],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file"],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The file to upload"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Asset uploaded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "asset": { "$ref": "#/components/schemas/AssetMetadata" },
                    "url": {
                      "type": "string",
                      "description": "Public URL to access the asset"
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "operationId": "deleteAllAssets",
        "summary": "Delete all page assets",
        "description": "Delete all assets uploaded to a page.",
        "tags": ["Assets"],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "All assets deleted",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SuccessResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/pages/{slug}/assets/{assetId}": {
      "get": {
        "operationId": "getAsset",
        "summary": "Serve an asset",
        "description": "Serve an uploaded asset file. No authentication required for public pages. Returns the file with appropriate Content-Type and immutable cache headers.",
        "tags": ["Assets"],
        "security": [],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "assetId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Asset file content",
            "content": {
              "*/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "operationId": "deleteAsset",
        "summary": "Delete an asset",
        "description": "Delete a specific asset from a page.",
        "tags": ["Assets"],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "assetId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Asset deleted",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SuccessResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/sites": {
      "get": {
        "operationId": "listSites",
        "summary": "List all sites",
        "description": "List all sites for your account, including their pages.",
        "tags": ["Sites"],
        "responses": {
          "200": {
            "description": "List of sites",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sites": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Site" }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "operationId": "createSite",
        "summary": "Create a site",
        "description": "Create a new site to group related pages together. Sites get their own URL namespace.",
        "tags": ["Sites"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["slug", "name"],
                "properties": {
                  "slug": {
                    "type": "string",
                    "description": "URL slug for the site (3-64 chars, lowercase alphanumeric and hyphens)",
                    "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
                    "minLength": 3,
                    "maxLength": 64
                  },
                  "name": {
                    "type": "string",
                    "description": "Display name for the site"
                  }
                }
              },
              "example": {
                "slug": "my-blog",
                "name": "My Blog"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Site created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Site" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": { "$ref": "#/components/responses/Conflict" }
        }
      }
    },
    "/api/sites/{siteId}": {
      "get": {
        "operationId": "getSite",
        "summary": "Get site details",
        "description": "Get a site's details including all its pages.",
        "tags": ["Sites"],
        "parameters": [
          {
            "name": "siteId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Site details",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Site" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "operationId": "updateSite",
        "summary": "Update a site",
        "description": "Update a site's name, slug, or default page.",
        "tags": ["Sites"],
        "parameters": [
          {
            "name": "siteId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Updated display name"
                  },
                  "slug": {
                    "type": "string",
                    "description": "Updated URL slug"
                  },
                  "defaultPageId": {
                    "type": ["string", "null"],
                    "description": "Page ID to show when visiting the site root. Must be a page in this site."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Site updated",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Site" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" }
        }
      },
      "delete": {
        "operationId": "deleteSite",
        "summary": "Delete a site",
        "description": "Delete a site and all its pages (cascading delete).",
        "tags": ["Sites"],
        "parameters": [
          {
            "name": "siteId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Site deleted",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SuccessResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/custom-domains": {
      "get": {
        "operationId": "listCustomDomains",
        "summary": "List custom domains",
        "description": "List all custom domains configured for your account. Pro plan required.",
        "tags": ["Custom Domains"],
        "responses": {
          "200": {
            "description": "List of domains",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "domains": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/CustomDomain" }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "operationId": "addCustomDomain",
        "summary": "Add a custom domain",
        "description": "Add a custom domain and point it to one of your pages. Requires Pro plan. After adding, you must configure a CNAME record with your DNS provider and then verify the domain.",
        "tags": ["Custom Domains"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["domain", "pageId"],
                "properties": {
                  "domain": {
                    "type": "string",
                    "description": "The custom domain (e.g., www.example.com)"
                  },
                  "pageId": {
                    "type": "string",
                    "description": "ID of the page to serve on this domain"
                  }
                }
              },
              "example": {
                "domain": "www.example.com",
                "pageId": "cm1a2b3c4d5e6f7g8h9i0"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Domain added with DNS instructions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "domain": { "$ref": "#/components/schemas/CustomDomain" },
                    "instructions": {
                      "type": "object",
                      "properties": {
                        "target": {
                          "type": "string",
                          "description": "CNAME target to configure in your DNS"
                        },
                        "message": {
                          "type": "string",
                          "description": "Human-readable setup instructions"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" }
        }
      }
    },
    "/api/custom-domains/{domain}": {
      "patch": {
        "operationId": "updateCustomDomain",
        "summary": "Update a custom domain",
        "description": "Change which page a custom domain points to.",
        "tags": ["Custom Domains"],
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["pageId"],
                "properties": {
                  "pageId": {
                    "type": "string",
                    "description": "ID of the page to serve on this domain"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Domain updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "domain": { "$ref": "#/components/schemas/CustomDomain" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "operationId": "removeCustomDomain",
        "summary": "Remove a custom domain",
        "description": "Remove a custom domain from your account.",
        "tags": ["Custom Domains"],
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Domain removed",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SuccessResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/custom-domains/{domain}/verify": {
      "post": {
        "operationId": "verifyCustomDomain",
        "summary": "Verify a custom domain",
        "description": "Check if the DNS records for a custom domain are properly configured. Call this after setting up your CNAME record.",
        "tags": ["Custom Domains"],
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Verification result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "domain": { "$ref": "#/components/schemas/CustomDomain" },
                    "verified": {
                      "type": "boolean",
                      "description": "Whether the domain is verified and active"
                    },
                    "cfStatus": {
                      "type": "string",
                      "description": "Cloudflare verification status"
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key authentication. Get your key from https://htmlpub.com/dashboard/api-keys (Pro plan required). Keys start with 'hp_live_'."
      }
    },
    "schemas": {
      "PageResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique page ID"
          },
          "slug": {
            "type": "string",
            "description": "URL slug"
          },
          "url": {
            "type": "string",
            "description": "Full public URL for the page"
          },
          "siteId": {
            "type": ["string", "null"],
            "description": "Site ID if assigned to a site"
          }
        }
      },
      "PageListItem": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "slug": { "type": "string" },
          "title": { "type": ["string", "null"] },
          "url": { "type": "string" },
          "siteId": { "type": ["string", "null"] },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AssetMetadata": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "filename": { "type": "string" },
          "contentType": { "type": "string" },
          "size": {
            "type": "integer",
            "description": "File size in bytes"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Site": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "slug": { "type": "string" },
          "name": { "type": "string" },
          "url": { "type": "string" },
          "defaultPageId": { "type": ["string", "null"] },
          "pageCount": { "type": "integer" },
          "pages": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/PageListItem" }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CustomDomain": {
        "type": "object",
        "properties": {
          "domain": { "type": "string" },
          "status": {
            "type": "string",
            "enum": ["PENDING", "VERIFIED", "FAILED"]
          },
          "pageId": { "type": "string" },
          "verifiedAt": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SuccessResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Error message"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request body, missing required fields, or validation error",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "Forbidden": {
        "description": "Insufficient permissions (plan limit reached, Pro plan required, or not your resource)",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found or expired",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "Conflict": {
        "description": "Resource already exists (slug taken, domain in use)",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Pages",
      "description": "Create, update, list, and delete HTML pages"
    },
    {
      "name": "Assets",
      "description": "Upload and manage page assets (images, CSS, JS, fonts)"
    },
    {
      "name": "Sites",
      "description": "Group pages into sites with their own URL namespace"
    },
    {
      "name": "Custom Domains",
      "description": "Connect your own domain to a page (Pro plan required)"
    }
  ]
}
