{
  "openapi": "3.1.0",
  "info": {
    "title": "Libris automation API",
    "version": "1",
    "summary": "Send books to a Libris server and get them back translated, with no human step.",
    "description": "Send an EPUB, TXT chapters or a JSON document to a Libris server, let it translate on its own, then\ndownload the result with its completion report. Nothing needs a person along the way.\n\n**Authentication.** Every call carries an API token: `Authorization: Bearer lbr_…`. Tokens are created\nin the interface (**My account › API tokens**); each one carries scopes, named on every operation below.\nThe session cookie of the web interface never opens `/api/v1`.\n\n**Asynchronous work.** A request is saved before the `202 Accepted` answer and runs in the worker.\nPoll its status (`?wait=` long-polls up to 60 seconds by default) or receive a signed webhook when it\nends. A request always ends: `completed`, `completed_with_residuals`, `failed` or `cancelled`\n(`imported` when it only imported chapters).\n\n**Errors.** Every error is `{\"detail\": {\"code\", \"message\", …}}`. `code` is stable; `message` is in\nFrench, or in English with `Accept-Language: en`. Validation errors never echo the submitted text.\n\n**Limits.** Calls per token and per minute (`API_RATE_LIMIT_PER_MINUTE`, `429` with `Retry-After`), body\nsize (`API_MAX_PAYLOAD_MB`) and chapters per request (`API_MAX_CHAPTERS`) are set by the server's\nadministrator.\n\nThe full guide, with `curl` examples, is [docs/api.md](https://github.com/HeartBtz/Libris/blob/main/docs/api.md).\n",
    "license": {
      "name": "AGPL-3.0-only",
      "identifier": "AGPL-3.0-only"
    }
  },
  "externalDocs": {
    "description": "Automation API guide",
    "url": "https://github.com/HeartBtz/Libris/blob/main/docs/api.md"
  },
  "servers": [
    {
      "url": "{server}",
      "description": "Your Libris server.",
      "variables": {
        "server": {
          "default": "https://libris.example.org"
        }
      }
    }
  ],
  "tags": [
    {
      "name": "Translation requests",
      "description": "Send content, follow it, pause, resume or cancel it."
    },
    {
      "name": "Results",
      "description": "Download the translated book, its chapters or its JSON document."
    },
    {
      "name": "Series",
      "description": "The series of the token's owner and their volumes."
    },
    {
      "name": "Providers",
      "description": "The model providers a request may name."
    },
    {
      "name": "Glossaries",
      "description": "Shared glossaries: the terminology of a universe several of your series follow."
    }
  ],
  "paths": {
    "/api/v1/glossaries": {
      "get": {
        "operationId": "listSharedGlossaries",
        "tags": [
          "Glossaries"
        ],
        "summary": "List your shared glossaries",
        "description": "Your shared glossaries, sorted by name, with their term counts and the series that follow them.\n\n**Scope:** `series:read`.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SharedGlossary"
                  }
                }
              }
            }
          },
          "401": {
            "description": "A missing, unknown, revoked or expired token, or a disabled account (header `WWW-Authenticate: Bearer`). Codes: `missing_token`, `invalid_token`, `revoked_token`, `expired_token`, `inactive_account`, `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_token": {
                    "value": {
                      "detail": {
                        "code": "missing_token",
                        "message": "Jeton d’API manquant : envoyez « Authorization: Bearer <jeton> »."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer realm=\"libris\"`",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the scope (`scope` names it), or a browser page from another site. Codes: `insufficient_scope`, `forbidden`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_scope": {
                    "value": {
                      "detail": {
                        "code": "insufficient_scope",
                        "message": "Ce jeton n’a pas la permission « jobs:read »."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many calls for this token; retry after `Retry-After` seconds. Codes: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "detail": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes pour ce jeton : réessayez plus tard."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure; the message carries a diagnostic reference for the server logs. Codes: `server_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerToken": []
          }
        ],
        "x-libris-scopes": [
          "series:read"
        ]
      },
      "post": {
        "operationId": "createSharedGlossary",
        "tags": [
          "Glossaries"
        ],
        "summary": "Create a shared glossary",
        "description": "An empty shared glossary; fill it with an import. Languages are optional: with them, it only applies to volumes of the same pair.\n\n**Scope:** `content:write`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SharedGlossaryInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SharedGlossaryDetail"
                }
              }
            }
          },
          "401": {
            "description": "A missing, unknown, revoked or expired token, or a disabled account (header `WWW-Authenticate: Bearer`). Codes: `missing_token`, `invalid_token`, `revoked_token`, `expired_token`, `inactive_account`, `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_token": {
                    "value": {
                      "detail": {
                        "code": "missing_token",
                        "message": "Jeton d’API manquant : envoyez « Authorization: Bearer <jeton> »."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer realm=\"libris\"`",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the scope (`scope` names it), or a browser page from another site. Codes: `insufficient_scope`, `forbidden`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_scope": {
                    "value": {
                      "detail": {
                        "code": "insufficient_scope",
                        "message": "Ce jeton n’a pas la permission « jobs:read »."
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "You already have a shared glossary of that name. Codes: `glossary_exists`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A bad body, or a blank name. Codes: `invalid_request`, `invalid_name`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "detail": {
                        "code": "invalid_request",
                        "message": "Requête invalide."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many calls for this token; retry after `Retry-After` seconds. Codes: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "detail": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes pour ce jeton : réessayez plus tard."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure; the message carries a diagnostic reference for the server logs. Codes: `server_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerToken": []
          }
        ],
        "x-libris-scopes": [
          "content:write"
        ]
      }
    },
    "/api/v1/glossaries/{glossary_id}": {
      "get": {
        "operationId": "getSharedGlossary",
        "tags": [
          "Glossaries"
        ],
        "summary": "Get a shared glossary and its terms",
        "description": "**Scope:** `series:read`.",
        "parameters": [
          {
            "name": "glossary_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The shared glossary's `id`."
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SharedGlossaryDetail"
                }
              }
            }
          },
          "401": {
            "description": "A missing, unknown, revoked or expired token, or a disabled account (header `WWW-Authenticate: Bearer`). Codes: `missing_token`, `invalid_token`, `revoked_token`, `expired_token`, `inactive_account`, `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_token": {
                    "value": {
                      "detail": {
                        "code": "missing_token",
                        "message": "Jeton d’API manquant : envoyez « Authorization: Bearer <jeton> »."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer realm=\"libris\"`",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the scope (`scope` names it), or a browser page from another site. Codes: `insufficient_scope`, `forbidden`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_scope": {
                    "value": {
                      "detail": {
                        "code": "insufficient_scope",
                        "message": "Ce jeton n’a pas la permission « jobs:read »."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown, or owned by someone else. Codes: `glossary_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "glossary_not_found": {
                    "value": {
                      "detail": {
                        "code": "glossary_not_found",
                        "message": "Glossaire partagé introuvable."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many calls for this token; retry after `Retry-After` seconds. Codes: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "detail": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes pour ce jeton : réessayez plus tard."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure; the message carries a diagnostic reference for the server logs. Codes: `server_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerToken": []
          }
        ],
        "x-libris-scopes": [
          "series:read"
        ]
      }
    },
    "/api/v1/glossaries/{glossary_id}/export/{format}": {
      "get": {
        "operationId": "exportSharedGlossary",
        "tags": [
          "Glossaries"
        ],
        "summary": "Download a shared glossary",
        "description": "Its terms as JSON, CSV (for spreadsheets: `delimiter=semicolon&bom=true`) or TBX, with the fields `source`, `translation`, `category`, `description`, `locked`, `accepted`.\n\n**Scope:** `series:read`.",
        "parameters": [
          {
            "name": "glossary_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The shared glossary's `id`."
          },
          {
            "name": "format",
            "in": "path",
            "required": true,
            "schema": {
              "enum": [
                "json",
                "csv",
                "tbx"
              ],
              "type": "string"
            },
            "description": "`json`, `csv` or `tbx`."
          },
          {
            "name": "delimiter",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "comma",
                "semicolon",
                "tab"
              ],
              "type": "string",
              "default": "comma"
            },
            "description": "CSV separator."
          },
          {
            "name": "bom",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Start a CSV file with a UTF-8 byte order mark (for Excel)."
          }
        ],
        "responses": {
          "200": {
            "description": "The file, with a `Content-Disposition` file name.",
            "headers": {
              "Content-Disposition": {
                "description": "File name.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              },
              "application/x-tbx+xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "A missing, unknown, revoked or expired token, or a disabled account (header `WWW-Authenticate: Bearer`). Codes: `missing_token`, `invalid_token`, `revoked_token`, `expired_token`, `inactive_account`, `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_token": {
                    "value": {
                      "detail": {
                        "code": "missing_token",
                        "message": "Jeton d’API manquant : envoyez « Authorization: Bearer <jeton> »."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer realm=\"libris\"`",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the scope (`scope` names it), or a browser page from another site. Codes: `insufficient_scope`, `forbidden`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_scope": {
                    "value": {
                      "detail": {
                        "code": "insufficient_scope",
                        "message": "Ce jeton n’a pas la permission « jobs:read »."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown, or owned by someone else. Codes: `glossary_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "glossary_not_found": {
                    "value": {
                      "detail": {
                        "code": "glossary_not_found",
                        "message": "Glossaire partagé introuvable."
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "A bad parameter. Codes: `invalid_request`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "detail": {
                        "code": "invalid_request",
                        "message": "Requête invalide."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many calls for this token; retry after `Retry-After` seconds. Codes: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "detail": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes pour ce jeton : réessayez plus tard."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure; the message carries a diagnostic reference for the server logs. Codes: `server_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerToken": []
          }
        ],
        "x-libris-scopes": [
          "series:read"
        ]
      }
    },
    "/api/v1/glossaries/{glossary_id}/import": {
      "post": {
        "operationId": "importSharedGlossary",
        "tags": [
          "Glossaries"
        ],
        "summary": "Import terms into a shared glossary",
        "description": "Reads a JSON, CSV or TBX file into the glossary and answers the import report. With `dry_run=true`, nothing changes: the report is a preview that lists invalid rows instead of refusing the file. Sources are matched case-insensitively.\n\n**Scope:** `content:write`.",
        "parameters": [
          {
            "name": "glossary_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The shared glossary's `id`."
          },
          {
            "name": "dry_run",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Preview the import without changing anything."
          }
        ],
        "requestBody": {
          "description": "The glossary file and its import options.",
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/GlossaryImportForm"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GlossaryImportReport"
                }
              }
            }
          },
          "401": {
            "description": "A missing, unknown, revoked or expired token, or a disabled account (header `WWW-Authenticate: Bearer`). Codes: `missing_token`, `invalid_token`, `revoked_token`, `expired_token`, `inactive_account`, `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_token": {
                    "value": {
                      "detail": {
                        "code": "missing_token",
                        "message": "Jeton d’API manquant : envoyez « Authorization: Bearer <jeton> »."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer realm=\"libris\"`",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the scope (`scope` names it), or a browser page from another site. Codes: `insufficient_scope`, `forbidden`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_scope": {
                    "value": {
                      "detail": {
                        "code": "insufficient_scope",
                        "message": "Ce jeton n’a pas la permission « jobs:read »."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown, or owned by someone else. Codes: `glossary_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "glossary_not_found": {
                    "value": {
                      "detail": {
                        "code": "glossary_not_found",
                        "message": "Glossaire partagé introuvable."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The file is above 2 MB. Codes: `glossary_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The file cannot be read, has invalid rows (without `skip_invalid`), or an import option is invalid. Codes: `invalid_glossary`, `invalid_strategy`, `invalid_mapping`, `invalid_request`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "detail": {
                        "code": "invalid_request",
                        "message": "Requête invalide."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many calls for this token; retry after `Retry-After` seconds. Codes: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "detail": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes pour ce jeton : réessayez plus tard."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure; the message carries a diagnostic reference for the server logs. Codes: `server_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerToken": []
          }
        ],
        "x-libris-scopes": [
          "content:write"
        ]
      }
    },
    "/api/v1/providers": {
      "get": {
        "operationId": "listProviders",
        "tags": [
          "Providers"
        ],
        "summary": "List the providers",
        "description": "The providers a request may name in `provider_id`, sorted by name: identity and model only, never an address or a key.\n\n**Scope:** `content:write`.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Provider"
                  }
                }
              }
            }
          },
          "401": {
            "description": "A missing, unknown, revoked or expired token, or a disabled account (header `WWW-Authenticate: Bearer`). Codes: `missing_token`, `invalid_token`, `revoked_token`, `expired_token`, `inactive_account`, `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_token": {
                    "value": {
                      "detail": {
                        "code": "missing_token",
                        "message": "Jeton d’API manquant : envoyez « Authorization: Bearer <jeton> »."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer realm=\"libris\"`",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the scope (`scope` names it), or a browser page from another site. Codes: `insufficient_scope`, `forbidden`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_scope": {
                    "value": {
                      "detail": {
                        "code": "insufficient_scope",
                        "message": "Ce jeton n’a pas la permission « jobs:read »."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many calls for this token; retry after `Retry-After` seconds. Codes: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "detail": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes pour ce jeton : réessayez plus tard."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure; the message carries a diagnostic reference for the server logs. Codes: `server_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerToken": []
          }
        ],
        "x-libris-scopes": [
          "content:write"
        ]
      }
    },
    "/api/v1/series": {
      "get": {
        "operationId": "listSeries",
        "tags": [
          "Series"
        ],
        "summary": "List your series",
        "description": "The series the token's owner owns, sorted by name.\n\n**Scope:** `series:read`.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SeriesSummary"
                  }
                }
              }
            }
          },
          "401": {
            "description": "A missing, unknown, revoked or expired token, or a disabled account (header `WWW-Authenticate: Bearer`). Codes: `missing_token`, `invalid_token`, `revoked_token`, `expired_token`, `inactive_account`, `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_token": {
                    "value": {
                      "detail": {
                        "code": "missing_token",
                        "message": "Jeton d’API manquant : envoyez « Authorization: Bearer <jeton> »."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer realm=\"libris\"`",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the scope (`scope` names it), or a browser page from another site. Codes: `insufficient_scope`, `forbidden`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_scope": {
                    "value": {
                      "detail": {
                        "code": "insufficient_scope",
                        "message": "Ce jeton n’a pas la permission « jobs:read »."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many calls for this token; retry after `Retry-After` seconds. Codes: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "detail": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes pour ce jeton : réessayez plus tard."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure; the message carries a diagnostic reference for the server logs. Codes: `server_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerToken": []
          }
        ],
        "x-libris-scopes": [
          "series:read"
        ]
      }
    },
    "/api/v1/series/{series_id}": {
      "get": {
        "operationId": "getSeries",
        "tags": [
          "Series"
        ],
        "summary": "Get a series and its volumes",
        "description": "**Scope:** `series:read`.",
        "parameters": [
          {
            "name": "series_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The series' `id`."
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SeriesDetail"
                }
              }
            }
          },
          "401": {
            "description": "A missing, unknown, revoked or expired token, or a disabled account (header `WWW-Authenticate: Bearer`). Codes: `missing_token`, `invalid_token`, `revoked_token`, `expired_token`, `inactive_account`, `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_token": {
                    "value": {
                      "detail": {
                        "code": "missing_token",
                        "message": "Jeton d’API manquant : envoyez « Authorization: Bearer <jeton> »."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer realm=\"libris\"`",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the scope (`scope` names it), or a browser page from another site. Codes: `insufficient_scope`, `forbidden`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_scope": {
                    "value": {
                      "detail": {
                        "code": "insufficient_scope",
                        "message": "Ce jeton n’a pas la permission « jobs:read »."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown, or owned by someone else. Codes: `series_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "series_not_found": {
                    "value": {
                      "detail": {
                        "code": "series_not_found",
                        "message": "Série introuvable."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many calls for this token; retry after `Retry-After` seconds. Codes: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "detail": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes pour ce jeton : réessayez plus tard."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure; the message carries a diagnostic reference for the server logs. Codes: `server_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerToken": []
          }
        ],
        "x-libris-scopes": [
          "series:read"
        ]
      }
    },
    "/api/v1/series/{series_id}/shared-glossary": {
      "get": {
        "operationId": "getSeriesSharedGlossary",
        "tags": [
          "Glossaries"
        ],
        "summary": "Get the shared glossary a series follows",
        "description": "**Scope:** `series:read`.",
        "parameters": [
          {
            "name": "series_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The series' `id`."
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SeriesSharedGlossary"
                }
              }
            }
          },
          "401": {
            "description": "A missing, unknown, revoked or expired token, or a disabled account (header `WWW-Authenticate: Bearer`). Codes: `missing_token`, `invalid_token`, `revoked_token`, `expired_token`, `inactive_account`, `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_token": {
                    "value": {
                      "detail": {
                        "code": "missing_token",
                        "message": "Jeton d’API manquant : envoyez « Authorization: Bearer <jeton> »."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer realm=\"libris\"`",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the scope (`scope` names it), or a browser page from another site. Codes: `insufficient_scope`, `forbidden`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_scope": {
                    "value": {
                      "detail": {
                        "code": "insufficient_scope",
                        "message": "Ce jeton n’a pas la permission « jobs:read »."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown series, or owned by someone else. Codes: `series_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "series_not_found": {
                    "value": {
                      "detail": {
                        "code": "series_not_found",
                        "message": "Série introuvable."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many calls for this token; retry after `Retry-After` seconds. Codes: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "detail": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes pour ce jeton : réessayez plus tard."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure; the message carries a diagnostic reference for the server logs. Codes: `server_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerToken": []
          }
        ],
        "x-libris-scopes": [
          "series:read"
        ]
      },
      "put": {
        "operationId": "attachSeriesSharedGlossary",
        "tags": [
          "Glossaries"
        ],
        "summary": "Attach a series to a shared glossary",
        "description": "The series follows this shared glossary from its next passages on (at most one per series); `glossary_id: null` detaches it. A glossary with languages only fits a series of the same pair.\n\n**Scope:** `content:write`.",
        "parameters": [
          {
            "name": "series_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The series' `id`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SharedGlossaryAttachment"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SeriesSharedGlossary"
                }
              }
            }
          },
          "401": {
            "description": "A missing, unknown, revoked or expired token, or a disabled account (header `WWW-Authenticate: Bearer`). Codes: `missing_token`, `invalid_token`, `revoked_token`, `expired_token`, `inactive_account`, `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_token": {
                    "value": {
                      "detail": {
                        "code": "missing_token",
                        "message": "Jeton d’API manquant : envoyez « Authorization: Bearer <jeton> »."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer realm=\"libris\"`",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the scope (`scope` names it), or a browser page from another site. Codes: `insufficient_scope`, `forbidden`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_scope": {
                    "value": {
                      "detail": {
                        "code": "insufficient_scope",
                        "message": "Ce jeton n’a pas la permission « jobs:read »."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown series or glossary, or owned by someone else. Codes: `series_not_found`, `glossary_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "series_not_found": {
                    "value": {
                      "detail": {
                        "code": "series_not_found",
                        "message": "Série introuvable."
                      }
                    }
                  },
                  "glossary_not_found": {
                    "value": {
                      "detail": {
                        "code": "glossary_not_found",
                        "message": "Glossaire partagé introuvable."
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The glossary's languages differ from the series'. Codes: `language_mismatch`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A bad body. Codes: `invalid_request`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "detail": {
                        "code": "invalid_request",
                        "message": "Requête invalide."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many calls for this token; retry after `Retry-After` seconds. Codes: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "detail": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes pour ce jeton : réessayez plus tard."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure; the message carries a diagnostic reference for the server logs. Codes: `server_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerToken": []
          }
        ],
        "x-libris-scopes": [
          "content:write"
        ]
      }
    },
    "/api/v1/translation-requests": {
      "post": {
        "operationId": "createTranslationRequest",
        "tags": [
          "Translation requests"
        ],
        "summary": "Send a translation request",
        "description": "Send **an EPUB** (multipart field `file`, or the raw file as `application/epub+zip` with its options in the query string), **TXT or DOCX chapters** (multipart, one or more `.txt` or `.docx` files in `file` or `files`, one chapter each, or one file split at its chapter headings with `split=headings`) or **a JSON document**. One request carries one kind of file.\n\nThe request is stored before the answer and its pipeline starts in the worker: `202 Accepted` with a `Location` header. The same content sent again with the same `Idempotency-Key` or `external_id` answers `200` with the original request and `Idempotent-Replayed: true`.\n\nWithout the `pipeline:start` scope, send `start=false` to import only.\n\nChapters sent to a volume already translated (`volume.latest`, or the same volume again) are appended and only they are translated. The request waits its turn in the fair queue at the `priority` it asks for, and is refused when its token's cost budget is reached.\n\n**Scope:** `content:write`, and `pipeline:start` to start the pipeline.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "1–200 printable characters. Sending the same content again with the same key answers `200` with the original request; different content answers `409 idempotency_conflict`."
          },
          {
            "name": "filename",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). The file name, used to guess the volume number.",
            "schema": {
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "external_id",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). Your identifier of the request (same rules as in the JSON document).",
            "schema": {
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$",
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "series",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). The series by name, created when missing (not with `series_id`). Required for TXT.",
            "schema": {
              "maxLength": 500,
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "series_id",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). The series by id (not with `series`).",
            "schema": {
              "maxLength": 36,
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "volume",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). Volume number, 1–10000, or `latest` for the series' last volume (TXT only). Required for TXT; for an EPUB in a series, taken from the file name when free, otherwise the next number.",
            "schema": {
              "anyOf": [
                {
                  "maximum": 10000,
                  "minimum": 1,
                  "type": "integer"
                },
                {
                  "const": "latest",
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ]
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "volume_external_id",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). Your identifier of the volume.",
            "schema": {
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$",
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "title",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). Volume title (an EPUB keeps its own otherwise).",
            "schema": {
              "default": "",
              "maxLength": 500,
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "author",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). Author of the volume.",
            "schema": {
              "default": "",
              "maxLength": 500,
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "source_language",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). BCP 47 tag such as `en`, `fr-FR`, `zh-Hant`.",
            "schema": {
              "maxLength": 35,
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "target_language",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). BCP 47 tag.",
            "schema": {
              "maxLength": 35,
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "provider_id",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). Provider to use; defaults to the volume's, then the series' provider.",
            "schema": {
              "maxLength": 36,
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "quality",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`).",
            "schema": {
              "enum": [
                "fast",
                "normal",
                "high",
                "maximum"
              ],
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "context_backend",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`).",
            "schema": {
              "enum": [
                "internal",
                "openviking",
                "hybrid"
              ],
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). Run the whole pipeline (needs the `pipeline:start` scope); false only imports.",
            "schema": {
              "default": true,
              "type": "boolean"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "final_review",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). Run the final review (never when the server disables it).",
            "schema": {
              "default": true,
              "type": "boolean"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "output_format",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). Default format of the result; `epub` only for an EPUB (and its default); `epub-bilingual` is a bilingual EPUB for proofreading, for any input.",
            "schema": {
              "enum": [
                "json",
                "txt",
                "txt-zip",
                "epub",
                "epub-bilingual"
              ],
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "callback_url",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). Webhook called when the request ends; its host must be allowed by an administrator.",
            "schema": {
              "maxLength": 2000,
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "callback_events",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). Comma-separated extra webhooks, for example `chapters.translated`.",
            "schema": {
              "default": "",
              "maxLength": 200,
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "replace_changed_chapters",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). Replace chapters already imported with another text (otherwise `409 chapter_conflict`). Unchanged passages keep their translation.",
            "schema": {
              "default": false,
              "type": "boolean"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "discard_human",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). With `replace_changed_chapters`: allow dropping passages a person edited or validated (otherwise `409 conflict`).",
            "schema": {
              "default": false,
              "type": "boolean"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "priority",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). Place in the fair queue: `low`, `normal` (default) or `high`, within the token's `max_priority` and its account's ceiling (otherwise `403 priority_not_allowed`). Does not change what the request is.",
            "schema": {
              "enum": [
                "low",
                "normal",
                "high"
              ],
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "analysis_mode",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). `parallel`: the passages are analysed side by side, then each is reconciled with what precedes it; `strict`: one passage after the other. Default: the volume's choice, else `ANALYSIS_MODE`.",
            "schema": {
              "enum": [
                "parallel",
                "strict"
              ],
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "threads",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). Passages worked on at once, analysis and translation alike (1–64). It can only lower the volume's share of the provider's capacity; default: that share.",
            "schema": {
              "maximum": 64,
              "minimum": 1,
              "type": "integer"
            },
            "x-libris-raw-epub-only": true
          },
          {
            "name": "split",
            "in": "query",
            "required": false,
            "description": "Raw EPUB body only (`Content-Type: application/epub+zip`). TXT or DOCX: `headings` splits one file holding many chapters at its chapter headings (a prologue, an interlude or another special keeps its place in the file and no chapter number; `Chapter 12 (Part 2)` is part 2 of chapter 12) (numbers and titles from the headings, the split recorded in `report.decisions.intake`); `none` (default) keeps each file as one chapter. Refused for an EPUB.",
            "schema": {
              "default": "none",
              "enum": [
                "none",
                "headings"
              ],
              "type": "string"
            },
            "x-libris-raw-epub-only": true
          }
        ],
        "requestBody": {
          "description": "The content to translate.",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TranslationPayload"
              },
              "example": {
                "external_id": "saga-volume-12",
                "series": {
                  "name": "The Synthetic Saga",
                  "create_if_missing": true
                },
                "volume": {
                  "external_id": "volume-12",
                  "number": 12,
                  "title": "Volume 12"
                },
                "author": "A. Author",
                "source_language": "en",
                "target_language": "fr",
                "chapters": [
                  {
                    "external_id": "chapter-001",
                    "number": 1,
                    "title": "Chapter 1",
                    "content": "First paragraph.\n\nSecond paragraph.\n"
                  }
                ],
                "pipeline": {
                  "start": true,
                  "quality": "high",
                  "context_backend": "hybrid",
                  "final_review": true
                },
                "output": {
                  "format": "json"
                },
                "callback_url": "https://hooks.example.org/libris"
              }
            },
            "multipart/form-data": {
              "schema": {
                "additionalProperties": false,
                "description": "An EPUB (one file in `file`), TXT or DOCX chapters (one or more `.txt` or `.docx` files in `file` or `files`; `series` or `series_id`, `volume`, `source_language` and `target_language` required) or one `.json` document in `file` with no other field. Empty fields count as not given; unknown fields are refused.",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The EPUB, the JSON document or a TXT or DOCX chapter."
                  },
                  "files": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    },
                    "description": "TXT or DOCX chapters, one file each, numbered from their names."
                  },
                  "external_id": {
                    "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$",
                    "type": "string",
                    "description": "Your identifier of the request (same rules as in the JSON document)."
                  },
                  "series": {
                    "maxLength": 500,
                    "type": "string",
                    "description": "The series by name, created when missing (not with `series_id`). Required for TXT."
                  },
                  "series_id": {
                    "maxLength": 36,
                    "type": "string",
                    "description": "The series by id (not with `series`)."
                  },
                  "volume": {
                    "anyOf": [
                      {
                        "maximum": 10000,
                        "minimum": 1,
                        "type": "integer"
                      },
                      {
                        "const": "latest",
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Volume number, 1–10000, or `latest` for the series' last volume (TXT only). Required for TXT; for an EPUB in a series, taken from the file name when free, otherwise the next number."
                  },
                  "volume_external_id": {
                    "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$",
                    "type": "string",
                    "description": "Your identifier of the volume."
                  },
                  "title": {
                    "default": "",
                    "maxLength": 500,
                    "type": "string",
                    "description": "Volume title (an EPUB keeps its own otherwise)."
                  },
                  "author": {
                    "default": "",
                    "maxLength": 500,
                    "type": "string",
                    "description": "Author of the volume."
                  },
                  "source_language": {
                    "maxLength": 35,
                    "type": "string",
                    "description": "BCP 47 tag such as `en`, `fr-FR`, `zh-Hant`."
                  },
                  "target_language": {
                    "maxLength": 35,
                    "type": "string",
                    "description": "BCP 47 tag."
                  },
                  "provider_id": {
                    "maxLength": 36,
                    "type": "string",
                    "description": "Provider to use; defaults to the volume's, then the series' provider."
                  },
                  "quality": {
                    "enum": [
                      "fast",
                      "normal",
                      "high",
                      "maximum"
                    ],
                    "type": "string"
                  },
                  "context_backend": {
                    "enum": [
                      "internal",
                      "openviking",
                      "hybrid"
                    ],
                    "type": "string"
                  },
                  "start": {
                    "default": true,
                    "type": "boolean",
                    "description": "Run the whole pipeline (needs the `pipeline:start` scope); false only imports."
                  },
                  "final_review": {
                    "default": true,
                    "type": "boolean",
                    "description": "Run the final review (never when the server disables it)."
                  },
                  "output_format": {
                    "enum": [
                      "json",
                      "txt",
                      "txt-zip",
                      "epub",
                      "epub-bilingual"
                    ],
                    "type": "string",
                    "description": "Default format of the result; `epub` only for an EPUB (and its default); `epub-bilingual` is a bilingual EPUB for proofreading, for any input."
                  },
                  "callback_url": {
                    "maxLength": 2000,
                    "type": "string",
                    "description": "Webhook called when the request ends; its host must be allowed by an administrator."
                  },
                  "callback_events": {
                    "default": "",
                    "maxLength": 200,
                    "type": "string",
                    "description": "Comma-separated extra webhooks, for example `chapters.translated`."
                  },
                  "replace_changed_chapters": {
                    "default": false,
                    "type": "boolean",
                    "description": "Replace chapters already imported with another text (otherwise `409 chapter_conflict`). Unchanged passages keep their translation."
                  },
                  "discard_human": {
                    "default": false,
                    "type": "boolean",
                    "description": "With `replace_changed_chapters`: allow dropping passages a person edited or validated (otherwise `409 conflict`)."
                  },
                  "priority": {
                    "enum": [
                      "low",
                      "normal",
                      "high"
                    ],
                    "type": "string",
                    "description": "Place in the fair queue: `low`, `normal` (default) or `high`, within the token's `max_priority` and its account's ceiling (otherwise `403 priority_not_allowed`). Does not change what the request is."
                  },
                  "analysis_mode": {
                    "enum": [
                      "parallel",
                      "strict"
                    ],
                    "type": "string",
                    "description": "`parallel`: the passages are analysed side by side, then each is reconciled with what precedes it; `strict`: one passage after the other. Default: the volume's choice, else `ANALYSIS_MODE`."
                  },
                  "threads": {
                    "maximum": 64,
                    "minimum": 1,
                    "type": "integer",
                    "description": "Passages worked on at once, analysis and translation alike (1–64). It can only lower the volume's share of the provider's capacity; default: that share."
                  },
                  "split": {
                    "default": "none",
                    "enum": [
                      "none",
                      "headings"
                    ],
                    "type": "string",
                    "description": "TXT or DOCX: `headings` splits one file holding many chapters at its chapter headings (a prologue, an interlude or another special keeps its place in the file and no chapter number; `Chapter 12 (Part 2)` is part 2 of chapter 12) (numbers and titles from the headings, the split recorded in `report.decisions.intake`); `none` (default) keeps each file as one chapter. Refused for an EPUB."
                  }
                },
                "type": "object"
              },
              "encoding": {
                "file": {
                  "contentType": "application/epub+zip, application/json, text/plain, application/vnd.openxmlformats-officedocument.wordprocessingml.document"
                },
                "files": {
                  "contentType": "text/plain, application/vnd.openxmlformats-officedocument.wordprocessingml.document"
                }
              }
            },
            "application/epub+zip": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted: the request is stored and will run in the worker.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestSummary"
                },
                "example": {
                  "request_id": "5b1c2d3e-0000-4000-8000-000000000001",
                  "external_id": "saga-volume-12",
                  "series_id": "5b1c2d3e-0000-4000-8000-000000000002",
                  "project_id": "5b1c2d3e-0000-4000-8000-000000000003",
                  "job_id": "5b1c2d3e-0000-4000-8000-000000000004",
                  "input": "json",
                  "status": "pending",
                  "status_url": "/api/v1/translation-requests/5b1c2d3e-0000-4000-8000-000000000001",
                  "result_url": "/api/v1/translation-requests/5b1c2d3e-0000-4000-8000-000000000001/result"
                }
              }
            },
            "headers": {
              "Location": {
                "description": "Path of the status document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "200": {
            "description": "Replayed: this content was already sent with this key or `external_id`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestSummary"
                },
                "example": {
                  "request_id": "5b1c2d3e-0000-4000-8000-000000000001",
                  "external_id": "saga-volume-12",
                  "series_id": "5b1c2d3e-0000-4000-8000-000000000002",
                  "project_id": "5b1c2d3e-0000-4000-8000-000000000003",
                  "job_id": "5b1c2d3e-0000-4000-8000-000000000004",
                  "input": "json",
                  "status": "pending",
                  "status_url": "/api/v1/translation-requests/5b1c2d3e-0000-4000-8000-000000000001",
                  "result_url": "/api/v1/translation-requests/5b1c2d3e-0000-4000-8000-000000000001/result"
                }
              }
            },
            "headers": {
              "Location": {
                "description": "Path of the status document.",
                "schema": {
                  "type": "string"
                }
              },
              "Idempotent-Replayed": {
                "description": "`true`.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "A missing, unknown, revoked or expired token, or a disabled account (header `WWW-Authenticate: Bearer`). Codes: `missing_token`, `invalid_token`, `revoked_token`, `expired_token`, `inactive_account`, `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_token": {
                    "value": {
                      "detail": {
                        "code": "missing_token",
                        "message": "Jeton d’API manquant : envoyez « Authorization: Bearer <jeton> »."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer realm=\"libris\"`",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "402": {
            "description": "The token's cost budget is reached (`budget` gives the cap, the spend, the period and when it resets); a request that only imports (`start` false) is still accepted. Codes: `budget_exceeded`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks a scope (`scope` names it), a browser page from another site, or the priority asked for is above the token's or the account's ceiling (`max_priority`). Codes: `insufficient_scope`, `forbidden`, `priority_not_allowed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_scope": {
                    "value": {
                      "detail": {
                        "code": "insufficient_scope",
                        "message": "Ce jeton n’a pas la permission « jobs:read »."
                      }
                    }
                  },
                  "priority_not_allowed": {
                    "value": {
                      "detail": {
                        "code": "priority_not_allowed",
                        "message": "Priorité « high » refusée : « normal » au plus pour ce compte ou ce jeton."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "The series named by `id` does not exist, or `create_if_missing` is false. Codes: `series_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "series_not_found": {
                    "value": {
                      "detail": {
                        "code": "series_not_found",
                        "message": "Série introuvable."
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The content cannot be taken as it is. Codes: `idempotency_conflict`, `chapter_conflict`, `conflict`, `volume_conflict`, `series_archived`, `volume_archived`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "idempotency_conflict": {
                    "value": {
                      "detail": {
                        "code": "idempotency_conflict",
                        "message": "Une autre requête a déjà utilisé cette clé d’idempotence ou cet external_id avec un contenu différent."
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is above `API_MAX_PAYLOAD_MB`. Codes: `payload_too_large`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Neither JSON, EPUB nor multipart. Codes: `unsupported_media_type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The document, the upload or an option is invalid. Codes: `invalid_payload`, `invalid_idempotency_key`, `unknown_provider`, `provider_required`, `invalid_epub`, `callback_refused`, `invalid_placement`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_payload": {
                    "value": {
                      "detail": {
                        "code": "invalid_payload",
                        "message": "Requête de traduction invalide."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many calls for this token (retry after `Retry-After` seconds), or the token or its account already has its quota of requests waiting to start (`scope`, `limit`): nothing is stored, retry once one of them has started. Codes: `rate_limited`, `queue_full`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "detail": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes pour ce jeton : réessayez plus tard."
                      }
                    }
                  },
                  "queue_full": {
                    "value": {
                      "detail": {
                        "code": "queue_full",
                        "message": "File d’attente pleine pour ce jeton : 50 travaux en attente au plus. Réessayez quand l’un d’eux aura démarré."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure; the message carries a diagnostic reference for the server logs. Codes: `server_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerToken": []
          }
        ],
        "x-libris-scopes": [
          "content:write",
          "pipeline:start"
        ]
      }
    },
    "/api/v1/translation-requests/{request_id}": {
      "get": {
        "operationId": "getTranslationRequest",
        "tags": [
          "Translation requests"
        ],
        "summary": "Get the status of a request",
        "description": "Status, progress per chapter and stage, stored result and, once the request ended, its completion report. Add `?wait=` to long-poll until the request ends.\n\n**Scope:** `jobs:read`.",
        "parameters": [
          {
            "name": "request_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The request's `request_id`."
          },
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 600,
              "minimum": 0,
              "default": 0
            },
            "description": "Long poll: answer as soon as the request ends, or after this many seconds (bounded by the server's `API_RESULT_MAX_WAIT_SECONDS`, 60 by default)."
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestDetail"
                },
                "example": {
                  "request_id": "5b1c2d3e-0000-4000-8000-000000000001",
                  "external_id": "saga-volume-12",
                  "series_id": "5b1c2d3e-0000-4000-8000-000000000002",
                  "project_id": "5b1c2d3e-0000-4000-8000-000000000003",
                  "job_id": "5b1c2d3e-0000-4000-8000-000000000004",
                  "input": "json",
                  "status": "running",
                  "status_url": "/api/v1/translation-requests/5b1c2d3e-0000-4000-8000-000000000001",
                  "result_url": "/api/v1/translation-requests/5b1c2d3e-0000-4000-8000-000000000001/result",
                  "created_at": 1790000000.0,
                  "updated_at": 1790000100.0,
                  "finished_at": null,
                  "stage": "translation",
                  "step": "translation",
                  "progress": {
                    "segments": 412,
                    "translated": 180,
                    "percent": 44,
                    "stages": [
                      {
                        "key": "translation",
                        "done": 180,
                        "total": 412,
                        "percent": 44
                      }
                    ],
                    "analysis": null
                  },
                  "estimate": null,
                  "error": "",
                  "stop_reason": "",
                  "next_attempt": 0,
                  "chapters": {
                    "created": 1,
                    "unchanged": 0,
                    "replaced": 0,
                    "items": [
                      {
                        "chapter_id": "5b1c2d3e-0000-4000-8000-000000000005",
                        "external_id": "chapter-001",
                        "number": 1,
                        "kind": "chapter",
                        "part": null,
                        "part_count": null,
                        "label": "",
                        "display_label": "Chapter 1",
                        "position": 0,
                        "mapping": {
                          "confidence": "high",
                          "reason": "indiqué dans la requête",
                          "detected": false
                        },
                        "title": "Chapter 1",
                        "segments": 140,
                        "translated": 60,
                        "validated": 0,
                        "flagged": 0,
                        "complete": false
                      }
                    ],
                    "new": [
                      "5b1c2d3e-0000-4000-8000-000000000005"
                    ]
                  },
                  "options": {
                    "start": true,
                    "final_review": true,
                    "output_format": "json",
                    "analysis_mode": null,
                    "threads": null
                  },
                  "priority": "normal",
                  "queue": null,
                  "result": null,
                  "report": null,
                  "webhook": {
                    "state": "pending",
                    "attempts": 0,
                    "error": ""
                  },
                  "chapter_events": {
                    "event": "chapters.translated",
                    "batches": 0,
                    "delivered": 0,
                    "pending": 0,
                    "failed": 0,
                    "waiting_chapters": 1,
                    "error": ""
                  }
                }
              }
            }
          },
          "401": {
            "description": "A missing, unknown, revoked or expired token, or a disabled account (header `WWW-Authenticate: Bearer`). Codes: `missing_token`, `invalid_token`, `revoked_token`, `expired_token`, `inactive_account`, `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_token": {
                    "value": {
                      "detail": {
                        "code": "missing_token",
                        "message": "Jeton d’API manquant : envoyez « Authorization: Bearer <jeton> »."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer realm=\"libris\"`",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the scope (`scope` names it), or a browser page from another site. Codes: `insufficient_scope`, `forbidden`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_scope": {
                    "value": {
                      "detail": {
                        "code": "insufficient_scope",
                        "message": "Ce jeton n’a pas la permission « jobs:read »."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown, or owned by someone else. Codes: `request_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "request_not_found": {
                    "value": {
                      "detail": {
                        "code": "request_not_found",
                        "message": "Requête de traduction introuvable."
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "A bad parameter. Codes: `invalid_request`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "detail": {
                        "code": "invalid_request",
                        "message": "Requête invalide."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many calls for this token; retry after `Retry-After` seconds. Codes: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "detail": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes pour ce jeton : réessayez plus tard."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure; the message carries a diagnostic reference for the server logs. Codes: `server_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerToken": []
          }
        ],
        "x-libris-scopes": [
          "jobs:read"
        ]
      }
    },
    "/api/v1/translation-requests/{request_id}/result": {
      "get": {
        "operationId": "getTranslationResult",
        "tags": [
          "Results"
        ],
        "summary": "Download the result",
        "description": "The chapters of the request in reading order (the whole book for an EPUB), as an EPUB (EPUB requests only), a bilingual EPUB for proofreading (`epub-bilingual`, any input, `layout` interleaved or side by side), a JSON document, one UTF-8 text file, or a ZIP of one text file per chapter with a `manifest.json`. `?format=` wins, then the `Accept` header, then the request's own format. `scope` chooses the chapters: the request's, only its new ones, or the whole volume.\n\nPassages without a translation keep their source text. Before the end the answer is `409 result_not_ready` (with `Retry-After`), unless `partial=true`.\n\n**Scope:** `results:read`.",
        "parameters": [
          {
            "name": "request_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The request's `request_id`."
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "json",
                "txt",
                "txt-zip",
                "epub",
                "epub-bilingual"
              ],
              "type": "string"
            },
            "description": "Result format; wins over the `Accept` header. Default: the request's own format (EPUB for an EPUB)."
          },
          {
            "name": "partial",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Return what is translated so far instead of `409 result_not_ready` (missing passages keep their source text; `X-Libris-Complete: false`)."
          },
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 600,
              "minimum": 0,
              "default": 0
            },
            "description": "Long poll: answer as soon as the request ends, or after this many seconds (bounded by the server's `API_RESULT_MAX_WAIT_SECONDS`, 60 by default)."
          },
          {
            "name": "layout",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "interleaved",
                "side-by-side"
              ],
              "type": "string",
              "default": "interleaved"
            },
            "description": "`format=epub-bilingual` only: each source paragraph followed by its translation (`interleaved`) or next to it in two columns that stack on a narrow screen (`side-by-side`)."
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "request",
                "new",
                "volume"
              ],
              "type": "string",
              "default": "request"
            },
            "description": "The chapters covered: those the request sent (`request`, the whole book for an EPUB), only those it created or replaced (`new`), or every chapter of the volume (`volume`). `epub` is always the whole book."
          }
        ],
        "responses": {
          "200": {
            "description": "The result. `X-Libris-Complete` tells whether every passage is translated. `application/epub+zip`: The translated EPUB (`format=epub`), or the bilingual EPUB (`format=epub-bilingual`). `application/json`: The JSON document (`format=json`). `text/plain`: One UTF-8 text file, chapters under their headings (`format=txt`). `application/zip`: `chapters/001 - Title.txt`… and `manifest.json` with each file's SHA-256 (`format=txt-zip`).",
            "headers": {
              "X-Libris-Complete": {
                "description": "`true` or `false`.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Libris-Status": {
                "description": "The request's status.",
                "schema": {
                  "$ref": "#/components/schemas/RequestStatus"
                }
              },
              "Content-Disposition": {
                "description": "File name (every format but JSON).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/epub+zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonResult"
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "A missing, unknown, revoked or expired token, or a disabled account (header `WWW-Authenticate: Bearer`). Codes: `missing_token`, `invalid_token`, `revoked_token`, `expired_token`, `inactive_account`, `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_token": {
                    "value": {
                      "detail": {
                        "code": "missing_token",
                        "message": "Jeton d’API manquant : envoyez « Authorization: Bearer <jeton> »."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer realm=\"libris\"`",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the scope (`scope` names it), or a browser page from another site. Codes: `insufficient_scope`, `forbidden`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_scope": {
                    "value": {
                      "detail": {
                        "code": "insufficient_scope",
                        "message": "Ce jeton n’a pas la permission « jobs:read »."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown request, or its volume was deleted. Codes: `request_not_found`, `volume_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "request_not_found": {
                    "value": {
                      "detail": {
                        "code": "request_not_found",
                        "message": "Requête de traduction introuvable."
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The result cannot be served yet or at all. Codes: `result_not_ready`, `request_failed`, `request_cancelled`, `format_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "result_not_ready": {
                    "value": {
                      "detail": {
                        "code": "result_not_ready",
                        "message": "La traduction de cette requête n’est pas terminée : réessayez plus tard, ou demandez un résultat partiel (partial=true)."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "422": {
            "description": "A bad parameter, or an EPUB rendered on demand could not be built. Codes: `invalid_request`, `delivery_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "detail": {
                        "code": "invalid_request",
                        "message": "Requête invalide."
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many calls for this token; retry after `Retry-After` seconds. Codes: `rate_limited`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "detail": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes pour ce jeton : réessayez plus tard."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure; the message carries a diagnostic reference for the server logs. Codes: `server_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerToken": []
          }
        ],
        "x-libris-scopes": [
          "results:read"
        ]
      }
    },
    "/api/v1/translation-requests/{request_id}/{action}": {
      "post": {
        "operationId": "controlTranslationRequest",
        "tags": [
          "Translation requests"
        ],
        "summary": "Pause, resume or cancel a request",
        "description": "Same rules as the interface. A request without a job yet (`queued`) can only be cancelled. Resuming a job paused by a cost budget is refused until the budget is raised, and resuming counts as a new entry in the queue. Answers with the status document.\n\n**Scope:** `jobs:control`.",
        "parameters": [
          {
            "name": "request_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The request's `request_id`."
          },
          {
            "name": "action",
            "in": "path",
            "required": true,
            "schema": {
              "enum": [
                "pause",
                "resume",
                "cancel"
              ],
              "type": "string"
            },
            "description": "`pause`, `resume` or `cancel`."
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestDetail"
                }
              }
            }
          },
          "401": {
            "description": "A missing, unknown, revoked or expired token, or a disabled account (header `WWW-Authenticate: Bearer`). Codes: `missing_token`, `invalid_token`, `revoked_token`, `expired_token`, `inactive_account`, `unauthorized`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_token": {
                    "value": {
                      "detail": {
                        "code": "missing_token",
                        "message": "Jeton d’API manquant : envoyez « Authorization: Bearer <jeton> »."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "WWW-Authenticate": {
                "description": "`Bearer realm=\"libris\"`",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "The token lacks the scope (`scope` names it), or a browser page from another site. Codes: `insufficient_scope`, `forbidden`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_scope": {
                    "value": {
                      "detail": {
                        "code": "insufficient_scope",
                        "message": "Ce jeton n’a pas la permission « jobs:read »."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown, or owned by someone else. Codes: `request_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "request_not_found": {
                    "value": {
                      "detail": {
                        "code": "request_not_found",
                        "message": "Requête de traduction introuvable."
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The job's state does not allow the action, or the book's or the token's cost budget that paused it is still reached. Codes: `not_started`, `conflict`, `budget_exceeded`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many calls for this token (retry after `Retry-After` seconds), or resuming would exceed the token's or the account's quota of waiting requests (`scope`, `limit`). Codes: `rate_limited`, `queue_full`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "detail": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes pour ce jeton : réessayez plus tard."
                      }
                    }
                  },
                  "queue_full": {
                    "value": {
                      "detail": {
                        "code": "queue_full",
                        "message": "File d’attente pleine pour ce jeton : 50 travaux en attente au plus. Réessayez quand l’un d’eux aura démarré."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure; the message carries a diagnostic reference for the server logs. Codes: `server_error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerToken": []
          }
        ],
        "x-libris-scopes": [
          "jobs:control"
        ]
      }
    }
  },
  "webhooks": {
    "chaptersTranslated": {
      "post": {
        "operationId": "chaptersTranslated",
        "tags": [
          "Translation requests"
        ],
        "summary": "Chapters were translated",
        "description": "Sent by the worker to the request's `callback_url` while its job runs, once per batch of chapters whose passages all have a translation, when the request listed `chapters.translated` in `callback_events`. Batches go before the final webhook when both are due, but a retried batch can arrive after it: order them by `batch`. The text of a batch is a draft until the request ends. Check the signature over the raw body and refuse old timestamps. Any `2xx` counts as delivered; anything else is retried with an exponential backoff (30 s, 60 s… up to one hour) at most `API_WEBHOOK_MAX_ATTEMPTS` times. Redirects are not followed.",
        "parameters": [
          {
            "name": "X-Libris-Event",
            "in": "header",
            "required": true,
            "description": "`chapters.translated`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Libris-Delivery",
            "in": "header",
            "required": true,
            "description": "`<request id>:chapters.translated:<batch>:<attempt number>`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Libris-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time in seconds.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Libris-Signature",
            "in": "header",
            "required": true,
            "description": "`sha256=<hex>`: HMAC-SHA256 of `<timestamp>.<raw body>` with the token's webhook secret, or the server's `API_WEBHOOK_SECRET`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChaptersTranslatedEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Delivered."
          }
        },
        "security": []
      }
    },
    "translationRequestFinished": {
      "post": {
        "operationId": "translationRequestFinished",
        "tags": [
          "Translation requests"
        ],
        "summary": "A request ended",
        "description": "Sent by the worker to the request's `callback_url` when it ends. Check the signature over the raw body and refuse old timestamps. Any `2xx` counts as delivered; anything else is retried with an exponential backoff (30 s, 60 s… up to one hour) at most `API_WEBHOOK_MAX_ATTEMPTS` times. Redirects are not followed.",
        "parameters": [
          {
            "name": "X-Libris-Event",
            "in": "header",
            "required": true,
            "description": "`translation_request.finished`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Libris-Delivery",
            "in": "header",
            "required": true,
            "description": "`<request id>:<attempt number>`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Libris-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix time in seconds.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Libris-Signature",
            "in": "header",
            "required": true,
            "description": "`sha256=<hex>`: HMAC-SHA256 of `<timestamp>.<raw body>` with the token's webhook secret, or the server's `API_WEBHOOK_SECRET`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Delivered."
          }
        },
        "security": []
      }
    }
  },
  "components": {
    "schemas": {
      "AnalysisPhase": {
        "type": "object",
        "description": "Where a running analysis is; `null` when no analysis runs.",
        "properties": {
          "step": {
            "type": "string",
            "enum": [
              "extraction",
              "consolidation",
              "reconciliation",
              "memory",
              "chapter_analysis",
              "book_bible"
            ],
            "description": "Parallel analysis: `extraction`, `consolidation`, `reconciliation`, `memory`, then `book_bible`; strict analysis: `chapter_analysis`, then `book_bible`."
          },
          "current": {
            "type": "integer",
            "description": "Passages (or Book Bible syntheses) started in this step."
          },
          "total": {
            "type": "integer"
          },
          "level": {
            "type": "integer",
            "description": "`book_bible` of a parallel analysis: the level of the tree being built."
          },
          "levels": {
            "type": "integer",
            "description": "Levels of the Book Bible tree."
          },
          "percent": {
            "type": "integer",
            "description": "How far the whole analysis is, 0–100."
          }
        },
        "required": [
          "step",
          "current",
          "total",
          "percent"
        ]
      },
      "ChapterCounts": {
        "type": "object",
        "description": "How many chapters the request created, found unchanged or replaced, and each chapter's progress.",
        "properties": {
          "created": {
            "type": "integer"
          },
          "unchanged": {
            "type": "integer"
          },
          "replaced": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChapterProgress"
            }
          },
          "new": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The chapters the request created or replaced (ids): the ones a follow-up translates."
          }
        },
        "required": [
          "items",
          "new"
        ],
        "additionalProperties": true
      },
      "ChapterEvents": {
        "type": "object",
        "description": "The `chapters.translated` webhooks of the request.",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "chapters.translated"
            ]
          },
          "batches": {
            "type": "integer",
            "description": "Batches queued so far."
          },
          "delivered": {
            "type": "integer"
          },
          "pending": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          },
          "waiting_chapters": {
            "type": "integer",
            "description": "Chapters of the request not translated (nor announced) yet."
          },
          "error": {
            "type": "string",
            "description": "Last failure of a batch, empty when none."
          }
        },
        "required": [
          "event",
          "batches",
          "delivered",
          "pending",
          "failed",
          "waiting_chapters",
          "error"
        ]
      },
      "ChapterInput": {
        "additionalProperties": false,
        "properties": {
          "external_id": {
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$",
            "type": [
              "string",
              "null"
            ],
            "default": null,
            "description": "Your identifier of the chapter: sending it again finds the same chapter."
          },
          "number": {
            "maximum": 100000,
            "minimum": 0,
            "type": [
              "number",
              "null"
            ],
            "default": null,
            "description": "Chapter number, 0–100000; decimals such as `12.5` (a real half chapter) allowed. Required for a chapter unless the title gives it; for a special (`kind`), its own number (`Interlude 2`)."
          },
          "title": {
            "default": "",
            "maxLength": 500,
            "type": "string",
            "description": "Optional; without it the chapter is named by its number."
          },
          "content": {
            "type": "string",
            "description": "The chapter's text (not blank, at most `TEXT_CHAPTER_MAX_CHARS` characters)."
          },
          "kind": {
            "enum": [
              "chapter",
              "prologue",
              "interlude",
              "side_story",
              "extra",
              "epilogue",
              "afterword",
              "author_note",
              "front_matter"
            ],
            "type": [
              "string",
              "null"
            ],
            "default": null,
            "description": "What the chapter is: `chapter`, `prologue`, `interlude`, `side_story`, `extra`, `epilogue`, `afterword`, `author_note`, `front_matter`. Absent: read from the title (`Prologue`, `Interlude – Ayla`), else `chapter`."
          },
          "part": {
            "maximum": 999,
            "minimum": 1,
            "type": [
              "integer",
              "null"
            ],
            "default": null,
            "description": "Part of a chapter published in several, 1–999 (`Chapter 12 (Part 2)`: `number` 12, `part` 2). Absent: read from the title."
          },
          "part_count": {
            "maximum": 999,
            "minimum": 1,
            "type": [
              "integer",
              "null"
            ],
            "default": null,
            "description": "How many parts the chapter has, when known."
          },
          "label": {
            "maxLength": 200,
            "type": [
              "string",
              "null"
            ],
            "default": null,
            "description": "Name shown after the kind (`Ayla` in `Interlude – Ayla`)."
          },
          "after": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Insert after this chapter: its Libris `chapter_id`, its `external_id` (of the volume or of this request), a chapter number (after that chapter, its parts and the specials already after it), or `start`. Unknown: `422 invalid_placement`. Also moves a chapter sent again."
          },
          "position": {
            "maximum": 100000,
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ],
            "default": null,
            "description": "Insert at this reading position (0: first). Not with `after`."
          }
        },
        "required": [
          "content"
        ],
        "type": "object"
      },
      "ChapterProgress": {
        "type": "object",
        "properties": {
          "chapter_id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "number": {
            "type": [
              "number",
              "null"
            ],
            "description": "The chapter's number; a special's own number (`Interlude 2`)."
          },
          "kind": {
            "type": "string",
            "enum": [
              "chapter",
              "prologue",
              "interlude",
              "side_story",
              "extra",
              "epilogue",
              "afterword",
              "author_note",
              "front_matter"
            ],
            "description": "What the chapter is in the reading."
          },
          "part": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Part of a chapter published in several (`Chapter 12 (Part 2)`)."
          },
          "part_count": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How many parts the chapter has, when known."
          },
          "label": {
            "type": "string",
            "description": "The name after the kind and number (`Ayla` in `Interlude – Ayla`)."
          },
          "display_label": {
            "type": "string",
            "description": "The full label in the source language (`Chapter 12 (Part 2)`)."
          },
          "position": {
            "type": "integer",
            "description": "Reading position in the volume: what orders chapters, not the number."
          },
          "mapping": {
            "type": [
              "object",
              "null"
            ],
            "description": "How the map was decided: `confidence`, `reason`, `detected` (read from the title)."
          },
          "title": {
            "type": "string"
          },
          "segments": {
            "type": "integer"
          },
          "translated": {
            "type": "integer"
          },
          "validated": {
            "type": "integer"
          },
          "flagged": {
            "type": "integer",
            "description": "Passages still flagged (`check`, `error`, `refused`) and not validated."
          },
          "complete": {
            "type": "boolean"
          }
        },
        "required": [
          "chapter_id",
          "external_id",
          "number",
          "kind",
          "part",
          "part_count",
          "label",
          "display_label",
          "position",
          "mapping",
          "title",
          "segments",
          "translated",
          "validated",
          "flagged",
          "complete"
        ]
      },
      "ChaptersTranslatedEvent": {
        "type": "object",
        "description": "Sent while the job runs, once per batch of chapters whose passages all have a translation.",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "chapters.translated"
            ]
          },
          "request_id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "series_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "batch": {
            "type": "integer",
            "description": "Counts from 1 per request; order batches by it."
          },
          "chapters": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "chapter_id": {
                  "type": "string",
                  "description": "Identifier (UUID)."
                },
                "external_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "number": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "title": {
                  "type": "string"
                }
              },
              "required": [
                "chapter_id",
                "external_id",
                "number",
                "title"
              ]
            },
            "description": "The chapters of this batch, in reading order."
          },
          "announced": {
            "type": "integer",
            "description": "Chapters announced so far, this batch included."
          },
          "total": {
            "type": "integer",
            "description": "Chapters in the request."
          },
          "status_url": {
            "type": "string"
          },
          "result_url": {
            "type": "string",
            "description": "Partial result of the request (`?partial=true`)."
          },
          "created_at": {
            "type": "number",
            "description": "Unix time in seconds."
          }
        },
        "required": [
          "event",
          "request_id",
          "external_id",
          "series_id",
          "project_id",
          "batch",
          "chapters",
          "announced",
          "total",
          "status_url",
          "result_url",
          "created_at"
        ],
        "additionalProperties": true
      },
      "CompletionReport": {
        "type": "object",
        "description": "What was translated, what kept its source and why, what it cost and how long it took, and the quality scores of its passages.",
        "properties": {
          "version": {
            "type": "integer"
          },
          "outcome": {
            "type": "string",
            "enum": [
              "completed",
              "completed_with_residuals",
              "failed",
              "cancelled",
              "imported"
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why the request did not complete."
          },
          "passages": {
            "type": "object",
            "description": "Counts over the request's passages (the whole book for an EPUB).",
            "properties": {
              "total": {
                "type": "integer"
              },
              "translated": {
                "type": "integer"
              },
              "source_retained": {
                "type": "integer"
              },
              "untranslated": {
                "type": "integer"
              },
              "flagged": {
                "type": "integer"
              },
              "validated": {
                "type": "integer"
              },
              "human": {
                "type": "integer"
              },
              "by_status": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "Passages per status."
              }
            },
            "required": [
              "total",
              "translated",
              "source_retained",
              "untranslated",
              "flagged",
              "validated",
              "human",
              "by_status"
            ]
          },
          "residual_total": {
            "type": "integer"
          },
          "residuals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Residual"
            },
            "description": "At most 500; `residuals_truncated` tells when the list is cut."
          },
          "residuals_truncated": {
            "type": "boolean"
          },
          "usage": {
            "$ref": "#/components/schemas/Usage"
          },
          "durations": {
            "type": "object",
            "properties": {
              "total_seconds": {
                "type": "number"
              },
              "queued_seconds": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "job_seconds": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "total_seconds",
              "queued_seconds",
              "job_seconds"
            ]
          },
          "autopilot": {
            "type": [
              "object",
              "null"
            ],
            "description": "How the autopilot ended: `outcome`, `rounds`, `reason`."
          },
          "decisions": {
            "type": "object",
            "properties": {
              "autopilot": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Decisions the autopilot logged for the job."
              },
              "intake": {
                "type": "array",
                "items": {
                  "type": "object"
                },
                "description": "Choices made when reading the upload (volume and chapter numbers, encoding, reused EPUB), each with its reason."
              }
            },
            "required": [
              "autopilot",
              "intake"
            ]
          },
          "chapter_map": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Each chapter of the request: `chapter_id`, `external_id`, `status`, `kind`, `number`, `part`, `part_count`, `label`, `position`, and for a detection its `confidence` and `reason`."
          },
          "delivery": {
            "type": [
              "object",
              "null"
            ],
            "description": "EPUB only: EPUBCheck `validation`, `repairs`, `inherited_errors`, and `errors` when the delivery failed."
          },
          "cost": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CostReport"
              },
              {
                "type": "null"
              }
            ]
          },
          "quality": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/QualityReport"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "version",
          "outcome",
          "reason",
          "passages",
          "residual_total",
          "residuals",
          "residuals_truncated",
          "usage",
          "durations",
          "autopilot",
          "decisions",
          "delivery"
        ],
        "additionalProperties": true
      },
      "CostReport": {
        "type": "object",
        "description": "Estimated against real cost, in the currency of the provider prices, with the book's budget.",
        "properties": {
          "estimated": {
            "type": [
              "number",
              "null"
            ],
            "description": "The estimate made when the job started; `null` when none was made."
          },
          "actual": {
            "type": [
              "number",
              "null"
            ],
            "description": "The job's real cost (`usage.cost`); `null` when no call had a price."
          },
          "budget": {
            "type": [
              "number",
              "null"
            ],
            "description": "The book's cost budget; `null` without one."
          },
          "book_spent": {
            "type": [
              "number",
              "null"
            ],
            "description": "What the book has cost in all, every job included."
          },
          "warning": {
            "type": [
              "string",
              "null"
            ],
            "description": "The warning given at launch when the estimate exceeded what was left of a budget."
          },
          "paused_for_budget": {
            "type": "boolean",
            "description": "Whether the job is paused because a budget was reached."
          },
          "provider_switches": {
            "type": "integer",
            "description": "How many times the job moved to a cheaper fallback provider to stay within a budget."
          }
        },
        "required": [
          "estimated",
          "actual",
          "budget",
          "book_spent",
          "warning",
          "paused_for_budget",
          "provider_switches"
        ]
      },
      "Error": {
        "type": "object",
        "description": "Every error of the automation API.",
        "properties": {
          "detail": {
            "$ref": "#/components/schemas/ErrorDetail"
          }
        },
        "required": [
          "detail"
        ]
      },
      "ErrorDetail": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable machine-readable code, for example `chapter_conflict`."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation, French by default, English with `Accept-Language: en`."
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ValidationIssue"
            },
            "description": "Validation problems (`invalid_payload`, `invalid_request`)."
          },
          "scope": {
            "type": "string",
            "description": "The missing scope (`insufficient_scope`), or whose quota is full, `token` or `account` (`queue_full`)."
          },
          "limit": {
            "type": "integer",
            "description": "The quota that is full (`queue_full`)."
          },
          "max_priority": {
            "type": "string",
            "enum": [
              "low",
              "normal",
              "high"
            ],
            "description": "The highest priority the token and its account may ask for (`priority_not_allowed`)."
          },
          "budget": {
            "type": "object",
            "description": "The token's cost budget (`budget_exceeded` answered with `402`).",
            "properties": {
              "amount": {
                "type": "number",
                "description": "The token's cap, in the currency of the provider prices."
              },
              "spent": {
                "type": "number",
                "description": "What the current period has spent."
              },
              "period": {
                "type": "string",
                "enum": [
                  "month",
                  "total"
                ],
                "description": "`month` (calendar month, UTC) or `total`."
              },
              "resets_at": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Start of the next month (Unix time); `null` for a total cap."
              }
            },
            "required": [
              "amount",
              "spent",
              "period",
              "resets_at"
            ]
          },
          "request_id": {
            "type": "string",
            "description": "The request that already used this key (`idempotency_conflict`)."
          },
          "status": {
            "type": "string",
            "description": "The request's status (`result_not_ready`, `request_failed`…)."
          },
          "reason": {
            "type": "string",
            "description": "Why the request failed or was cancelled."
          },
          "incomplete_chapters": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Chapters not fully translated yet."
          },
          "conflicts": {
            "type": "array",
            "items": {},
            "description": "Chapters already imported with another text (`chapter_conflict`)."
          },
          "protected_segments": {
            "type": "array",
            "items": {},
            "description": "Passages edited by a person that a replacement would drop."
          }
        },
        "required": [
          "code",
          "message"
        ],
        "additionalProperties": true
      },
      "GlossaryImportForm": {
        "type": "object",
        "description": "A glossary file and how to read it. CSV files may carry a byte order mark; French or English headers are recognised.",
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "description": "The glossary: JSON, CSV or TBX (v2 and v3), 2 MB at most."
          },
          "strategy": {
            "type": "string",
            "enum": [
              "skip",
              "replace",
              "replace_all"
            ],
            "default": "skip",
            "description": "`skip` keeps the terms in place; `replace` replaces unlocked terms that differ; `replace_all` replaces locked ones too."
          },
          "delimiter": {
            "type": "string",
            "enum": [
              "comma",
              "semicolon",
              "tab"
            ],
            "description": "CSV separator; detected when absent."
          },
          "mapping": {
            "type": "string",
            "description": "JSON object from field to column number (from 0), for example `{\"source\": 0, \"translation\": 2}`; from the headers when absent."
          },
          "header": {
            "type": "boolean",
            "description": "Whether the first row holds column names; detected when absent."
          },
          "skip_invalid": {
            "type": "boolean",
            "default": false,
            "description": "Leave invalid rows out instead of refusing the file."
          }
        },
        "required": [
          "file"
        ]
      },
      "GlossaryImportReport": {
        "type": "object",
        "description": "The import plan: what the file adds, what conflicts with the terms in place and what is invalid.",
        "properties": {
          "format": {
            "type": "string",
            "enum": [
              "json",
              "csv",
              "tbx"
            ]
          },
          "encoding": {
            "type": "string"
          },
          "delimiter": {
            "type": [
              "string",
              "null"
            ]
          },
          "columns": {
            "type": "array",
            "items": {},
            "description": "The first row of a CSV file."
          },
          "header": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "mapping": {
            "type": [
              "object",
              "null"
            ],
            "description": "Field → column number used."
          },
          "strategy": {
            "type": "string",
            "enum": [
              "skip",
              "replace",
              "replace_all"
            ]
          },
          "counts": {
            "type": "object",
            "properties": {
              "terms": {
                "type": "integer"
              },
              "new": {
                "type": "integer"
              },
              "unchanged": {
                "type": "integer"
              },
              "conflicts": {
                "type": "integer"
              },
              "replaced": {
                "type": "integer"
              },
              "kept": {
                "type": "integer"
              },
              "duplicates": {
                "type": "integer"
              },
              "errors": {
                "type": "integer"
              }
            },
            "required": [
              "terms",
              "new",
              "unchanged",
              "conflicts",
              "replaced",
              "kept",
              "duplicates",
              "errors"
            ]
          },
          "new": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Terms the import adds (at most 200)."
          },
          "conflicts": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Sources already present with other values: `existing`, `incoming`, the differing `fields`, `locked` and the `action` (`replace` or `keep`); at most 200."
          },
          "duplicates": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "A source repeated in the file (the first row counts); at most 200."
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Invalid rows: `line`, `message`; at most 200."
          },
          "truncated": {
            "type": "boolean",
            "description": "Whether a list was cut at 200 items."
          },
          "applied": {
            "type": "boolean",
            "description": "`false` for a preview (`dry_run=true`)."
          },
          "imported": {
            "type": "integer",
            "description": "Applied imports only: terms added."
          },
          "replaced": {
            "type": "integer",
            "description": "Applied imports only: terms replaced."
          },
          "skipped": {
            "type": "integer",
            "description": "Applied imports only: terms left as they were."
          }
        },
        "required": [
          "format",
          "strategy",
          "counts",
          "new",
          "conflicts",
          "duplicates",
          "errors",
          "truncated",
          "applied"
        ],
        "additionalProperties": true
      },
      "JsonResult": {
        "type": "object",
        "description": "The JSON result (`?format=json`).",
        "properties": {
          "schema_version": {
            "type": "integer"
          },
          "request_id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/RequestStatus"
          },
          "complete": {
            "type": "boolean"
          },
          "series": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Identifier (UUID)."
                  },
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "name"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "volume": {
            "type": "object",
            "properties": {
              "project_id": {
                "type": "string",
                "description": "Identifier (UUID)."
              },
              "external_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "number": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "title": {
                "type": "string"
              }
            },
            "required": [
              "project_id",
              "external_id",
              "number",
              "title"
            ]
          },
          "source_language": {
            "type": "string"
          },
          "target_language": {
            "type": "string"
          },
          "strategy": {
            "type": "object",
            "description": "Provider name and model (never its address or key), `quality`, `context_backend`, `final_review`."
          },
          "incomplete_chapters": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "chapters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResultChapter"
            }
          },
          "report": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CompletionReport"
              },
              {
                "type": "null"
              }
            ]
          },
          "scope": {
            "type": "string",
            "enum": [
              "request",
              "new",
              "volume"
            ],
            "description": "What the result covers: the request's chapters, only the new ones, or the whole volume."
          }
        },
        "required": [
          "schema_version",
          "request_id",
          "external_id",
          "status",
          "complete",
          "series",
          "volume",
          "source_language",
          "target_language",
          "strategy",
          "incomplete_chapters",
          "chapters",
          "report"
        ],
        "additionalProperties": true
      },
      "OutputOptions": {
        "additionalProperties": false,
        "properties": {
          "format": {
            "default": "json",
            "enum": [
              "json",
              "txt",
              "txt-zip",
              "epub-bilingual"
            ],
            "type": "string",
            "description": "Default format of the result; `epub-bilingual` is a bilingual EPUB for proofreading."
          }
        },
        "type": "object"
      },
      "PipelineOptions": {
        "additionalProperties": false,
        "properties": {
          "start": {
            "default": true,
            "type": "boolean",
            "description": "Run the whole pipeline (needs the `pipeline:start` scope); false only imports."
          },
          "provider_id": {
            "maxLength": 36,
            "type": [
              "string",
              "null"
            ],
            "default": null,
            "description": "Provider to use; defaults to the volume's, then the series' provider."
          },
          "quality": {
            "enum": [
              "fast",
              "normal",
              "high",
              "maximum"
            ],
            "type": [
              "string",
              "null"
            ],
            "default": null
          },
          "context_backend": {
            "enum": [
              "internal",
              "openviking",
              "hybrid"
            ],
            "type": [
              "string",
              "null"
            ],
            "default": null
          },
          "final_review": {
            "default": true,
            "type": "boolean",
            "description": "Run the final review (never when the server disables it)."
          },
          "priority": {
            "enum": [
              "low",
              "normal",
              "high"
            ],
            "type": [
              "string",
              "null"
            ],
            "default": null,
            "description": "Place in the fair queue: `low`, `normal` (default) or `high`, within the token's `max_priority` and its account's ceiling (otherwise `403 priority_not_allowed`). Does not change what the request is."
          },
          "analysis_mode": {
            "enum": [
              "parallel",
              "strict"
            ],
            "type": [
              "string",
              "null"
            ],
            "default": null,
            "description": "`parallel`: the passages are analysed side by side, then each is reconciled with what precedes it; `strict`: one passage after the other. Default: the volume's choice, else `ANALYSIS_MODE`."
          },
          "threads": {
            "maximum": 64,
            "minimum": 1,
            "type": [
              "integer",
              "null"
            ],
            "default": null,
            "description": "Passages worked on at once, analysis and translation alike (1–64). It can only lower the volume's share of the provider's capacity; default: that share."
          }
        },
        "type": "object"
      },
      "Progress": {
        "type": "object",
        "properties": {
          "segments": {
            "type": "integer",
            "description": "Passages of the request's chapters (the whole book for an EPUB)."
          },
          "translated": {
            "type": "integer"
          },
          "percent": {
            "type": "integer"
          },
          "stages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StageProgress"
            },
            "description": "The volume's progress per stage."
          },
          "analysis": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/AnalysisPhase"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "segments",
          "translated",
          "percent",
          "stages",
          "analysis"
        ]
      },
      "Provider": {
        "type": "object",
        "description": "A provider a request may name: identity and model only, never an address or a key.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "description": "`openai`, `openai_direct`, `openai_responses`, `anthropic` or `codex_chatgpt`."
          },
          "model": {
            "type": "string"
          },
          "created_at": {
            "type": "number",
            "description": "Unix time in seconds."
          },
          "default_for_series": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Your series that use this provider by default."
          }
        },
        "required": [
          "id",
          "name",
          "kind",
          "model",
          "created_at",
          "default_for_series"
        ],
        "additionalProperties": true
      },
      "QualityReport": {
        "type": "object",
        "description": "Quality scores (0–100) of the passages the request covers, computed from the signals Libris records (checks, critiques, doubts, failed calls, recoveries, retained originals); no model call.",
        "properties": {
          "scored": {
            "type": "integer",
            "description": "Passages with a score."
          },
          "average": {
            "type": [
              "number",
              "null"
            ],
            "description": "Average score (0–100); `null` when none is scored."
          },
          "minimum": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Lowest score."
          },
          "bands": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Passages per band: `good` (85 and above), `fair` (70), `weak` (50), `poor` (below)."
          },
          "histogram": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Passages per ten-point bucket, from 0–9 to 90–100."
          },
          "to_review": {
            "type": "integer",
            "description": "Passages below `review_below` that no person validated."
          },
          "review_below": {
            "type": "integer",
            "description": "The score under which a passage should be reviewed."
          },
          "weakest_chapters": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "The 10 chapters with the lowest scores: `chapter_id`, `title`, `external_id`, `number`, `passages`, `scored`, `average`, `minimum`, `weak`…"
          },
          "review_first": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "The 10 passages to review first: `segment_id`, `chapter_id`, `position`, `score`, `band` and the `signals` (`code`, `count`, `penalty`) that lowered them."
          }
        },
        "required": [
          "scored",
          "average",
          "minimum",
          "bands",
          "histogram",
          "to_review",
          "review_below",
          "weakest_chapters",
          "review_first"
        ],
        "additionalProperties": true
      },
      "QueuePlace": {
        "type": "object",
        "description": "Where a request that has not started yet stands in the fair queue.",
        "properties": {
          "position": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Place in the line of its provider (1: next); `null` when it waits for its volume."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why it waits: `starting`, `provider_busy`, `account_limit`, `token_limit`, `retry_scheduled`, `provider_missing`, or `volume_busy` while another job holds the volume."
          },
          "effective_priority": {
            "type": "string",
            "enum": [
              "low",
              "normal",
              "high"
            ],
            "description": "Its priority, raised one level after a long wait."
          },
          "next_attempt": {
            "type": "number",
            "description": "When a waiting job retries (Unix time, `0` when not waiting)."
          }
        },
        "required": [
          "position",
          "reason"
        ],
        "additionalProperties": true
      },
      "RequestDetail": {
        "type": "object",
        "description": "The status document of a request.",
        "properties": {
          "request_id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your identifier of the request."
          },
          "series_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier (UUID)."
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The volume (UUID)."
          },
          "job_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The pipeline job; `null` while the request waits (`queued`)."
          },
          "input": {
            "type": "string",
            "enum": [
              "epub",
              "txt",
              "docx",
              "json"
            ],
            "description": "What was sent."
          },
          "status": {
            "$ref": "#/components/schemas/RequestStatus"
          },
          "status_url": {
            "type": "string",
            "description": "Path of the status document."
          },
          "result_url": {
            "type": "string",
            "description": "Path of the result."
          },
          "created_at": {
            "type": "number",
            "description": "Unix time in seconds."
          },
          "updated_at": {
            "type": "number",
            "description": "Unix time in seconds."
          },
          "finished_at": {
            "type": [
              "number",
              "null"
            ],
            "description": "Unix time in seconds."
          },
          "stage": {
            "type": [
              "string",
              "null"
            ],
            "description": "Current stage of the volume (`null` without a job)."
          },
          "step": {
            "type": [
              "string",
              "null"
            ],
            "description": "Current step of the job, for example `translation`, `final_review`."
          },
          "progress": {
            "$ref": "#/components/schemas/Progress"
          },
          "estimate": {
            "type": [
              "object",
              "null"
            ],
            "description": "Remaining time and cost, once enough calls were observed."
          },
          "error": {
            "type": "string",
            "description": "Why the job stopped or failed; empty otherwise."
          },
          "stop_reason": {
            "type": "string"
          },
          "next_attempt": {
            "type": "number",
            "description": "When a waiting job retries (Unix time, `0` when not waiting)."
          },
          "chapters": {
            "$ref": "#/components/schemas/ChapterCounts"
          },
          "options": {
            "type": "object",
            "properties": {
              "start": {
                "type": "boolean"
              },
              "final_review": {
                "type": "boolean"
              },
              "output_format": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "analysis_mode": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "parallel",
                  "strict",
                  null
                ]
              },
              "threads": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "required": [
              "start",
              "final_review",
              "output_format",
              "analysis_mode",
              "threads"
            ]
          },
          "priority": {
            "type": "string",
            "enum": [
              "low",
              "normal",
              "high"
            ],
            "description": "The request's priority, as changed by a person in the interface if it was."
          },
          "queue": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/QueuePlace"
              },
              {
                "type": "null"
              }
            ]
          },
          "result": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/StoredResult"
              },
              {
                "type": "null"
              }
            ]
          },
          "report": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CompletionReport"
              },
              {
                "type": "null"
              }
            ]
          },
          "webhook": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/WebhookState"
              },
              {
                "type": "null"
              }
            ]
          },
          "chapter_events": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ChapterEvents"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "request_id",
          "external_id",
          "series_id",
          "project_id",
          "job_id",
          "input",
          "status",
          "status_url",
          "result_url",
          "created_at",
          "updated_at",
          "finished_at",
          "stage",
          "step",
          "progress",
          "estimate",
          "error",
          "stop_reason",
          "next_attempt",
          "chapters",
          "options",
          "priority",
          "queue",
          "result",
          "report",
          "webhook",
          "chapter_events"
        ],
        "additionalProperties": true
      },
      "RequestStatus": {
        "type": "string",
        "enum": [
          "queued",
          "imported",
          "pending",
          "running",
          "paused",
          "waiting",
          "blocked",
          "finalizing",
          "completed",
          "completed_with_residuals",
          "failed",
          "cancelled"
        ],
        "description": "`queued`: waiting for its volume. `imported`: chapters imported, nothing started (end state). `pending`/`running`: the job waits for a worker or works. `paused`, `waiting` (provider temporarily unavailable, retried), `blocked` (needs attention). `finalizing`: the result is being built. End states: `completed`, `completed_with_residuals` (some passages kept their source, listed in the report), `failed`, `cancelled`."
      },
      "RequestSummary": {
        "type": "object",
        "description": "A translation request, as answered when it is created.",
        "properties": {
          "request_id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your identifier of the request."
          },
          "series_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier (UUID)."
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The volume (UUID)."
          },
          "job_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The pipeline job; `null` while the request waits (`queued`)."
          },
          "input": {
            "type": "string",
            "enum": [
              "epub",
              "txt",
              "docx",
              "json"
            ],
            "description": "What was sent."
          },
          "status": {
            "$ref": "#/components/schemas/RequestStatus"
          },
          "status_url": {
            "type": "string",
            "description": "Path of the status document."
          },
          "result_url": {
            "type": "string",
            "description": "Path of the result."
          }
        },
        "required": [
          "request_id",
          "external_id",
          "series_id",
          "project_id",
          "job_id",
          "input",
          "status",
          "status_url",
          "result_url"
        ]
      },
      "Residual": {
        "type": "object",
        "description": "A passage delivered in its source text.",
        "properties": {
          "segment_id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "chapter_id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "chapter_external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "chapter_title": {
            "type": "string"
          },
          "position": {
            "type": "integer"
          },
          "status": {
            "type": "string"
          },
          "kept": {
            "type": "string",
            "enum": [
              "source"
            ]
          },
          "reason": {
            "type": "string",
            "description": "The autopilot's reason, the passage's last error, or `source_retained`, `untranslated`, `markup_mismatch`, `epubcheck_repair`."
          }
        },
        "required": [
          "segment_id",
          "kept",
          "reason"
        ]
      },
      "ResultChapter": {
        "type": "object",
        "properties": {
          "chapter_id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "number": {
            "type": [
              "number",
              "null"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "chapter",
              "prologue",
              "interlude",
              "side_story",
              "extra",
              "epilogue",
              "afterword",
              "author_note",
              "front_matter"
            ]
          },
          "part": {
            "type": [
              "integer",
              "null"
            ]
          },
          "part_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "label": {
            "type": "string"
          },
          "display_label": {
            "type": "string",
            "description": "The label in the target language (`Chapitre 12 (partie 2)`)."
          },
          "position": {
            "type": "integer",
            "description": "Reading position: chapters are listed in this order."
          },
          "title": {
            "type": "string"
          },
          "translated_title": {
            "type": "string"
          },
          "complete": {
            "type": "boolean"
          },
          "missing_segments": {
            "type": "integer"
          },
          "translation": {
            "type": "string",
            "description": "The chapter's text; missing passages keep their source text."
          },
          "source_sha256": {
            "type": [
              "string",
              "null"
            ],
            "description": "SHA-256 of the normalized source text (`null` when not recorded)."
          },
          "sha256": {
            "type": "string",
            "description": "SHA-256 of `translation` (UTF-8)."
          },
          "review": {
            "type": "object",
            "properties": {
              "segments": {
                "type": "integer"
              },
              "validated": {
                "type": "integer"
              },
              "flagged": {
                "type": "integer"
              }
            },
            "required": [
              "segments",
              "validated",
              "flagged"
            ]
          },
          "issues": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Unresolved quality issues: `segment_id`, `severity`, `code`, `message`."
          },
          "flagged_passages": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Passages still flagged: `segment_id`, `position`, `status`."
          }
        },
        "required": [
          "chapter_id",
          "external_id",
          "number",
          "kind",
          "part",
          "part_count",
          "label",
          "display_label",
          "position",
          "title",
          "translated_title",
          "complete",
          "missing_segments",
          "translation",
          "source_sha256",
          "sha256",
          "review",
          "issues",
          "flagged_passages"
        ],
        "additionalProperties": true
      },
      "SeriesDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "source_language": {
            "type": [
              "string",
              "null"
            ]
          },
          "target_language": {
            "type": [
              "string",
              "null"
            ]
          },
          "archived": {
            "type": "boolean"
          },
          "volumes": {
            "type": "integer"
          },
          "created_at": {
            "type": "number",
            "description": "Unix time in seconds."
          },
          "updated_at": {
            "type": "number",
            "description": "Unix time in seconds."
          },
          "volume_list": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VolumeSummary"
            },
            "description": "Sorted by volume number."
          }
        },
        "required": [
          "id",
          "name",
          "kind",
          "source_language",
          "target_language",
          "archived",
          "volumes",
          "created_at",
          "updated_at",
          "volume_list"
        ],
        "additionalProperties": true
      },
      "SeriesReference": {
        "additionalProperties": false,
        "properties": {
          "id": {
            "maxLength": 36,
            "type": [
              "string",
              "null"
            ],
            "default": null,
            "description": "A series you own."
          },
          "name": {
            "maxLength": 500,
            "type": [
              "string",
              "null"
            ],
            "default": null,
            "description": "Found by name; created when missing unless `create_if_missing` is false."
          },
          "create_if_missing": {
            "default": true,
            "type": "boolean",
            "description": "Create the series named by `name` when it does not exist."
          }
        },
        "type": "object"
      },
      "SeriesSharedGlossary": {
        "type": "object",
        "description": "The shared glossary the series follows, with its terms; `null` when it follows none.",
        "properties": {
          "glossary": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/SharedGlossaryDetail"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "glossary"
        ]
      },
      "SeriesSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "source_language": {
            "type": [
              "string",
              "null"
            ]
          },
          "target_language": {
            "type": [
              "string",
              "null"
            ]
          },
          "archived": {
            "type": "boolean"
          },
          "volumes": {
            "type": "integer"
          },
          "created_at": {
            "type": "number",
            "description": "Unix time in seconds."
          },
          "updated_at": {
            "type": "number",
            "description": "Unix time in seconds."
          }
        },
        "required": [
          "id",
          "name",
          "kind",
          "source_language",
          "target_language",
          "archived",
          "volumes",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": true
      },
      "SharedGlossary": {
        "type": "object",
        "description": "A named glossary several series of the same universe follow. Its accepted terms come after the book's and the series' own (book > series > shared glossary).",
        "properties": {
          "id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "source_language": {
            "type": [
              "string",
              "null"
            ]
          },
          "target_language": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "number",
            "description": "Unix time in seconds."
          },
          "updated_at": {
            "type": "number",
            "description": "Unix time in seconds."
          },
          "term_count": {
            "type": "integer"
          },
          "locked_count": {
            "type": "integer",
            "description": "Locked terms: enforced and checked in every passage."
          },
          "series": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Identifier (UUID)."
                },
                "name": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "name"
              ]
            },
            "description": "The series that follow it."
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "source_language",
          "target_language",
          "created_at",
          "updated_at",
          "term_count",
          "locked_count",
          "series"
        ],
        "additionalProperties": true
      },
      "SharedGlossaryAttachment": {
        "type": "object",
        "description": "The shared glossary a series follows.",
        "properties": {
          "glossary_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The shared glossary to follow; `null` detaches the series."
          }
        },
        "required": [
          "glossary_id"
        ],
        "additionalProperties": false
      },
      "SharedGlossaryDetail": {
        "type": "object",
        "description": "A shared glossary with its terms.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "source_language": {
            "type": [
              "string",
              "null"
            ]
          },
          "target_language": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "number",
            "description": "Unix time in seconds."
          },
          "updated_at": {
            "type": "number",
            "description": "Unix time in seconds."
          },
          "term_count": {
            "type": "integer"
          },
          "locked_count": {
            "type": "integer",
            "description": "Locked terms: enforced and checked in every passage."
          },
          "series": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Identifier (UUID)."
                },
                "name": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "name"
              ]
            },
            "description": "The series that follow it."
          },
          "terms": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SharedTerm"
            },
            "description": "Sorted by source."
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "source_language",
          "target_language",
          "created_at",
          "updated_at",
          "term_count",
          "locked_count",
          "series",
          "terms"
        ],
        "additionalProperties": true
      },
      "SharedGlossaryInput": {
        "type": "object",
        "description": "A new shared glossary.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "1–200 characters, unique among your shared glossaries."
          },
          "description": {
            "type": "string",
            "maxLength": 4000,
            "default": "",
            "description": "At most 4000 characters."
          },
          "source_language": {
            "type": [
              "string",
              "null"
            ],
            "description": "BCP 47 tag; with the target, the glossary only applies to volumes of that pair."
          },
          "target_language": {
            "type": [
              "string",
              "null"
            ],
            "description": "BCP 47 tag."
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": false
      },
      "SharedTerm": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "source": {
            "type": "string"
          },
          "translation": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "locked": {
            "type": "boolean",
            "description": "Enforced by the pipeline and the autopilot, checked in every passage."
          },
          "accepted": {
            "type": "boolean",
            "description": "Only accepted terms are applied."
          }
        },
        "required": [
          "id",
          "source",
          "translation",
          "category",
          "description",
          "locked",
          "accepted"
        ],
        "additionalProperties": true
      },
      "StageProgress": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "`import`, `analysis`, `translation`, `review` or `export`."
          },
          "done": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "percent": {
            "type": "integer"
          }
        },
        "required": [
          "key",
          "done",
          "total",
          "percent"
        ]
      },
      "StoredResult": {
        "type": "object",
        "description": "The result file stored when the request ended successfully.",
        "properties": {
          "format": {
            "type": "string",
            "enum": [
              "epub",
              "json",
              "txt",
              "txt-zip",
              "epub-bilingual"
            ]
          },
          "media_type": {
            "type": "string"
          },
          "filename": {
            "type": "string"
          },
          "size": {
            "type": "integer",
            "description": "Bytes."
          },
          "sha256": {
            "type": "string"
          },
          "created_at": {
            "type": "number",
            "description": "Unix time in seconds."
          }
        },
        "required": [
          "format",
          "media_type",
          "filename",
          "size",
          "sha256",
          "created_at"
        ]
      },
      "TranslationPayload": {
        "additionalProperties": false,
        "properties": {
          "external_id": {
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$",
            "type": [
              "string",
              "null"
            ],
            "default": null,
            "description": "Your identifier of the request: a letter or digit, then letters, digits and `._:/-` (200 at most). Unique per owner: the same content sent again returns the original request."
          },
          "series": {
            "$ref": "#/components/schemas/SeriesReference",
            "description": "The series: `id` (one you own) or `name`."
          },
          "volume": {
            "$ref": "#/components/schemas/VolumeReference",
            "description": "The volume: found by `external_id`, then by `number` in the series; otherwise created."
          },
          "author": {
            "default": "",
            "maxLength": 500,
            "type": "string",
            "description": "Author of the volume."
          },
          "source_language": {
            "maxLength": 35,
            "type": "string",
            "description": "BCP 47 tag such as `en`, `fr-FR`, `zh-Hant`."
          },
          "target_language": {
            "maxLength": 35,
            "type": "string",
            "description": "BCP 47 tag."
          },
          "chapters": {
            "items": {
              "$ref": "#/components/schemas/ChapterInput"
            },
            "minItems": 1,
            "type": "array",
            "description": "1 to `API_MAX_CHAPTERS` chapters; numbers (with their part) and `external_id`s must not repeat. Numbered chapters are placed by number and part; a special after the chapter it follows in this list."
          },
          "replace_changed_chapters": {
            "default": false,
            "type": "boolean",
            "description": "Replace chapters already imported with another text (otherwise `409 chapter_conflict`). Unchanged passages keep their translation."
          },
          "discard_human": {
            "default": false,
            "type": "boolean",
            "description": "With `replace_changed_chapters`: allow dropping passages a person edited or validated (otherwise `409 conflict`)."
          },
          "pipeline": {
            "$ref": "#/components/schemas/PipelineOptions",
            "description": "How to run the pipeline."
          },
          "output": {
            "$ref": "#/components/schemas/OutputOptions",
            "description": "Default format of the result."
          },
          "callback_url": {
            "maxLength": 2000,
            "type": [
              "string",
              "null"
            ],
            "default": null,
            "description": "Webhook called when the request ends; its host must be allowed by an administrator."
          },
          "callback_events": {
            "items": {
              "const": "chapters.translated",
              "type": "string"
            },
            "maxItems": 5,
            "type": "array",
            "description": "Extra webhooks on top of `translation_request.finished`: `chapters.translated` sends one per batch of chapters translated while the job runs."
          }
        },
        "required": [
          "series",
          "volume",
          "source_language",
          "target_language",
          "chapters"
        ],
        "type": "object",
        "example": {
          "external_id": "saga-volume-12",
          "series": {
            "name": "The Synthetic Saga",
            "create_if_missing": true
          },
          "volume": {
            "external_id": "volume-12",
            "number": 12,
            "title": "Volume 12"
          },
          "author": "A. Author",
          "source_language": "en",
          "target_language": "fr",
          "chapters": [
            {
              "external_id": "chapter-001",
              "number": 1,
              "title": "Chapter 1",
              "content": "First paragraph.\n\nSecond paragraph.\n"
            }
          ],
          "pipeline": {
            "start": true,
            "quality": "high",
            "context_backend": "hybrid",
            "final_review": true
          },
          "output": {
            "format": "json"
          },
          "callback_url": "https://hooks.example.org/libris"
        },
        "description": "A JSON translation request. Unknown fields are refused; nothing is ever downloaded from a URL it names."
      },
      "Usage": {
        "type": "object",
        "description": "Model calls of the request's job.",
        "properties": {
          "calls": {
            "type": "integer"
          },
          "prompt_tokens": {
            "type": "integer"
          },
          "completion_tokens": {
            "type": "integer"
          },
          "cached_calls": {
            "type": "integer"
          },
          "cost": {
            "type": [
              "number",
              "null"
            ],
            "description": "Cost of the calls with a known price; `null` when none had one."
          }
        },
        "required": [
          "calls",
          "prompt_tokens",
          "completion_tokens",
          "cached_calls",
          "cost"
        ]
      },
      "ValidationIssue": {
        "type": "object",
        "properties": {
          "loc": {
            "type": "array",
            "items": {
              "type": [
                "string",
                "integer"
              ]
            },
            "description": "Where the problem is, for example `[\"chapters\", 0, \"number\"]`."
          },
          "msg": {
            "type": "string",
            "description": "What is wrong (the submitted value is never repeated)."
          },
          "type": {
            "type": "string",
            "description": "Kind of problem."
          }
        },
        "required": [
          "loc",
          "msg"
        ]
      },
      "VolumeReference": {
        "additionalProperties": false,
        "properties": {
          "external_id": {
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$",
            "type": [
              "string",
              "null"
            ],
            "default": null,
            "description": "Your identifier of the volume."
          },
          "latest": {
            "default": false,
            "type": "boolean",
            "description": "Follow up a webnovel: the chapters go to the series' last numbered volume (else its continuous chapter container, else volume 1). Not with `number`."
          },
          "number": {
            "maximum": 10000,
            "minimum": 1,
            "type": [
              "integer",
              "null"
            ],
            "default": null,
            "description": "Volume number, 1–10000. Required unless `latest` is true."
          },
          "title": {
            "default": "",
            "maxLength": 500,
            "type": "string",
            "description": "Title of a new volume (default: “Series — number”)."
          }
        },
        "type": "object"
      },
      "VolumeSummary": {
        "type": "object",
        "properties": {
          "project_id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "title": {
            "type": "string"
          },
          "volume_number": {
            "type": [
              "integer",
              "null"
            ]
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "source_format": {
            "type": "string"
          },
          "project_kind": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "chapters": {
            "type": "integer"
          }
        },
        "required": [
          "project_id",
          "title",
          "volume_number",
          "external_id",
          "source_format",
          "project_kind",
          "status",
          "chapters"
        ],
        "additionalProperties": true
      },
      "WebhookEvent": {
        "type": "object",
        "description": "Sent once when a request ends (any end state, `imported` included).",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "translation_request.finished"
            ]
          },
          "request_id": {
            "type": "string",
            "description": "Identifier (UUID)."
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/RequestStatus"
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "job_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "status_url": {
            "type": "string"
          },
          "result_url": {
            "type": "string"
          },
          "artifact": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "format": {
                    "type": "string"
                  },
                  "size": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  }
                },
                "required": [
                  "format",
                  "size",
                  "sha256"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "report": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CompletionReport"
              },
              {
                "type": "null"
              }
            ]
          },
          "finished_at": {
            "type": [
              "number",
              "null"
            ],
            "description": "Unix time in seconds."
          }
        },
        "required": [
          "event",
          "request_id",
          "external_id",
          "status",
          "error",
          "project_id",
          "job_id",
          "status_url",
          "result_url",
          "artifact",
          "report",
          "finished_at"
        ],
        "additionalProperties": true
      },
      "WebhookState": {
        "type": "object",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "pending",
              "delivered",
              "failed"
            ]
          },
          "attempts": {
            "type": "integer"
          },
          "error": {
            "type": "string",
            "description": "Last failure, empty when none."
          }
        },
        "required": [
          "state",
          "attempts",
          "error"
        ]
      }
    },
    "securitySchemes": {
      "bearerToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "lbr_<8 characters>_<secret>",
        "description": "An API token created in the interface (My account › API tokens)."
      }
    }
  }
}
