{
  "openapi": "3.1.0",
  "info": {
    "title": "Laddro Career API",
    "version": "1.0.0",
    "description": "Tailor resumes, generate cover letters, and export PDFs via a simple REST API.\n\n## Getting Started\n\n1. **Get an API key** from [console.laddro.com](https://console.laddro.com)\n2. Pass it in the `x-api-key` header with every request\n3. Start with `GET /v1/resumes` to list your resumes, then tailor or export them\n\n## Rate Limits\n\nRequests consume credits based on the action. Free users receive a small monthly quota, subscribers receive a higher quota, and prepaid credits are used after quota. When credits run out, the API returns 402 with a message to upgrade or purchase credits.\n\n## SSE Streaming\n\nAll AI endpoints support Server-Sent Events. Set `Accept: text/event-stream` to receive progress updates:\n- `event: progress` — step updates (fetching job, parsing resume, tailoring...)\n- `event: complete` — final result\n- `event: error` — error with message\n\nWithout SSE, endpoints return a standard JSON response after processing completes.",
    "contact": {
      "name": "Laddro",
      "url": "https://docs.laddro.com",
      "email": "support@laddro.com"
    },
    "x-logo": {
      "url": "/logo.svg",
      "altText": "Laddro Developers"
    }
  },
  "servers": [
    { "url": "https://api.laddro.com", "description": "Current" },
    { "url": "https://api.laddro.com", "description": "Production" }
  ],
  "security": [
    { "apiKey": [] }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Your Laddro API key. Starts with laddro_live_"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "description": "Human-readable error message" }
        }
      },
      "ResumeSummary": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "resumeId": { "type": "string", "format": "uuid" },
          "title": { "type": "string", "example": "Software Engineer Resume" },
          "isDefault": { "type": "boolean" },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "ResumeList": {
        "type": "object",
        "properties": {
          "items": { "type": "array", "items": { "$ref": "#/components/schemas/ResumeSummary" } },
          "total": { "type": "integer", "example": 5 },
          "limit": { "type": "integer", "example": 20 },
          "offset": { "type": "integer", "example": 0 }
        }
      },
      "TailorRequest": {
        "type": "object",
        "required": ["positionName"],
        "properties": {
          "resumeId": {
            "type": "string",
            "format": "uuid",
            "description": "Resume to tailor. If omitted, the user's default resume is used."
          },
          "positionName": {
            "type": "string",
            "description": "Job title you are applying for",
            "example": "Senior Frontend Engineer"
          },
          "jobDescription": {
            "type": "string",
            "maxLength": 50000,
            "description": "Full job description text. Either this or jobUrl is required.",
            "example": "We are looking for a Senior Frontend Engineer with 5+ years..."
          },
          "jobUrl": {
            "type": "string",
            "format": "uri",
            "description": "URL to the job posting. The API will fetch and extract the description.",
            "example": "https://jobs.example.com/senior-frontend-engineer"
          },
          "mode": {
            "type": "string",
            "enum": ["standard", "new"],
            "default": "standard",
            "description": "'standard' rewrites existing sections. 'new' allows full restructuring."
          },
          "language": {
            "type": "string",
            "description": "Language code for the output (e.g. 'en', 'de', 'fr'). Defaults to resume language.",
            "example": "en"
          },
          "includeCoverLetter": { "type": "boolean", "default": false, "description": "Also generate a cover letter. Returns a ZIP with resume.pdf and cover-letter.pdf." },
          "templateId": { "type": "string", "description": "Template for PDF rendering. Defaults to resume's saved template.", "example": "GRAPHITE" },
          "colorId": { "type": "string", "description": "Color scheme ID.", "example": "graphite-blue" },
          "font": { "type": "string", "description": "Font family.", "example": "Inter" },
          "spacing": { "type": "number", "description": "Line spacing.", "example": 8 },
          "margin": { "type": "number", "description": "Page margin.", "example": 8 },
          "fontSize": { "type": "number", "description": "Base font size.", "example": 8 },
          "pageNumbering": { "type": "string", "enum": ["none", "simple", "fraction", "page"] }
        }
      },
      "CoverLetterRequest": {
        "type": "object",
        "required": ["positionName"],
        "properties": {
          "resumeId": {
            "type": "string",
            "format": "uuid",
            "description": "Resume to base the cover letter on. If omitted, the default resume is used."
          },
          "positionName": {
            "type": "string",
            "example": "Senior Frontend Engineer"
          },
          "jobDescription": {
            "type": "string",
            "maxLength": 50000,
            "description": "Either this or jobUrl is required."
          },
          "jobUrl": {
            "type": "string",
            "format": "uri",
            "description": "URL to the job posting."
          },
          "language": {
            "type": "string",
            "example": "de"
          },
          "templateId": { "type": "string", "description": "Template for PDF rendering.", "example": "GRAPHITE" },
          "colorId": { "type": "string", "description": "Color scheme ID.", "example": "graphite-blue" },
          "font": { "type": "string", "description": "Font family.", "example": "Inter" },
          "spacing": { "type": "number", "example": 8 },
          "margin": { "type": "number", "example": 8 },
          "fontSize": { "type": "number", "example": 8 },
          "pageNumbering": { "type": "string", "enum": ["none", "simple", "fraction", "page"] }
        }
      },
      "ExportRequest": {
        "type": "object",
        "required": ["resumeId"],
        "properties": {
          "resumeId": { "type": "string", "format": "uuid", "description": "Resume to export" },
          "templateId": { "type": "string", "description": "Template ID (e.g. GRAPHITE). Defaults to resume's saved template.", "example": "GRAPHITE" },
          "locale": { "type": "string", "description": "Language for section headers and dates.", "example": "en" },
          "colorId": { "type": "string", "description": "Color scheme ID from GET /v1/templates/{id}.", "example": "graphite-blue" },
          "font": { "type": "string", "description": "Font family from GET /v1/fonts.", "example": "Inter" },
          "spacing": { "type": "number", "example": 8 },
          "margin": { "type": "number", "example": 8 },
          "fontSize": { "type": "number", "example": 8 },
          "showProfileImage": { "type": "boolean", "example": false },
          "profileImageUrl": { "type": "string" },
          "pageNumbering": { "type": "string", "enum": ["none", "simple", "fraction", "page"], "example": "none" }
        }
      },
      "Template": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "example": "GRAPHITE" },
          "name": { "type": "string", "example": "Graphite" },
          "atsScore": { "type": "integer", "example": 99 },
          "layoutType": { "type": "string", "enum": ["single-column", "two-column", "two-column-sidebar"] },
          "supportsProfileImage": { "type": "boolean" },
          "defaults": {
            "type": "object",
            "properties": {
              "pageSize": { "type": "string", "example": "A4" },
              "spacing": { "type": "integer", "example": 8 },
              "fontSize": { "type": "integer", "example": 8 },
              "font": { "type": "string", "example": "Inter" },
              "pageNumbering": { "type": "string", "example": "none" }
            }
          }
        }
      },
      "TemplateDetail": {
        "type": "object",
        "allOf": [
          { "$ref": "#/components/schemas/Template" },
          {
            "type": "object",
            "properties": {
              "availableColors": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string", "example": "graphite-blue" },
                    "backgroundColor": { "type": "string", "example": "#FFFFFF" },
                    "backgroundPartColor": { "type": "string" },
                    "underlineColor": { "type": "string" },
                    "text": { "type": "string" },
                    "textMuted": { "type": "string" }
                  }
                }
              },
              "availableFonts": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "family": { "type": "string", "example": "Inter" },
                    "label": { "type": "string", "example": "Inter" }
                  }
                }
              }
            }
          }
        ]
      }
    }
  },
  "paths": {
    "/v1/templates": {
      "get": {
        "tags": ["Templates"],
        "summary": "List resume templates",
        "description": "Returns all 22 available resume templates with ATS compatibility scores, layout types, and default settings. No authentication required.",
        "security": [],
        "responses": {
          "200": {
            "description": "List of templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "templates": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Template" }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/templates/{templateId}": {
      "get": {
        "tags": ["Templates"],
        "summary": "Get template details",
        "description": "Returns full details for a template including all color schemes (with human-readable IDs), available fonts, and default settings. No authentication required.",
        "security": [],
        "parameters": [
          {
            "name": "templateId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "description": "Template ID (case-insensitive)",
            "example": "GRAPHITE"
          }
        ],
        "responses": {
          "200": {
            "description": "Template with colors and fonts",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TemplateDetail" }
              }
            }
          },
          "404": {
            "description": "Template not found",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/v1/fonts": {
      "get": {
        "tags": ["Templates"],
        "summary": "List available fonts",
        "description": "Returns all 21 font families available for resume rendering. No authentication required.",
        "security": [],
        "responses": {
          "200": {
            "description": "List of fonts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "fonts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "family": { "type": "string", "example": "Inter" },
                          "label": { "type": "string", "example": "Inter" }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/languages": {
      "get": {
        "tags": ["Templates"],
        "summary": "List supported languages",
        "description": "Returns all 14 supported locales for resume rendering, section headers, and date formatting. No authentication required.",
        "security": [],
        "responses": {
          "200": {
            "description": "List of languages",
            "content": {
              "application/json": {
                "example": {
                  "languages": [
                    { "code": "en", "name": "English" },
                    { "code": "de", "name": "German" },
                    { "code": "es", "name": "Spanish" },
                    { "code": "fr", "name": "French" }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/resumes": {
      "get": {
        "tags": ["Resumes"],
        "summary": "List resumes",
        "description": "Returns a paginated list of the user's resumes without content. Use GET /v1/resumes/{resumeId} for full data.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "default": 20, "maximum": 100 }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": { "type": "integer", "default": 0 }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResumeList" } } }
          },
          "401": { "description": "Invalid or missing API key", "content": { "application/json": { "example": { "error": "Missing x-api-key header" } } } },
          "402": { "description": "Monthly usage limit exceeded", "content": { "application/json": { "example": { "error": "Monthly API limit reached. Purchase credits or upgrade your plan at console.laddro.com" } } } }
        }
      }
    },
    "/v1/resumes/{resumeId}": {
      "get": {
        "tags": ["Resumes"],
        "summary": "Get resume",
        "description": "Returns resume metadata (title, dates, default status). Resume content is never exposed via the API.",
        "parameters": [
          {
            "name": "resumeId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "Resume metadata",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResumeSummary" } } }
          },
          "401": { "description": "Invalid or missing API key", "content": { "application/json": { "example": { "error": "Invalid API key" } } } },
          "404": { "description": "No resume found with this ID for your account", "content": { "application/json": { "example": { "error": "Resume not found" } } } }
        }
      }
    },
    "/v1/resumes/{resumeId}/render": {
      "put": {
        "tags": ["Resumes"],
        "summary": "Render resume with new template settings",
        "description": "Apply new template settings to a resume and get a PDF back. Change the template, colors, font, spacing — get an instant preview.\n\nCosts 3 credits per render.",
        "parameters": [
          {
            "name": "resumeId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["templateId"],
                "properties": {
                  "templateId": { "type": "string", "example": "GRAPHITE" },
                  "locale": { "type": "string", "example": "en" },
                  "colorId": { "type": "string", "example": "graphite-blue" },
                  "font": { "type": "string", "example": "Inter" },
                  "spacing": { "type": "number", "example": 8 },
                  "margin": { "type": "number", "example": 8 },
                  "fontSize": { "type": "number", "example": 8 },
                  "showProfileImage": { "type": "boolean", "example": false },
                  "profileImageUrl": { "type": "string" },
                  "pageNumbering": { "type": "string", "enum": ["none", "simple", "fraction", "page"], "example": "none" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rendered PDF",
            "content": {
              "application/pdf": { "schema": { "type": "string", "format": "binary" } },
              "application/zip": { "schema": { "type": "string", "format": "binary" } }
            }
          },
          "400": { "description": "Missing fields", "content": { "application/json": { "example": { "error": "templateId is required", "code": "bad_request" } } } },
          "401": { "description": "Invalid API key", "content": { "application/json": { "example": { "error": "Invalid API key", "code": "invalid_key" } } } },
          "404": { "description": "Resume not found", "content": { "application/json": { "example": { "error": "Resume not found", "code": "not_found" } } } },
          "502": { "description": "Renderer failed", "content": { "application/json": { "example": { "error": "PDF renderer unreachable", "code": "renderer_error" } } } }
        }
      }
    },
    "/v1/resumes/parse": {
      "post": {
        "tags": ["Resumes"],
        "summary": "Create resume from PDF",
        "description": "Upload a PDF resume, and get a professionally rendered PDF back using your chosen template and settings.\n\nThe API extracts the content from your PDF, parses it, and renders it with the specified template. Costs 1 credit.\n\n**Content-Type:** multipart/form-data",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file"],
                "properties": {
                  "file": { "type": "string", "format": "binary", "description": "PDF or DOCX resume (max 10MB)" },
                  "templateId": { "type": "string", "description": "Template to render with. Defaults to GRAPHITE.", "example": "GRAPHITE" },
                  "locale": { "type": "string", "description": "Language for section headers and dates.", "example": "en" },
                  "colorId": { "type": "string", "description": "Color scheme ID from GET /v1/templates/{id}.", "example": "graphite-blue" },
                  "font": { "type": "string", "description": "Font family from GET /v1/fonts.", "example": "Inter" },
                  "spacing": { "type": "number", "description": "Line spacing.", "example": 8 },
                  "margin": { "type": "number", "description": "Page margin.", "example": 8 },
                  "fontSize": { "type": "number", "description": "Base font size.", "example": 8 },
                  "showProfileImage": { "type": "boolean", "description": "Show profile photo.", "example": false },
                  "profileImageUrl": { "type": "string", "description": "Profile image URL." },
                  "pageNumbering": { "type": "string", "enum": ["none", "simple", "fraction", "page"], "example": "none" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rendered PDF",
            "content": {
              "application/pdf": { "schema": { "type": "string", "format": "binary" } }
            }
          },
          "400": { "description": "Invalid upload", "content": { "application/json": { "examples": { "missing_file": { "value": { "error": "Missing required 'file' field — upload a PDF resume", "code": "bad_request" } }, "wrong_type": { "value": { "error": "Only PDF files are supported — received .docx", "code": "bad_request" } } } } } },
          "401": { "description": "Invalid API key", "content": { "application/json": { "example": { "error": "Invalid API key", "code": "invalid_key" } } } },
          "422": { "description": "Could not extract text", "content": { "application/json": { "example": { "error": "Could not extract meaningful text from PDF — the file may be image-based or scanned", "code": "unprocessable" } } } },
          "502": { "description": "AI or renderer failed", "content": { "application/json": { "example": { "error": "AI service is temporarily unavailable — try again later", "code": "provider_error" } } } }
        }
      }
    },
    "/v1/tailor": {
      "post": {
        "tags": ["Tailor"],
        "summary": "Tailor resume for a job",
        "description": "Rewrites a resume to match a specific job posting. Returns a PDF.\n\n**Resume source:**\n- `resumeId` — use a stored resume\n- `file` (multipart) — upload a PDF\n- Empty — uses your default resume\n\n**Job (pick one):**\n- `jobDescription` — paste the job description text\n- `jobUrl` — provide a URL and we'll extract it\n\n**Cover letter:** Set `includeCoverLetter: true` to also generate a cover letter. Returns a ZIP with both PDFs.\n\nCosts 1 credit.\n\nSupports both JSON and multipart/form-data.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/TailorRequest" }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["positionName"],
                "properties": {
                  "file": { "type": "string", "format": "binary", "description": "PDF or DOCX resume (max 10MB)" },
                  "resumeId": { "type": "string" },
                  "positionName": { "type": "string" },
                  "jobDescription": { "type": "string" },
                  "jobUrl": { "type": "string" },
                  "mode": { "type": "string", "enum": ["standard", "new"] },
                  "language": { "type": "string" },
                  "includeCoverLetter": { "type": "string", "enum": ["true", "false"] }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tailored resume as PDF (or ZIP with cover letter if includeCoverLetter is true)",
            "headers": {
              "X-Resume-Id": { "description": "ID of the saved tailored resume.", "schema": { "type": "string" } },
              "X-Cover-Letter-Id": { "description": "ID of the saved cover letter when includeCoverLetter is true.", "schema": { "type": "string" } },
              "Content-Disposition": { "description": "Download filename for the generated artifact.", "schema": { "type": "string" } }
            },
            "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } } }
          },
          "400": { "description": "Missing or invalid fields", "content": { "application/json": { "examples": { "missing_position": { "value": { "error": "positionName is required" } }, "missing_job": { "value": { "error": "Either jobDescription or jobUrl is required" } }, "too_long": { "value": { "error": "Job description exceeds 50,000 character limit" } }, "bad_url": { "value": { "error": "Failed to fetch job URL: could not reach URL" } } } } } },
          "401": { "description": "Invalid API key", "content": { "application/json": { "example": { "error": "Missing x-api-key header" } } } },
          "402": { "description": "Usage limit exceeded", "content": { "application/json": { "example": { "error": "Monthly API limit reached. Purchase credits or upgrade your plan at console.laddro.com" } } } },
          "404": { "description": "Resume not found", "content": { "application/json": { "example": { "error": "Resume not found" } } } },
          "502": { "description": "AI service failed", "content": { "application/json": { "example": { "error": "AI service rate limit exceeded — try again in a moment" } } } }
        }
      }
    },
    "/v1/cover-letters": {
      "get": {
        "tags": ["Cover Letters"],
        "summary": "List cover letters",
        "description": "Returns a paginated list of your cover letters. Metadata only — no content.",
        "parameters": [
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } },
          { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0 } }
        ],
        "responses": {
          "200": {
            "description": "Paginated list",
            "content": {
              "application/json": {
                "example": {
                  "items": [
                    { "id": "abc-123", "coverLetterId": "def-456", "title": "Cover letter for Senior Frontend Engineer", "createdAt": "2026-05-08T01:00:00Z", "updatedAt": "2026-05-08T01:00:00Z" }
                  ],
                  "total": 1,
                  "limit": 20,
                  "offset": 0
                }
              }
            }
          },
          "401": { "description": "Invalid API key", "content": { "application/json": { "example": { "error": "Missing x-api-key header", "code": "unauthorized" } } } }
        }
      },
      "post": {
        "tags": ["Cover Letters"],
        "summary": "Create cover letter",
        "description": "Create a cover letter manually — no AI needed. Fill in the fields and save. Then render to PDF via PUT /v1/cover-letters/{id}/render.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["fullName", "letterContent"],
                "properties": {
                  "title": { "type": "string", "description": "Title for your reference.", "example": "Cover letter for Acme Corp" },
                  "fullName": { "type": "string", "example": "Jane Doe" },
                  "jobTitle": { "type": "string", "example": "Senior Frontend Engineer" },
                  "address": { "type": "string", "example": "Berlin, Germany" },
                  "email": { "type": "string", "example": "jane@example.com" },
                  "phone": { "type": "string", "example": "+49 170 1234567" },
                  "companyName": { "type": "string", "example": "Acme Corp" },
                  "hiringManager": { "type": "string", "example": "John Smith" },
                  "letterContent": { "type": "string", "description": "The cover letter body as HTML (<p> tags).", "example": "<p>Dear John Smith,</p><p>I am writing to express my interest in the Senior Frontend Engineer position at Acme Corp...</p><p>Sincerely,</p><p>Jane Doe</p>" }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Cover letter created",
            "content": {
              "application/json": {
                "example": { "coverLetterId": "abc-123", "title": "Cover letter for Acme Corp", "status": "created" }
              }
            }
          },
          "400": { "description": "Missing fields", "content": { "application/json": { "examples": { "missing_content": { "value": { "error": "letterContent is required", "code": "bad_request" } }, "missing_name": { "value": { "error": "fullName is required", "code": "bad_request" } } } } } },
          "401": { "description": "Invalid API key", "content": { "application/json": { "example": { "error": "Missing x-api-key header", "code": "unauthorized" } } } }
        }
      }
    },
    "/v1/cover-letters/{coverLetterId}": {
      "get": {
        "tags": ["Cover Letters"],
        "summary": "Get cover letter",
        "description": "Returns cover letter metadata and the saved letter content for the owner. Use the render endpoint to get a PDF.",
        "parameters": [
          { "name": "coverLetterId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": {
          "200": {
            "description": "Cover letter metadata",
            "content": {
              "application/json": {
                "example": {
                  "id": "abc-123",
                  "coverLetterId": "def-456",
                  "title": "Cover letter for Senior Frontend Engineer",
                  "letterContent": "Dear hiring team...",
                  "data": { "letterContent": "Dear hiring team..." },
                  "createdAt": "2026-05-08T01:00:00Z",
                  "updatedAt": "2026-05-08T01:00:00Z"
                }
              }
            }
          },
          "401": { "description": "Invalid API key", "content": { "application/json": { "example": { "error": "Missing x-api-key header", "code": "unauthorized" } } } },
          "404": { "description": "Cover letter not found", "content": { "application/json": { "example": { "error": "Cover letter not found", "code": "not_found" } } } }
        }
      }
    },
    "/v1/cover-letters/{coverLetterId}/render": {
      "put": {
        "tags": ["Cover Letters"],
        "summary": "Render cover letter with template settings",
        "description": "Apply template settings to a saved cover letter and get a PDF back. Settings are saved for future renders.\n\nCosts 3 credits.",
        "parameters": [
          { "name": "coverLetterId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["templateId"],
                "properties": {
                  "templateId": { "type": "string", "example": "GRAPHITE" },
                  "locale": { "type": "string", "example": "en" },
                  "colorId": { "type": "string", "example": "graphite-blue" },
                  "font": { "type": "string", "example": "Inter" },
                  "spacing": { "type": "number", "example": 8 },
                  "margin": { "type": "number", "example": 8 },
                  "fontSize": { "type": "number", "example": 8 },
                  "pageNumbering": { "type": "string", "enum": ["none", "simple", "fraction", "page"], "example": "none" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rendered PDF",
            "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } } }
          },
          "400": { "description": "Missing fields", "content": { "application/json": { "example": { "error": "templateId is required", "code": "bad_request" } } } },
          "401": { "description": "Invalid API key", "content": { "application/json": { "example": { "error": "Invalid API key", "code": "invalid_key" } } } },
          "404": { "description": "Cover letter not found", "content": { "application/json": { "example": { "error": "Cover letter not found", "code": "not_found" } } } },
          "502": { "description": "Renderer failed", "content": { "application/json": { "example": { "error": "PDF renderer unavailable after 3 retries", "code": "renderer_error" } } } }
        }
      }
    },
    "/v1/cover-letters/generate": {
      "post": {
        "tags": ["Cover Letters"],
        "summary": "Generate cover letter",
        "description": "Generates a professional cover letter based on your resume and a job description.\n\nCosts 1 credit.\n\n**Resume source:** same as /v1/tailor (resumeId, file upload, or default).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CoverLetterRequest" }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["positionName"],
                "properties": {
                  "file": { "type": "string", "format": "binary", "description": "PDF or DOCX resume (max 10MB)" },
                  "resumeId": { "type": "string" },
                  "positionName": { "type": "string" },
                  "jobDescription": { "type": "string" },
                  "jobUrl": { "type": "string" },
                  "language": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generated cover letter as PDF",
            "headers": {
              "X-Cover-Letter-Id": { "description": "ID of the saved generated cover letter.", "schema": { "type": "string" } },
              "Content-Disposition": { "description": "Download filename for the generated artifact.", "schema": { "type": "string" } }
            },
            "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } } }
          },
          "400": { "description": "Missing or invalid fields", "content": { "application/json": { "examples": { "missing_position": { "value": { "error": "positionName is required" } }, "missing_job": { "value": { "error": "Either jobDescription or jobUrl is required" } } } } } },
          "401": { "description": "Invalid API key", "content": { "application/json": { "example": { "error": "Missing x-api-key header" } } } },
          "402": { "description": "Usage limit exceeded", "content": { "application/json": { "example": { "error": "Monthly API limit reached. Purchase credits or upgrade your plan at console.laddro.com" } } } },
          "404": { "description": "Resume not found", "content": { "application/json": { "example": { "error": "Resume not found" } } } },
          "502": { "description": "AI service failed", "content": { "application/json": { "example": { "error": "AI service rate limit exceeded — try again in a moment" } } } }
        }
      }
    },
    "/v1/export": {
      "post": {
        "tags": ["Export"],
        "summary": "Export resume as PDF",
        "description": "Generates a PDF from a resume using the specified template, colors, and font settings.\n\n**Response modes:**\n- Default: returns PDF binary (Content-Type: application/pdf)\n- SSE: returns progress events then metadata\n\nAll settings are optional. If omitted, the resume's saved template settings are used. Each export costs 3 credits.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ExportRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "PDF binary or SSE stream",
            "content": {
              "application/pdf": {
                "schema": { "type": "string", "format": "binary" }
              }
            }
          },
          "400": { "description": "Missing or invalid fields", "content": { "application/json": { "example": { "error": "resumeId is required" } } } },
          "401": { "description": "Invalid API key", "content": { "application/json": { "example": { "error": "Invalid API key" } } } },
          "402": { "description": "Usage limit exceeded", "content": { "application/json": { "example": { "error": "Monthly API limit reached. Purchase credits or upgrade your plan at console.laddro.com" } } } },
          "404": { "description": "Resume not found", "content": { "application/json": { "example": { "error": "Resume not found" } } } },
          "502": { "description": "PDF renderer unreachable", "content": { "application/json": { "example": { "error": "PDF renderer unreachable" } } } }
        }
      }
    }
  },
  "tags": [
    { "name": "Resumes", "description": "List, get, create from PDF, and render resumes with different template settings." },
    { "name": "Tailor", "description": "Tailor a resume for a specific job posting. Rewrites content to match the job description." },
    { "name": "Cover Letters", "description": "Generate a cover letter from your resume and a job description." },
    { "name": "Export", "description": "Export a resume as PDF." },
    { "name": "Templates", "description": "Browse resume templates, color schemes, and fonts. No authentication required." }
  ]
}