{"openapi":"3.1.0","info":{"title":"GenLegalTxt API","version":"2.0.0","description":"Public API to generate legal documents in 7 languages: Spanish, English, Catalan, Basque, Galician, French and German.\n\nOne endpoint per document type (no API key required):\n- `POST /api/generate/privacy-policy`\n- `POST /api/generate/legal-notice`\n- `POST /api/generate/cookie-policy`\n- `POST /api/generate/accessibility-statement`\n- `POST /api/generate/confidentiality-notice`\n- `POST /api/generate/general-terms-and-conditions-of-sale`\n\n**Rate limit:** 30 requests per minute per IP, shared across all endpoints.\nResponses include `X-RateLimit-Limit` and `X-RateLimit-Remaining` headers, and `Retry-After` when the limit is reached (HTTP 429).\n\n**Security note:** all strings in `formData` are HTML-escaped server-side, and the returned document is\nadditionally sanitized (scripts, event handlers and dangerous URL schemes are removed).\n\n**Output formats:** every endpoint accepts `format: 'html'` (default) or `format: 'markdown'`.\nThe legacy aggregate endpoint `POST /api/generate-document` also remains available for backwards compatibility."},"servers":[{"url":"https://genlegaltxts.com"},{"url":"http://localhost:3000","description":"Local development"}],"tags":[{"name":"privacy-policy","description":"Generate a privacy policy"},{"name":"legal-notice","description":"Generate a legal notice"},{"name":"cookie-policy","description":"Generate a cookie policy"},{"name":"accessibility-statement","description":"Generate an accessibility statement"},{"name":"confidentiality-notice","description":"Generate a confidentiality notice"},{"name":"general-terms-and-conditions-of-sale","description":"Generate general terms and conditions of sale"}],"paths":{"/api/generate/privacy-policy":{"post":{"operationId":"generatePrivacyPolicy","summary":"Generate a privacy policy","description":"Generates a privacy policy (RGPD/LOPD-GDD compliant) as an HTML string.\n\n**Rate limit:** 30 requests/minute/IP (shared across all generate endpoints).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["formData"],"properties":{"locale":{"type":"string","enum":["es","en","ca","eu","gl","fr","de"],"default":"es","description":"Language of the generated document. Unknown values fall back to Spanish (es)."},"format":{"type":"string","enum":["html","markdown"],"default":"html","description":"Output format of `content`. `html` (default) returns the sanitized document HTML; `markdown` returns the document converted to Markdown (via turndown)."},"formData":{"type":"object","additionalProperties":true,"properties":{"webUrl":{"type":"string","description":"Website URL, e.g. https://example.com"},"webName":{"type":"string","description":"Website or company display name"},"controllerType":{"type":"string","description":"'persona' (individual) or 'empresa' (company)"},"fullName":{"type":"string","description":"Owner full name (when controllerType is 'persona')"},"idNumber":{"type":"string","description":"Owner ID number, e.g. DNI/NIE (when controllerType is 'persona')"},"companyName":{"type":"string","description":"Company name (when controllerType is 'empresa')"},"taxId":{"type":"string","description":"Company tax id, e.g. NIF/CIF (when controllerType is 'empresa')"},"registrationInfo":{"type":"string","description":"Commercial registry name"},"registrationDetails":{"type":"string","description":"Registry details (volume, sheet, page...)"},"legalRepresentative":{"type":"string","description":"Legal representative name"},"address":{"type":"string","description":"Registered address"},"phone":{"type":"string","description":"Contact phone"},"fax":{"type":"string","description":"Contact fax (optional)"},"email":{"type":"string","description":"Contact email for data protection requests"},"dataTypes":{"type":"array","description":"Types of personal data collected","items":{"type":"string"}},"retentionPeriod":{"type":"string","description":"Data retention period description"},"dataSharing":{"type":"string","description":"Description of data sharing/recipients"},"thirdPartyInfo":{"type":"string","description":"Info about third-party services processing data"},"hasSsl":{"type":"boolean","description":"Whether the site uses SSL/HTTPS"}},"description":"Form data for this document type. All fields are optional — missing fields render as empty sections, but providing them produces a complete document. All strings are HTML-escaped server-side (5000-character cap); arrays and nested objects are sanitized recursively. Malformed payloads that a template cannot process return HTTP 400 with the offending field in the message."}},"examples":{"default":{"summary":"privacy-policy example","value":{"documentType":"privacy-policy","locale":"es","formData":{"webUrl":"https://example.com","webName":"Example","controllerType":"empresa","companyName":"Example S.L.","taxId":"B12345678","address":"Calle Falsa 123, Madrid","email":"privacy@example.com","dataTypes":["identification data","browsing data"],"retentionPeriod":"As long as the commercial relationship lasts","hasSsl":true}}}}}}}},"responses":{"200":{"description":"Document generated successfully","headers":{"X-RateLimit-Limit":{"description":"Maximum requests allowed per minute (30)","schema":{"type":"string","example":"30"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window","schema":{"type":"string","example":"29"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateResponse"}}}},"400":{"description":"Bad request — missing formData, malformed JSON, or formData does not satisfy the template contract (the reason names the failing field)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"405":{"description":"Method not allowed — only POST is accepted"},"429":{"description":"Too many requests — 30 req/min/IP exceeded. Check Retry-After (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying","schema":{"type":"string","example":"37"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/generate/legal-notice":{"post":{"operationId":"generateLegalNotice","summary":"Generate a legal notice","description":"Generates a legal notice and general terms of use (LSSI-CE compliant) as an HTML string. Uses a nested `data` object for the questionnaire booleans (values are the strings 'Sí' or 'No').\n\n**Rate limit:** 30 requests/minute/IP (shared across all generate endpoints).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["formData"],"properties":{"locale":{"type":"string","enum":["es","en","ca","eu","gl","fr","de"],"default":"es","description":"Language of the generated document. Unknown values fall back to Spanish (es)."},"format":{"type":"string","enum":["html","markdown"],"default":"html","description":"Output format of `content`. `html` (default) returns the sanitized document HTML; `markdown` returns the document converted to Markdown (via turndown)."},"formData":{"type":"object","additionalProperties":true,"properties":{"web":{"type":"string","description":"Website URL shown as the site identity"},"denominacion":{"type":"string","description":"Owner or company display name"},"data":{"type":"object","description":"Questionnaire: claseTitular ('persona'|'empresa'), accesoLibre, registroLibre, interaccionLibre, mayorEdad, paisParticular, enlacesTerceros, enlacesAfiliacion — each 'Sí' or 'No'"},"nombrePersona":{"type":"string","description":"Owner name (when data.claseTitular is 'persona')"},"nifPersona":{"type":"string","description":"Owner NIF (when data.claseTitular is 'persona')"},"nombreEmpresa":{"type":"string","description":"Company name (when data.claseTitular is 'empresa')"},"nifEmpresa":{"type":"string","description":"Company NIF (when data.claseTitular is 'empresa')"},"registroEmpresa":{"type":"string","description":"Commercial registry"},"datosRegistroEmpresa":{"type":"string","description":"Registry details"},"representanteEmpresa":{"type":"string","description":"Company representative"},"direccionTitular":{"type":"string","description":"Owner address"},"telefonoTitular":{"type":"string","description":"Owner phone (optional)"},"faxTitular":{"type":"string","description":"Owner fax (optional)"},"emailTitular":{"type":"string","description":"Owner contact email"},"pais":{"type":"string","description":"Country"}},"description":"Form data for this document type. All fields are optional — missing fields render as empty sections, but providing them produces a complete document. All strings are HTML-escaped server-side (5000-character cap); arrays and nested objects are sanitized recursively. Malformed payloads that a template cannot process return HTTP 400 with the offending field in the message."}},"examples":{"default":{"summary":"legal-notice example","value":{"locale":"es","formData":{"web":"example.com","denominacion":"Example S.L.","data":{"claseTitular":"empresa","accesoLibre":"Sí","registroLibre":"No","interaccionLibre":"No","mayorEdad":"No","paisParticular":"No","enlacesTerceros":"No","enlacesAfiliacion":"No"},"nombreEmpresa":"Example S.L.","nifEmpresa":"B12345678","registroEmpresa":"Registro Mercantil de Madrid","datosRegistroEmpresa":"Tomo 123, Folio 45, Hoja M-67890","representanteEmpresa":"Jane Doe","direccionTitular":"Calle Falsa 123, Madrid","emailTitular":"legal@example.com","pais":"España"}}}}}}}},"responses":{"200":{"description":"Document generated successfully","headers":{"X-RateLimit-Limit":{"description":"Maximum requests allowed per minute (30)","schema":{"type":"string","example":"30"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window","schema":{"type":"string","example":"29"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateResponse"}}}},"400":{"description":"Bad request — missing formData, malformed JSON, or formData does not satisfy the template contract (the reason names the failing field)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"405":{"description":"Method not allowed — only POST is accepted"},"429":{"description":"Too many requests — 30 req/min/IP exceeded. Check Retry-After (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying","schema":{"type":"string","example":"37"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/generate/cookie-policy":{"post":{"operationId":"generateCookiePolicy","summary":"Generate a cookie policy","description":"Generates a cookie policy as an HTML string.\n\n**Rate limit:** 30 requests/minute/IP (shared across all generate endpoints).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["formData"],"properties":{"locale":{"type":"string","enum":["es","en","ca","eu","gl","fr","de"],"default":"es","description":"Language of the generated document. Unknown values fall back to Spanish (es)."},"format":{"type":"string","enum":["html","markdown"],"default":"html","description":"Output format of `content`. `html` (default) returns the sanitized document HTML; `markdown` returns the document converted to Markdown (via turndown)."},"formData":{"type":"object","additionalProperties":true,"properties":{"webUrl":{"type":"string","description":"Website URL"},"webName":{"type":"string","description":"Website name"},"hasSocialMedia":{"type":"boolean","description":"Site uses social media cookies"},"hasOwnCookies":{"type":"boolean","description":"Site sets its own cookies"},"hasThirdPartyCookies":{"type":"boolean","description":"Site uses third-party cookies"},"thirdPartyDetails":{"type":"string","description":"Description of third-party cookies used"}},"description":"Form data for this document type. All fields are optional — missing fields render as empty sections, but providing them produces a complete document. All strings are HTML-escaped server-side (5000-character cap); arrays and nested objects are sanitized recursively. Malformed payloads that a template cannot process return HTTP 400 with the offending field in the message."}},"examples":{"default":{"summary":"cookie-policy example","value":{"locale":"es","formData":{"webUrl":"https://example.com","webName":"Example","hasSocialMedia":true,"hasOwnCookies":true,"hasThirdPartyCookies":false,"thirdPartyDetails":""}}}}}}}},"responses":{"200":{"description":"Document generated successfully","headers":{"X-RateLimit-Limit":{"description":"Maximum requests allowed per minute (30)","schema":{"type":"string","example":"30"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window","schema":{"type":"string","example":"29"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateResponse"}}}},"400":{"description":"Bad request — missing formData, malformed JSON, or formData does not satisfy the template contract (the reason names the failing field)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"405":{"description":"Method not allowed — only POST is accepted"},"429":{"description":"Too many requests — 30 req/min/IP exceeded. Check Retry-After (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying","schema":{"type":"string","example":"37"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/generate/accessibility-statement":{"post":{"operationId":"generateAccessibilityStatement","summary":"Generate an accessibility statement","description":"Generates an accessibility statement (RD 1112/2018 compliant) as an HTML string. Set each UNE guideline field to true when the site conforms to it, false when it does not.\n\n**Rate limit:** 30 requests/minute/IP (shared across all generate endpoints).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["formData"],"properties":{"locale":{"type":"string","enum":["es","en","ca","eu","gl","fr","de"],"default":"es","description":"Language of the generated document. Unknown values fall back to Spanish (es)."},"format":{"type":"string","enum":["html","markdown"],"default":"html","description":"Output format of `content`. `html` (default) returns the sanitized document HTML; `markdown` returns the document converted to Markdown (via turndown)."},"formData":{"type":"object","additionalProperties":true,"properties":{"web":{"type":"string","description":"Website URL"},"denominacion":{"type":"string","description":"Website name"},"emailContacto":{"type":"string","description":"Contact email for accessibility feedback"},"telefonoContacto":{"type":"string","description":"Contact phone"},"faxContacto":{"type":"string","description":"Contact fax (optional)"},"notAccessible":{"type":"boolean","description":"Declare known non-accessible content"},"UNE5":{"type":"boolean","description":"Conforms with UNE-EN 301 549 section 5"},"UNE52":{"type":"boolean","description":"Conforms with section 5.2"},"UNE6":{"type":"boolean","description":"Conforms with section 6"},"UNE61":{"type":"boolean","description":"Conforms with section 6.1"},"UNE6211":{"type":"boolean","description":"Conforms with 6.2.1.1"},"UNE6212":{"type":"boolean","description":"Conforms with 6.2.1.2"},"UNE6221":{"type":"boolean","description":"Conforms with 6.2.2.1"},"UNE6222":{"type":"boolean","description":"Conforms with 6.2.2.2"},"UNE6223":{"type":"boolean","description":"Conforms with 6.2.2.3"},"UNE6224":{"type":"boolean","description":"Conforms with 6.2.2.4"},"UNE623":{"type":"boolean","description":"Conforms with 6.2.3"},"UNE624":{"type":"boolean","description":"Conforms with 6.2.4"},"UNE64":{"type":"boolean","description":"Conforms with section 6.4"},"UNE652":{"type":"boolean","description":"Conforms with 6.5.2"},"UNE653":{"type":"boolean","description":"Conforms with 6.5.3"},"UNE654":{"type":"boolean","description":"Conforms with 6.5.4"},"UNE656":{"type":"boolean","description":"Conforms with 6.5.6"},"UNE7":{"type":"boolean","description":"Conforms with section 7"},"UNE711":{"type":"boolean","description":"Conforms with 7.1.1"},"UNE712":{"type":"boolean","description":"Conforms with 7.1.2"},"UNE713":{"type":"boolean","description":"Conforms with 7.1.3"},"UNE714":{"type":"boolean","description":"Conforms with 7.1.4"},"UNE715":{"type":"boolean","description":"Conforms with 7.1.5"},"UNE721":{"type":"boolean","description":"Conforms with 7.2.1"},"UNE722":{"type":"boolean","description":"Conforms with 7.2.2"},"UNE723":{"type":"boolean","description":"Conforms with 7.2.3"},"UNE73":{"type":"boolean","description":"Conforms with section 7.3"},"UNE9":{"type":"boolean","description":"Conforms with section 9"},"UNE9111":{"type":"boolean","description":"Conforms with 9.1.1.1"},"UNE9121":{"type":"boolean","description":"Conforms with 9.1.2.1"},"UNE9122":{"type":"boolean","description":"Conforms with 9.1.2.2"},"UNE9123":{"type":"boolean","description":"Conforms with 9.1.2.3"},"UNE9125":{"type":"boolean","description":"Conforms with 9.1.2.5"},"UNE9131":{"type":"boolean","description":"Conforms with 9.1.3.1"},"UNE9132":{"type":"boolean","description":"Conforms with 9.1.3.2"},"UNE9133":{"type":"boolean","description":"Conforms with 9.1.3.3"},"UNE9134":{"type":"boolean","description":"Conforms with 9.1.3.4"},"UNE9135":{"type":"boolean","description":"Conforms with 9.1.3.5"},"UNE9141":{"type":"boolean","description":"Conforms with 9.1.4.1"},"UNE9142":{"type":"boolean","description":"Conforms with 9.1.4.2"},"UNE9143":{"type":"boolean","description":"Conforms with 9.1.4.3"},"UNE9144":{"type":"boolean","description":"Conforms with 9.1.4.4"},"UNE9145":{"type":"boolean","description":"Conforms with 9.1.4.5"},"UNE91410":{"type":"boolean","description":"Conforms with 9.1.4.10"},"UNE91411":{"type":"boolean","description":"Conforms with 9.1.4.11"},"UNE91412":{"type":"boolean","description":"Conforms with 9.1.4.12"},"UNE91413":{"type":"boolean","description":"Conforms with 9.1.4.13"},"UNE9211":{"type":"boolean","description":"Conforms with 9.2.1.1"},"UNE9212":{"type":"boolean","description":"Conforms with 9.2.1.2"},"UNE9214":{"type":"boolean","description":"Conforms with 9.2.1.4"},"UNE9221":{"type":"boolean","description":"Conforms with 9.2.2.1"},"UNE9222":{"type":"boolean","description":"Conforms with 9.2.2.2"},"UNE9231":{"type":"boolean","description":"Conforms with 9.2.3.1"},"UNE9241":{"type":"boolean","description":"Conforms with 9.2.4.1"},"UNE9242":{"type":"boolean","description":"Conforms with 9.2.4.2"},"UNE9243":{"type":"boolean","description":"Conforms with 9.2.4.3"},"UNE9244":{"type":"boolean","description":"Conforms with 9.2.4.4"},"UNE9245":{"type":"boolean","description":"Conforms with 9.2.4.5"},"UNE9246":{"type":"boolean","description":"Conforms with 9.2.4.6"},"UNE9247":{"type":"boolean","description":"Conforms with 9.2.4.7"},"UNE9251":{"type":"boolean","description":"Conforms with 9.2.5.1"},"UNE9252":{"type":"boolean","description":"Conforms with 9.2.5.2"},"UNE9253":{"type":"boolean","description":"Conforms with 9.2.5.3"},"UNE9254":{"type":"boolean","description":"Conforms with 9.2.5.4"},"UNE9311":{"type":"boolean","description":"Conforms with 9.3.1.1"},"UNE9312":{"type":"boolean","description":"Conforms with 9.3.1.2"},"UNE9321":{"type":"boolean","description":"Conforms with 9.3.2.1"},"UNE9322":{"type":"boolean","description":"Conforms with 9.3.2.2"},"UNE9323":{"type":"boolean","description":"Conforms with 9.3.2.3"},"UNE9324":{"type":"boolean","description":"Conforms with 9.3.2.4"},"UNE9331":{"type":"boolean","description":"Conforms with 9.3.3.1"}},"description":"Form data for this document type. All fields are optional — missing fields render as empty sections, but providing them produces a complete document. All strings are HTML-escaped server-side (5000-character cap); arrays and nested objects are sanitized recursively. Malformed payloads that a template cannot process return HTTP 400 with the offending field in the message."}},"examples":{"default":{"summary":"accessibility-statement example","value":{"locale":"es","formData":{"web":"https://example.com","denominacion":"Example","emailContacto":"accessibility@example.com","telefonoContacto":"+34 600 000 000","notAccessible":false,"UNE5":true,"UNE9111":true,"UNE9142":true}}}}}}}},"responses":{"200":{"description":"Document generated successfully","headers":{"X-RateLimit-Limit":{"description":"Maximum requests allowed per minute (30)","schema":{"type":"string","example":"30"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window","schema":{"type":"string","example":"29"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateResponse"}}}},"400":{"description":"Bad request — missing formData, malformed JSON, or formData does not satisfy the template contract (the reason names the failing field)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"405":{"description":"Method not allowed — only POST is accepted"},"429":{"description":"Too many requests — 30 req/min/IP exceeded. Check Retry-After (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying","schema":{"type":"string","example":"37"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/generate/confidentiality-notice":{"post":{"operationId":"generateConfidentialityNotice","summary":"Generate a confidentiality notice","description":"Generates a short confidentiality notice for emails/documents as an HTML string.\n\n**Rate limit:** 30 requests/minute/IP (shared across all generate endpoints).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["formData"],"properties":{"locale":{"type":"string","enum":["es","en","ca","eu","gl","fr","de"],"default":"es","description":"Language of the generated document. Unknown values fall back to Spanish (es)."},"format":{"type":"string","enum":["html","markdown"],"default":"html","description":"Output format of `content`. `html` (default) returns the sanitized document HTML; `markdown` returns the document converted to Markdown (via turndown)."},"formData":{"type":"object","additionalProperties":true,"properties":{"arcoRights":{"type":"string","description":"Email or web link where ARCO rights (access, rectification, cancellation, opposition) can be exercised"}},"description":"Form data for this document type. All fields are optional — missing fields render as empty sections, but providing them produces a complete document. All strings are HTML-escaped server-side (5000-character cap); arrays and nested objects are sanitized recursively. Malformed payloads that a template cannot process return HTTP 400 with the offending field in the message."}},"examples":{"default":{"summary":"confidentiality-notice example","value":{"locale":"es","formData":{"arcoRights":"privacy@example.com"}}}}}}}},"responses":{"200":{"description":"Document generated successfully","headers":{"X-RateLimit-Limit":{"description":"Maximum requests allowed per minute (30)","schema":{"type":"string","example":"30"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window","schema":{"type":"string","example":"29"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateResponse"}}}},"400":{"description":"Bad request — missing formData, malformed JSON, or formData does not satisfy the template contract (the reason names the failing field)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"405":{"description":"Method not allowed — only POST is accepted"},"429":{"description":"Too many requests — 30 req/min/IP exceeded. Check Retry-After (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying","schema":{"type":"string","example":"37"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/generate/general-terms-and-conditions-of-sale":{"post":{"operationId":"generateGeneralTermsAndConditionsOfSale","summary":"Generate general terms and conditions of sale","description":"Generates e-commerce general terms and conditions of sale as an HTML string. Uses a nested `data` object for the questionnaire booleans (values are the strings 'Sí' or 'No').\n\n**Rate limit:** 30 requests/minute/IP (shared across all generate endpoints).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["formData"],"properties":{"locale":{"type":"string","enum":["es","en","ca","eu","gl","fr","de"],"default":"es","description":"Language of the generated document. Unknown values fall back to Spanish (es)."},"format":{"type":"string","enum":["html","markdown"],"default":"html","description":"Output format of `content`. `html` (default) returns the sanitized document HTML; `markdown` returns the document converted to Markdown (via turndown)."},"formData":{"type":"object","additionalProperties":true,"properties":{"web":{"type":"string","description":"Website URL"},"denominacion":{"type":"string","description":"Company display name"},"data":{"type":"object","description":"Questionnaire: claseTitular ('persona'|'empresa'), audienciaEspana, emailConfirmacion, preciosGastosEnvio — each 'Sí' or 'No'"},"nombrePersona":{"type":"string","description":"Owner name (when data.claseTitular is 'persona')"},"nifPersona":{"type":"string","description":"Owner NIF (when data.claseTitular is 'persona')"},"nombreEmpresa":{"type":"string","description":"Company name (when data.claseTitular is 'empresa')"},"nifEmpresa":{"type":"string","description":"Company NIF (when data.claseTitular is 'empresa')"},"registroEmpresa":{"type":"string","description":"Commercial registry"},"datosRegistroEmpresa":{"type":"string","description":"Registry details"},"direccionTitular":{"type":"string","description":"Owner address"},"telefonoTitular":{"type":"string","description":"Owner phone (optional)"},"faxTitular":{"type":"string","description":"Owner fax (optional)"},"emailTitular":{"type":"string","description":"Owner contact email"},"descripcionActividad":{"type":"string","description":"Description of the commercial activity"},"registroObligatorio":{"type":"string","description":"'Sí' or 'No' — whether activity requires a special registry"},"botonCompra":{"type":"string","description":"'Sí' or 'No' — site has a purchase button"},"administracionUsuario":{"type":"string","description":"Who manages the store (company/individual)"},"fabricacionProducto":{"type":"string","description":"'Sí' or 'No' — products are self-manufactured"},"preciosGastosEnvio":{"type":"string","description":"'Sí' or 'No' — shipping costs shown in the purchase process"},"empresaReparto":{"type":"string","description":"Shipping company used"},"metodosPago":{"type":"string","description":"Payment methods accepted"},"tarjetaRegalo":{"type":"string","description":"'Sí' or 'No' — gift cards sold"},"hasSSL":{"type":"boolean","description":"Site uses SSL/HTTPS"},"territorioEnvio":{"type":"string","description":"Shipping territories offered"},"direccionDevolucion":{"type":"string","description":"Return address"},"formularioDesistimiento":{"type":"string","description":"'Sí' or 'No' — withdrawal form provided"},"servicioPostventa":{"type":"string","description":"After-sales service description"},"enlaceServicioPostventa":{"type":"string","description":"After-sales service link"}},"description":"Form data for this document type. All fields are optional — missing fields render as empty sections, but providing them produces a complete document. All strings are HTML-escaped server-side (5000-character cap); arrays and nested objects are sanitized recursively. Malformed payloads that a template cannot process return HTTP 400 with the offending field in the message."}},"examples":{"default":{"summary":"general-terms-and-conditions-of-sale example","value":{"locale":"es","formData":{"web":"https://shop.example.com","denominacion":"Example Shop S.L.","data":{"claseTitular":"empresa","audienciaEspana":"Sí","emailConfirmacion":"Sí","preciosGastosEnvio":"Sí"},"nombreEmpresa":"Example Shop S.L.","nifEmpresa":"B12345678","registroEmpresa":"Registro Mercantil de Madrid","datosRegistroEmpresa":"Tomo 123, Folio 45, Hoja M-67890","direccionTitular":"Calle Falsa 123, Madrid","emailTitular":"shop@example.com","descripcionActividad":"Venta online de productos de electrónica","registroObligatorio":"No","botonCompra":"Sí","administracionUsuario":"Example Shop S.L.","fabricacionProducto":"No","preciosGastosEnvio":"Sí","empresaReparto":"Correos Express","metodosPago":"Tarjeta, PayPal, Bizum","tarjetaRegalo":"No","hasSSL":true,"territorioEnvio":"España peninsular y Baleares","direccionDevolucion":"Calle Falsa 123, Madrid","formularioDesistimiento":"Sí","servicioPostventa":"Soporte por email en 48h","enlaceServicioPostventa":"https://shop.example.com/soporte"}}}}}}}},"responses":{"200":{"description":"Document generated successfully","headers":{"X-RateLimit-Limit":{"description":"Maximum requests allowed per minute (30)","schema":{"type":"string","example":"30"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window","schema":{"type":"string","example":"29"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateResponse"}}}},"400":{"description":"Bad request — missing formData, malformed JSON, or formData does not satisfy the template contract (the reason names the failing field)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"405":{"description":"Method not allowed — only POST is accepted"},"429":{"description":"Too many requests — 30 req/min/IP exceeded. Check Retry-After (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying","schema":{"type":"string","example":"37"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/generate-document":{"post":{"operationId":"generateDocumentLegacy","summary":"Generate a document (legacy aggregate endpoint)","description":"Backwards-compatible aggregate endpoint. Same behavior and rate limit as the per-type endpoints, but takes `documentType` in the request body. New integrations should prefer the per-type endpoints.\n\n**Request body:** `{ documentType, locale?, formData, format? }` — documentType is one of the six types listed in this spec.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["formData"],"properties":{"locale":{"type":"string","enum":["es","en","ca","eu","gl","fr","de"],"default":"es","description":"Language of the generated document. Unknown values fall back to Spanish (es)."},"format":{"type":"string","enum":["html","markdown"],"default":"html","description":"Output format of `content`. `html` (default) returns the sanitized document HTML; `markdown` returns the document converted to Markdown (via turndown)."},"formData":{"type":"object","additionalProperties":true,"properties":{"web":{"type":"string","description":"Website URL shown as the site identity"},"denominacion":{"type":"string","description":"Owner or company display name"},"data":{"type":"object","description":"Questionnaire: claseTitular ('persona'|'empresa'), accesoLibre, registroLibre, interaccionLibre, mayorEdad, paisParticular, enlacesTerceros, enlacesAfiliacion — each 'Sí' or 'No'"},"nombrePersona":{"type":"string","description":"Owner name (when data.claseTitular is 'persona')"},"nifPersona":{"type":"string","description":"Owner NIF (when data.claseTitular is 'persona')"},"nombreEmpresa":{"type":"string","description":"Company name (when data.claseTitular is 'empresa')"},"nifEmpresa":{"type":"string","description":"Company NIF (when data.claseTitular is 'empresa')"},"registroEmpresa":{"type":"string","description":"Commercial registry"},"datosRegistroEmpresa":{"type":"string","description":"Registry details"},"representanteEmpresa":{"type":"string","description":"Company representative"},"direccionTitular":{"type":"string","description":"Owner address"},"telefonoTitular":{"type":"string","description":"Owner phone (optional)"},"faxTitular":{"type":"string","description":"Owner fax (optional)"},"emailTitular":{"type":"string","description":"Owner contact email"},"pais":{"type":"string","description":"Country"}},"description":"Form data for this document type. All fields are optional — missing fields render as empty sections, but providing them produces a complete document. All strings are HTML-escaped server-side (5000-character cap); arrays and nested objects are sanitized recursively. Malformed payloads that a template cannot process return HTTP 400 with the offending field in the message."}},"examples":{"default":{"summary":"legal-notice example","value":{"locale":"es","formData":{"web":"example.com","denominacion":"Example S.L.","data":{"claseTitular":"empresa","accesoLibre":"Sí","registroLibre":"No","interaccionLibre":"No","mayorEdad":"No","paisParticular":"No","enlacesTerceros":"No","enlacesAfiliacion":"No"},"nombreEmpresa":"Example S.L.","nifEmpresa":"B12345678","registroEmpresa":"Registro Mercantil de Madrid","datosRegistroEmpresa":"Tomo 123, Folio 45, Hoja M-67890","representanteEmpresa":"Jane Doe","direccionTitular":"Calle Falsa 123, Madrid","emailTitular":"legal@example.com","pais":"España"}}}}}}}},"responses":{"200":{"description":"Document generated successfully","headers":{"X-RateLimit-Limit":{"description":"Maximum requests allowed per minute (30)","schema":{"type":"string","example":"30"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current window","schema":{"type":"string","example":"29"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateResponse"}}}},"400":{"description":"Bad request — missing formData, malformed JSON, or formData does not satisfy the template contract (the reason names the failing field)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"405":{"description":"Method not allowed — only POST is accepted"},"429":{"description":"Too many requests — 30 req/min/IP exceeded. Check Retry-After (seconds).","headers":{"Retry-After":{"description":"Seconds to wait before retrying","schema":{"type":"string","example":"37"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"tags":["legacy"]}}},"components":{"schemas":{"GenerateResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"true when the document was generated","example":true},"content":{"type":"string","description":"Generated document. HTML (default) or Markdown when the request sets format: markdown — see content.format.","example":"<div><h2>AVISO LEGAL...</h2>...</div>"},"format":{"type":"string","enum":["html","markdown"],"description":"Format of the returned content","example":"html"},"locale":{"type":"string","description":"Locale actually used for generation","example":"es"},"documentType":{"type":"string","description":"Document type that was generated","example":"legal-notice"}}},"ErrorResponse":{"type":"object","properties":{"statusCode":{"type":"number","example":400},"statusMessage":{"type":"string","example":"formData does not match the required fields for legal-notice: ..."},"message":{"type":"string","example":"formData does not match the required fields for legal-notice: ..."}}}}}}