{
  "openapi": "3.1.0",
  "info": {
    "title": "makesPDF API",
    "version": "1.0.0",
    "description": "Deterministic PDF rendering API: GitHub-flavoured Markdown or a compact DSL to tagged, PDF/A-2A + PDF/UA-1 dual-compliant PDFs. Authenticate with a Bearer token (API key or OAuth device flow), or pay per call via x402 (USDC on Base) on the x402-tagged operations. Errors use the shared Error schema. Rate limits are reported in RateLimit / RateLimit-Policy headers, with Retry-After on 429. Versioning: the major version is in the path (/api/v1); breaking changes ship as a new version, and a deprecated endpoint keeps working for at least 90 days after it starts sending Deprecation and Sunset headers.",
    "x-guidance": "POST /api/v1/markdown converts markdown to PDF ($0.01). POST /api/v1/render renders a template from templateId or inline DSL ($0.02). Both require an x402 payment: call unpaid to receive the 402 challenge (PAYMENT-REQUIRED header), sign an EIP-3009 USDC transfer, retry with the PAYMENT-SIGNATURE header. Free: GET /api/v1/templates?owner=library lists curated templates.",
    "contact": {
      "name": "makesPDF support",
      "email": "jason@makespdf.com",
      "url": "https://makespdf.com/contact"
    }
  },
  "externalDocs": {
    "description": "API reference, including errors, rate limits and the versioning policy",
    "url": "https://makespdf.com/docs/api"
  },
  "servers": [
    {
      "url": "https://makespdf.com"
    }
  ],
  "x-discovery": {
    "ownershipProofs": [
      "0xF4d2bC1034beE5eA270AcA773aEB97736d14fb31"
    ]
  },
  "security": [],
  "paths": {
    "/api/v1/markdown": {
      "summary": "Markdown to PDF (x402-paid)",
      "description": "Convert GitHub-flavoured Markdown to a tagged, accessible PDF — PDF/A-2A + PDF/UA-1 dual-compliant out of the box. Always returns a 402 challenge to unpaid requests.",
      "post": {
        "operationId": "markdownToPdf",
        "summary": "Convert Markdown to PDF",
        "description": "Pay-per-call Markdown to PDF for wallet-holding agents ($0.01 in USDC via x402). Call unpaid to get the 402 challenge, then retry with PAYMENT-SIGNATURE. Use /api/v1/md instead if you have an API key.",
        "tags": [
          "x402"
        ],
        "responses": {
          "200": {
            "description": "Rendered PDF (PDF/A-2A + PDF/UA-1 dual-compliant, tagged). Send `Accept: application/json` for metadata instead of bytes.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "X-Artifact-Id": {
                "description": "Render artifact id.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RenderMetadata"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "x-payment-info": {
          "price": {
            "mode": "fixed",
            "currency": "USD",
            "amount": "0.010000"
          },
          "protocols": [
            {
              "x402": {}
            }
          ]
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "markdown"
                ],
                "properties": {
                  "markdown": {
                    "type": "string",
                    "minLength": 1,
                    "description": "GitHub-flavoured Markdown source. Max 200KB of text - inline `data:` image payloads are excluded from that budget and bounded by the image limits (5MB each, 20 per document). Request body max 15MB."
                  },
                  "options": {
                    "type": "object",
                    "properties": {
                      "pageSize": {
                        "type": "string",
                        "enum": [
                          "A3",
                          "A4",
                          "A5",
                          "Letter",
                          "Legal"
                        ]
                      },
                      "fontFamily": {
                        "type": "string",
                        "enum": [
                          "Inter",
                          "NotoSans"
                        ]
                      },
                      "fontSize": {
                        "type": "number",
                        "minimum": 6,
                        "maximum": 24
                      },
                      "margins": {
                        "type": "array",
                        "minItems": 4,
                        "maxItems": 4,
                        "items": {
                          "type": "number"
                        },
                        "description": "[top, right, bottom, left] in points."
                      },
                      "title": {
                        "type": "string",
                        "maxLength": 200
                      },
                      "theme": {
                        "type": "string",
                        "enum": [
                          "default",
                          "github"
                        ]
                      },
                      "pageNumbers": {
                        "type": "string",
                        "enum": [
                          "page-n-of-total",
                          "n-of-total",
                          "n"
                        ],
                        "description": "Footer page numbers: 'page-n-of-total' -> 'Page 3 of 12', 'n-of-total' -> '3 / 12', 'n' -> '3'. Omit for no page numbers."
                      },
                      "pageNumberPosition": {
                        "type": "string",
                        "enum": [
                          "left",
                          "center",
                          "right"
                        ],
                        "description": "Footer alignment when pageNumbers is set. Default 'right'."
                      }
                    },
                    "description": "Rendering options. Omit for defaults."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/render": {
      "summary": "Render template to PDF (x402-paid)",
      "description": "Generate a PDF from structured data — no source document needed. Supply data plus a curated-library template id (or inline DSL); output is a tagged, accessible PDF/UA-1 + PDF/A-2A document. Always returns a 402 challenge to unpaid requests.",
      "post": {
        "operationId": "renderPdf",
        "summary": "Render a template (templateId or inline DSL) to PDF",
        "description": "The billed production render. Provide exactly one of templateId or dsl, plus data. Authed callers pay 1 credit per 10 pages; wallet callers pay $0.02 per render via x402. Failed renders are never billed.",
        "tags": [
          "x402"
        ],
        "responses": {
          "200": {
            "description": "Rendered PDF (PDF/A-2A + PDF/UA-1 dual-compliant, tagged). Send `Accept: application/json` for metadata instead of bytes.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "X-Artifact-Id": {
                "description": "Render artifact id.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RenderMetadata"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "x-payment-info": {
          "price": {
            "mode": "fixed",
            "currency": "USD",
            "amount": "0.020000"
          },
          "protocols": [
            {
              "x402": {}
            }
          ]
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "required": [
                      "templateId"
                    ],
                    "properties": {
                      "templateId": {
                        "type": "string",
                        "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$",
                        "description": "Curated-library or saved template id. Browse the library at GET /api/v1/templates?owner=library."
                      },
                      "data": {
                        "type": "object",
                        "description": "Values substituted into {{placeholders}}."
                      },
                      "options": {
                        "type": "object",
                        "properties": {
                          "title": {
                            "type": "string",
                            "maxLength": 200
                          }
                        }
                      }
                    }
                  },
                  {
                    "type": "object",
                    "required": [
                      "dsl"
                    ],
                    "properties": {
                      "dsl": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 512000,
                        "description": "Builder DSL script (bring your own template). Reference: https://makespdf.com/skills/pdf-template-author.md"
                      },
                      "data": {
                        "type": "object",
                        "description": "Values substituted into {{placeholders}}."
                      },
                      "options": {
                        "type": "object",
                        "properties": {
                          "title": {
                            "type": "string",
                            "maxLength": 200
                          }
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/md": {
      "summary": "Markdown to PDF",
      "description": "Markdown to PDF for authenticated callers, billed at 1 credit per 10 pages. While the anonymous promo is enabled, unauthenticated calls are also accepted (rate-limited per IP, 20-page cap, watermarked). Not x402-annotated — use /api/v1/markdown for x402.",
      "post": {
        "operationId": "mdToPdf",
        "summary": "Convert Markdown to PDF (API key)",
        "description": "Convert GitHub-flavoured Markdown (tables, code, Mermaid, math, alerts, footnotes) to a tagged PDF in about a second. Options can also come from YAML frontmatter.",
        "tags": [
          "render"
        ],
        "responses": {
          "200": {
            "description": "Rendered PDF (PDF/A-2A + PDF/UA-1 dual-compliant, tagged). Send `Accept: application/json` for metadata instead of bytes.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "X-Artifact-Id": {
                "description": "Render artifact id.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RenderMetadata"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "markdown"
                ],
                "properties": {
                  "markdown": {
                    "type": "string",
                    "minLength": 1,
                    "description": "GitHub-flavoured Markdown source. Max 200KB of text - inline `data:` image payloads are excluded from that budget and bounded by the image limits (5MB each, 20 per document). Request body max 15MB."
                  },
                  "options": {
                    "type": "object",
                    "properties": {
                      "pageSize": {
                        "type": "string",
                        "enum": [
                          "A3",
                          "A4",
                          "A5",
                          "Letter",
                          "Legal"
                        ]
                      },
                      "fontFamily": {
                        "type": "string",
                        "enum": [
                          "Inter",
                          "NotoSans"
                        ]
                      },
                      "fontSize": {
                        "type": "number",
                        "minimum": 6,
                        "maximum": 24
                      },
                      "margins": {
                        "type": "array",
                        "minItems": 4,
                        "maxItems": 4,
                        "items": {
                          "type": "number"
                        },
                        "description": "[top, right, bottom, left] in points."
                      },
                      "title": {
                        "type": "string",
                        "maxLength": 200
                      },
                      "theme": {
                        "type": "string",
                        "enum": [
                          "default",
                          "github"
                        ]
                      },
                      "pageNumbers": {
                        "type": "string",
                        "enum": [
                          "page-n-of-total",
                          "n-of-total",
                          "n"
                        ],
                        "description": "Footer page numbers: 'page-n-of-total' -> 'Page 3 of 12', 'n-of-total' -> '3 / 12', 'n' -> '3'. Omit for no page numbers."
                      },
                      "pageNumberPosition": {
                        "type": "string",
                        "enum": [
                          "left",
                          "center",
                          "right"
                        ],
                        "description": "Footer alignment when pageNumbers is set. Default 'right'."
                      }
                    },
                    "description": "Rendering options. Omit for defaults."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/preview": {
      "summary": "Render a DSL draft (free)",
      "description": "Free, rate-limited authoring endpoint. Same pipeline as /api/v1/render.",
      "post": {
        "operationId": "previewPdf",
        "summary": "Render a DSL template draft to PDF",
        "description": "Render inline builder DSL with data while authoring a template. Free and not billed; limited to 200 renders per hour. Publish with POST /api/v1/render.",
        "tags": [
          "render"
        ],
        "responses": {
          "200": {
            "description": "Rendered PDF (PDF/A-2A + PDF/UA-1 dual-compliant, tagged). Send `Accept: application/json` for metadata instead of bytes.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "X-Artifact-Id": {
                "description": "Render artifact id.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RenderMetadata"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "dsl"
                ],
                "properties": {
                  "dsl": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Builder DSL script. Reference: https://makespdf.com/skills/pdf-template-author.md"
                  },
                  "data": {
                    "type": "object",
                    "description": "Values substituted into {{placeholders}}. Defaults to the script's sampleData."
                  },
                  "options": {
                    "type": "object",
                    "properties": {
                      "title": {
                        "type": "string",
                        "maxLength": 200
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/templates": {
      "summary": "List templates",
      "description": "List templates under the read predicate — curated library rows are world-readable.",
      "get": {
        "operationId": "listTemplates",
        "summary": "List templates",
        "description": "Metadata for visible templates, newest first. Anonymous callers see the curated library only. Fetch one with GET /api/v1/templates/{id} to get its DSL.",
        "tags": [
          "public"
        ],
        "responses": {
          "200": {
            "description": "Visible templates.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "security": [],
        "parameters": [
          {
            "name": "owner",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "mine",
                "library",
                "all"
              ],
              "default": "all"
            }
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tags",
            "in": "query",
            "description": "Comma-separated; all must match.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Free text over name and description.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ]
      }
    },
    "/api/v1/templates/{id}": {
      "summary": "Get a template",
      "description": "One template with its DSL source and sample data.",
      "get": {
        "operationId": "getTemplate",
        "summary": "Get a template by id",
        "description": "Returns the template's DSL and sampleData. Library templates are readable anonymously; user templates only by their owner.",
        "tags": [
          "public"
        ],
        "responses": {
          "200": {
            "description": "The template.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/v1/artifacts": {
      "summary": "List render artifacts",
      "description": "List the caller's recent render artifacts (metadata only).",
      "get": {
        "operationId": "listArtifacts",
        "summary": "List artifacts",
        "description": "The caller's retained renders (7 days by default), newest first. Download one with GET /api/v1/artifacts/{id}/pdf.",
        "tags": [
          "artifacts"
        ],
        "responses": {
          "200": {
            "description": "The caller's artifacts.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtifactList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "kind",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "markdown",
                "dsl-preview",
                "dsl-render",
                "alt-text-report",
                "tag-report"
              ]
            }
          },
          {
            "name": "templateId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ]
      }
    },
    "/api/v1/md/validate": {
      "summary": "Validate markdown",
      "description": "Check markdown for PDF/UA-1 accessibility issues (heading hierarchy, missing alt text) without rendering.",
      "post": {
        "operationId": "validateMarkdown",
        "summary": "Validate markdown accessibility",
        "description": "Reports heading-hierarchy and missing-alt-text issues without rendering. Not billed.",
        "tags": [
          "validate"
        ],
        "responses": {
          "200": {
            "description": "Validation result.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "markdown"
                ],
                "properties": {
                  "markdown": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/preview/validate": {
      "summary": "Validate DSL template",
      "description": "Check a DSL template for structure and accessibility issues without rendering.",
      "post": {
        "operationId": "validateTemplate",
        "summary": "Validate a DSL template",
        "description": "Runs catalog validation (tags, nesting, styles) and alt-text checks without rendering. Not billed.",
        "tags": [
          "validate"
        ],
        "responses": {
          "200": {
            "description": "Validation result.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "dsl"
                ],
                "properties": {
                  "dsl": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key or OAuth device-flow token sent as `Authorization: Bearer <token>`. Agents should get one through the device flow (/skills/pdf-auth.md); never ask a user for their password."
      }
    },
    "headers": {
      "RateLimit": {
        "description": "Quota left in each policy this request counted against (draft-ietf-httpapi-ratelimit-headers-11), e.g. `\"render\";r=1999;t=3600`. `r` = requests remaining, `t` = seconds until the oldest counted request leaves the window.",
        "schema": {
          "type": "string"
        }
      },
      "RateLimit-Policy": {
        "description": "Policies this request counted against, e.g. `\"render\";q=2000;w=3600`. `q` = quota, `w` = window in seconds.",
        "schema": {
          "type": "string"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying. Takes precedence over RateLimit.",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "description": "Every 4xx/5xx JSON body. `error` is always present; the other fields appear when they apply.",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message. Some routes use a stable kebab-case value (e.g. `rate-limited`, `in-place-impossible`) that is safe to branch on."
          },
          "code": {
            "type": "string",
            "description": "Machine-readable error code, when the route sets one (e.g. `containers-disabled`)."
          },
          "details": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Per-field validation messages, `path: message`."
          },
          "issues": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ValidationIssue"
            },
            "description": "Template validation issues, on DSL/catalog failures."
          },
          "hint": {
            "type": "string",
            "description": "How to fix the request."
          },
          "retryAfter": {
            "type": "integer",
            "description": "Seconds until the rate limit resets (429 only; mirrors Retry-After)."
          },
          "docs": {
            "type": "string",
            "format": "uri"
          },
          "agent_guide": {
            "type": "string",
            "format": "uri"
          },
          "skill": {
            "type": "string",
            "format": "uri"
          }
        },
        "additionalProperties": true
      },
      "ValidationIssue": {
        "type": "object",
        "required": [
          "severity",
          "message"
        ],
        "properties": {
          "severity": {
            "type": "string",
            "enum": [
              "error",
              "warning"
            ]
          },
          "message": {
            "type": "string"
          },
          "path": {
            "type": "string",
            "description": "Location in the document tree."
          },
          "rule": {
            "type": "string",
            "description": "Rule identifier, e.g. a PDF/UA check."
          }
        }
      },
      "ValidationResult": {
        "type": "object",
        "required": [
          "valid",
          "issues",
          "summary"
        ],
        "properties": {
          "valid": {
            "type": "boolean",
            "description": "True when there are no error-severity issues."
          },
          "issues": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ValidationIssue"
            }
          },
          "summary": {
            "type": "object",
            "required": [
              "errors",
              "warnings"
            ],
            "properties": {
              "errors": {
                "type": "integer"
              },
              "warnings": {
                "type": "integer"
              }
            }
          }
        }
      },
      "RenderMetadata": {
        "type": "object",
        "required": [
          "success",
          "pageCount",
          "sizeBytes",
          "renderMs"
        ],
        "description": "Returned instead of PDF bytes when the request sends `Accept: application/json`.",
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "templateId": {
            "type": "string",
            "description": "Stored template rendered (/render only)."
          },
          "pageCount": {
            "type": "integer"
          },
          "sizeBytes": {
            "type": "integer"
          },
          "renderMs": {
            "type": "integer"
          },
          "artifactId": {
            "type": "string",
            "description": "Render artifact id; download again via GET /api/v1/artifacts/{id}/pdf."
          },
          "templateWarnings": {
            "type": "array",
            "items": {}
          },
          "layoutWarnings": {
            "type": "array",
            "items": {}
          },
          "credits_deducted": {
            "type": "integer"
          },
          "remaining_balance": {
            "type": "integer"
          },
          "paid": {
            "type": "object",
            "properties": {
              "payer": {
                "type": "string"
              },
              "txHash": {
                "type": "string"
              }
            }
          }
        },
        "additionalProperties": true
      },
      "TemplateSummary": {
        "type": "object",
        "required": [
          "templateId",
          "name",
          "category",
          "tags",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "templateId": {
            "type": "string",
            "description": "Library slug or user template UUID."
          },
          "ownerUserId": {
            "type": [
              "string",
              "null"
            ],
            "description": "null for curated library rows."
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TemplateList": {
        "type": "object",
        "required": [
          "templates",
          "total",
          "limit",
          "offset"
        ],
        "properties": {
          "templates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TemplateSummary"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        }
      },
      "Template": {
        "allOf": [
          {
            "$ref": "#/components/schemas/TemplateSummary"
          },
          {
            "type": "object",
            "required": [
              "dsl"
            ],
            "properties": {
              "dsl": {
                "type": "string",
                "description": "Builder DSL source."
              },
              "sampleData": {
                "description": "Example data for the template, or null."
              }
            }
          }
        ]
      },
      "Artifact": {
        "type": "object",
        "required": [
          "artifactId",
          "kind",
          "status",
          "createdAt",
          "expiresAt",
          "hasPdf"
        ],
        "properties": {
          "artifactId": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "markdown",
              "dsl-preview",
              "dsl-render",
              "alt-text-report",
              "tag-report"
            ]
          },
          "origin": {
            "type": "string",
            "enum": [
              "ui",
              "api",
              "cli"
            ]
          },
          "templateId": {
            "type": [
              "string",
              "null"
            ]
          },
          "templateName": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ]
          },
          "pageCount": {
            "type": [
              "integer",
              "null"
            ]
          },
          "byteSize": {
            "type": [
              "integer",
              "null"
            ]
          },
          "creditsCharged": {
            "type": [
              "integer",
              "null"
            ]
          },
          "errorMessage": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "hasPdf": {
            "type": "boolean"
          }
        }
      },
      "ArtifactList": {
        "type": "object",
        "required": [
          "items",
          "total",
          "limit",
          "offset"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Artifact"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        }
      },
      "PaymentRequired": {
        "type": "object",
        "description": "x402 PaymentRequired envelope. The same requirements are in the PAYMENT-REQUIRED header; see https://www.x402.org/.",
        "properties": {
          "x402Version": {
            "type": "integer"
          },
          "error": {
            "type": "string"
          },
          "accepts": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        },
        "additionalProperties": true
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Malformed body or failed validation. Never billed.",
        "headers": {
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimit-Policy"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid Bearer token. The body links the docs, llms.txt and the auth skill.",
        "headers": {
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimit-Policy"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "Insufficient credits (authed callers) or an x402 challenge (wallet callers, PAYMENT-REQUIRED header).",
        "content": {
          "application/json": {
            "schema": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/PaymentRequired"
                },
                {
                  "$ref": "#/components/schemas/Error"
                }
              ]
            }
          }
        }
      },
      "NotFound": {
        "description": "Not found, or not visible to this caller.",
        "headers": {
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimit-Policy"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded.",
        "headers": {
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimit-Policy"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServerError": {
        "description": "Unexpected server error. Never billed.",
        "headers": {
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimit-Policy"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
