{
  "openapi": "3.0.3",
  "info": {
    "title": "Easy PDF Server",
    "version": "1.1.0",
    "description": "Render HTML or a public URL to a PDF. Send your API key as an Authorization bearer token, or as the key field in the JSON body.",
    "contact": {
      "name": "Easy PDF Server",
      "email": "easypdfserver@a8b.co",
      "url": "https://www.easypdfserver.com/docs"
    }
  },
  "servers": [
    {
      "url": "https://api.easypdfserver.com",
      "description": "Hosted API"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {}
  ],
  "paths": {
    "/make-pdf": {
      "post": {
        "summary": "Render HTML or a URL to a PDF",
        "description": "Send exactly one of html or url. The response body is the PDF file. Defaults are US Letter, 0.39in margins, backgrounds printed, no headers or footers. CSS @page rules are respected.",
        "operationId": "makePdf",
        "security": [
          {
            "bearerAuth": []
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MakePdfRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rendered PDF",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "429": {
            "$ref": "#/components/responses/Quota"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "502": {
            "$ref": "#/components/responses/RenderError"
          },
          "503": {
            "$ref": "#/components/responses/Busy"
          }
        }
      }
    },
    "/portal": {
      "post": {
        "summary": "Create a Stripe billing portal session",
        "description": "Returns a URL for invoices, payment methods, and cancellation. Requires a key that belongs to a billing account.",
        "operationId": "portal",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PortalRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A portal URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "502": {
            "$ref": "#/components/responses/BillingError"
          },
          "503": {
            "$ref": "#/components/responses/BillingError"
          }
        }
      }
    },
    "/healthcheck": {
      "get": {
        "summary": "Check that the API is up",
        "operationId": "healthcheck",
        "security": [],
        "responses": {
          "200": {
            "description": "Service status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your API key, for example eps_abc123."
      }
    },
    "schemas": {
      "MakePdfRequest": {
        "type": "object",
        "description": "Send key in the body or the Authorization header, plus exactly one of html or url.",
        "properties": {
          "key": {
            "type": "string",
            "description": "API key. Optional when the Authorization header is sent."
          },
          "html": {
            "type": "string",
            "description": "HTML document or fragment to render. Maximum request body is 10 MB."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Public http or https URL to render. Private, loopback, and link-local addresses are rejected."
          }
        },
        "oneOf": [
          {
            "required": ["html"]
          },
          {
            "required": ["url"]
          }
        ],
        "additionalProperties": false,
        "example": {
          "html": "<h1>Hello world</h1>"
        }
      },
      "PortalRequest": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "API key. Optional when the Authorization header is sent."
          }
        },
        "additionalProperties": false
      },
      "PortalResponse": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Health": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "time": {
            "type": "string",
            "format": "date-time"
          },
          "keys": {
            "type": "integer",
            "description": "Number of keys in the store"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human readable message"
          },
          "code": {
            "type": "string",
            "description": "Stable machine readable code"
          }
        },
        "required": ["error", "code"]
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid JSON, invalid input, or a rejected URL",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "invalid_input": {
                "value": {
                  "error": "Send either html or url, not both and not neither.",
                  "code": "invalid_input"
                }
              },
              "invalid_url": {
                "value": {
                  "error": "Only http and https URLs can be rendered.",
                  "code": "invalid_url"
                }
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or incorrect API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Incorrect API key.",
              "code": "invalid_api_key"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key is not active",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "This API key is not active. If you cancelled, subscribe again to continue.",
              "code": "inactive_api_key"
            }
          }
        }
      },
      "TooLarge": {
        "description": "Request body over 10 MB",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Quota": {
        "description": "Monthly quota used",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServerError": {
        "description": "Unexpected server error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RenderError": {
        "description": "The page could not be rendered",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Busy": {
        "description": "The renderer is saturated",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BillingError": {
        "description": "Billing is not configured or Stripe rejected the request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
