{
  "openapi": "3.1.0",
  "info": {
    "title": "Documentemplate API",
    "version": "1.0.0",
    "summary": "Generate documents in bulk from a spreadsheet and a template.",
    "description": "Send a spreadsheet (.xlsx or .csv) and the id of a published template. The API queues a batch, generates one PDF per row and returns a ZIP.\n\nThe call is asynchronous: POST /v1/batch answers 202 at once with the batch id; poll GET /v1/batch/{id}?wait=25 (it holds up to 25 s until the state changes) until `state` is `done` or `failed`, then download GET /v1/batch/{id}/result.\n\nQuotas of the plan (rows per batch, stored documents, monthly PDFs) are checked before anything is stored. Bulk generation is part of the Pro plan.\n\nHuman-readable docs: https://documentemplate.com/docs/api · Markdown: https://documentemplate.com/docs/api.md",
    "contact": { "name": "Documentemplate", "url": "https://documentemplate.com" }
  },
  "servers": [{ "url": "https://documentemplate.com/api" }],
  "security": [{ "bearerToken": [] }],
  "paths": {
    "/v1/batch": {
      "post": {
        "operationId": "createBatch",
        "summary": "Queue a batch",
        "description": "Reads the sheet, checks the plan and quotas, and queues a batch that generates one document per row. Answers 202 immediately.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["templateId", "file"],
                "properties": {
                  "templateId": { "type": "string", "description": "Id of the template. It must be published." },
                  "file": { "type": "string", "format": "binary", "description": "The spreadsheet: .xlsx or .csv, up to 10 MB. First row = column titles; each following row = one document." },
                  "mapping": { "type": "string", "description": "JSON object {\"field\": column}. A column is given by its title or its position (from 0). Without it, columns are matched to fields by title.", "examples": ["{\"customer\":\"Customer name\",\"amount\":\"Total\"}"] },
                  "nameColumn": { "type": "string", "description": "Column (title or position) that names each file." },
                  "output": { "type": "string", "enum": ["FILES", "MERGED"], "default": "FILES", "description": "FILES = one PDF per document; MERGED = a single PDF with all of them." },
                  "format": { "type": "string", "enum": ["PDF"], "default": "PDF", "description": "Download format. PDF is currently the only one." }
                }
              },
              "encoding": { "file": { "contentType": "text/csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" } }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Batch accepted and queued.",
            "headers": {
              "Location": { "description": "Where to ask for the batch state.", "schema": { "type": "string" } },
              "Retry-After": { "description": "Seconds to wait before asking again.", "schema": { "type": "integer" } }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Batch" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/v1/batch/{id}": {
      "get": {
        "operationId": "getBatch",
        "summary": "Get a batch (optionally waiting for a change)",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "wait", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0, "maximum": 25, "default": 0 }, "description": "Seconds to hold the request until the batch finishes (max 25). Recommended way to wait." }
        ],
        "responses": {
          "200": {
            "description": "The batch, with per-row results. `Retry-After` is present while it has not finished.",
            "headers": { "Retry-After": { "schema": { "type": "integer" } } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Batch" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/batch/{id}/result": {
      "get": {
        "operationId": "downloadBatchResult",
        "summary": "Download the ZIP of a finished batch",
        "description": "Only available when `state` is `done` (kept for 7 days). The ZIP holds the generated documents (or one merged PDF) and `informe-del-lote.csv`, a report of what happened in each row.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "The ZIP.", "content": { "application/zip": { "schema": { "type": "string", "format": "binary" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerToken": { "type": "http", "scheme": "bearer", "description": "API token (dtk_api_…) generated in the app, in the API section. Shown once; regenerating it invalidates the previous one." }
    },
    "schemas": {
      "Batch": {
        "type": "object",
        "required": ["id", "state", "templateId", "templateName", "total", "processed", "generated", "withWarnings", "failed", "createdAt", "warnings", "statusUrl"],
        "properties": {
          "id": { "type": "string" },
          "state": { "type": "string", "enum": ["queued", "running", "done", "failed"] },
          "templateId": { "type": "string" },
          "templateName": { "type": "string" },
          "total": { "type": "integer", "description": "Rows (documents) in the sheet." },
          "processed": { "type": "integer" },
          "generated": { "type": "integer" },
          "withWarnings": { "type": "integer" },
          "failed": { "type": "integer" },
          "queuePosition": { "type": "integer", "description": "1 = next. Only while `queued`." },
          "createdAt": { "type": "string", "format": "date-time" },
          "finishedAt": { "type": "string", "format": "date-time" },
          "expiresAt": { "type": "string", "format": "date-time", "description": "When the result is deleted (7 days after finishing)." },
          "resultSize": { "type": "integer", "description": "Bytes of the ZIP. Only when `done`." },
          "error": { "type": "string", "description": "Why the whole batch failed. Only when `failed`." },
          "warnings": { "type": "array", "items": { "type": "string" } },
          "results": { "type": "array", "items": { "$ref": "#/components/schemas/RowResult" }, "description": "Only in the detail (GET), not when the batch is created." },
          "statusUrl": { "type": "string" },
          "resultUrl": { "type": "string", "description": "Only when `done`." },
          "pollAfterSeconds": { "type": "integer", "description": "Only while the batch has not finished." }
        }
      },
      "RowResult": {
        "type": "object",
        "required": ["row", "file", "status", "messages"],
        "properties": {
          "row": { "type": "integer", "description": "The row as seen in Excel (the header is row 1)." },
          "file": { "type": "string", "description": "Name of the generated file; empty if the row was not generated." },
          "status": { "type": "string", "enum": ["ok", "warning", "error"] },
          "messages": { "type": "array", "items": { "type": "string" } }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "statusCode": { "type": "integer" },
          "message": { "oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] },
          "error": { "type": "string" }
        }
      }
    },
    "responses": {
      "BadRequest": { "description": "Malformed request: missing templateId or file, unreadable sheet, template not published, unknown field or column in `mapping`, invalid `format`/`output`, or the batch is not finished yet.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "The token is missing, wrong, regenerated or revoked, or the account is no longer valid.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Forbidden": { "description": "The plan does not allow bulk generation, or the batch does not fit the quota (rows per batch, stored documents, monthly PDFs). The message says which. Nothing is created.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "The batch does not exist or belongs to another account.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "TooManyRequests": { "description": "Too many requests. Wait a moment and retry.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    }
  }
}
