{
  "openapi": "3.1.0",
  "info": {
    "title": "iDMS API",
    "version": "2.0",
    "description": "Intelligent document management for Swiss property management.\n\nHuman-readable reference with examples, the German enum glossary and the\nfull classification model lives at `/docs` (source: `docs/API_V2.md`).\n\nConventions:\n- Every response uses the envelope `{ data, meta, error }`.\n- All endpoints require a Bearer API key and are rate-limited to\n  100 requests/minute per key (`X-RateLimit-*` headers on every response).\n- The API never returns `403` for cross-org access — out-of-org documents are a\n  `404`, indistinguishable from a missing resource.\n",
    "contact": {
      "name": "mory.ai",
      "url": "https://idms.mory.ai/docs"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://mory.ai"
    }
  },
  "servers": [
    {
      "url": "https://idms.mory.ai"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/api/v2/documents": {
      "get": {
        "operationId": "listDocuments",
        "summary": "List documents",
        "tags": [
          "Documents — read"
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "doc_type",
            "in": "query",
            "description": "Exact match on the legacy doc_type (e.g. invoice).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "subtype",
            "in": "query",
            "description": "Exact match on the current-model doc_subtype.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "intent",
            "in": "query",
            "description": "Exact match on the current-model intent.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "legacy pipeline status (e.g. completed).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "zahlungsstatus",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "offen",
                "bezahlt",
                "teilbezahlt",
                "ueberfaellig",
                "sonstiges"
              ]
            }
          },
          {
            "name": "bereich",
            "in": "query",
            "description": "Current-model Bereich key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "description": "Tag key; repeatable for OR semantics.",
            "schema": {
              "type": "string"
            },
            "explode": true
          },
          {
            "name": "betrag_min",
            "in": "query",
            "description": "Minimum extracted total amount.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "betrag_max",
            "in": "query",
            "description": "Maximum extracted total amount.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "faellig_after",
            "in": "query",
            "description": "Due date on/after this date (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "faellig_before",
            "in": "query",
            "description": "Due date on/before this date (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "external_id",
            "in": "query",
            "description": "Exact lookup by your external_id (idempotency key).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "description": "Documents with updated_at after this ISO-8601 timestamp, ordered oldest-first (delta sync); pair with meta.server_time as the next cursor. Malformed value → 400.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "profile",
            "in": "query",
            "description": "Output profile key (provisioned per account).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated document list.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Document"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PageMeta"
                    },
                    "error": {
                      "type": "null"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "patch": {
        "operationId": "bulkPatchDocuments",
        "summary": "Bulk-update classification on up to 100 documents",
        "tags": [
          "Documents — write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "items"
                ],
                "properties": {
                  "items": {
                    "type": "array",
                    "maxItems": 100,
                    "items": {
                      "allOf": [
                        {
                          "type": "object",
                          "required": [
                            "id"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            }
                          }
                        },
                        {
                          "$ref": "#/components/schemas/ClassificationPatch"
                        }
                      ]
                    }
                  },
                  "reason": {
                    "type": "string",
                    "description": "Audit-log reason."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-item results; partial failures do not abort the batch.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "ok",
                              "error"
                            ]
                          },
                          "error": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "doc": {
                            "description": "Full updated detail on success; absent on error.",
                            "oneOf": [
                              {
                                "$ref": "#/components/schemas/DocumentDetail"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          }
                        }
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "ok": {
                          "type": "integer"
                        },
                        "error": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/UnknownTaxonomyKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v2/documents/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "operationId": "getDocument",
        "summary": "Get one document (full detail)",
        "tags": [
          "Documents — read"
        ],
        "parameters": [
          {
            "name": "profile",
            "in": "query",
            "description": "Output profile key; rewrites the response shape.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Full document with classification, metadata, contacts and linked entities.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DocumentDetail"
                    },
                    "meta": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "profile": {
                          "type": "object"
                        },
                        "missing_required_fields": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "error": {
                      "type": "null"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "patch": {
        "operationId": "patchDocument",
        "summary": "Update classification fields on one document",
        "tags": [
          "Documents — write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ClassificationPatch"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "reason": {
                        "type": "string",
                        "description": "Audit-log reason."
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/DocumentDetailResponse"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnknownTaxonomyKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v2/documents/{id}/classification": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "patch": {
        "operationId": "patchClassification",
        "summary": "Update classification (alias of PATCH /documents/:id)",
        "tags": [
          "Documents — write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ClassificationPatch"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "reason": {
                        "type": "string"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/DocumentDetailResponse"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnknownTaxonomyKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v2/documents/{id}/metadata": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "patch": {
        "operationId": "patchMetadata",
        "summary": "Merge fields into extracted_metadata",
        "tags": [
          "Documents — write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Arbitrary JSON keys are merged into extracted_metadata. `reason` is consumed for the audit log, not stored.\n",
                "additionalProperties": true,
                "properties": {
                  "reason": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/DocumentDetailResponse"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v2/documents/{id}/contacts": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "operationId": "getDocumentContacts",
        "summary": "List contacts linked to a document",
        "description": "Contact ids are org-level and may disappear when duplicates are merged — treat document_id as the stable key and re-fetch rather than caching contact_id long-term.\n",
        "tags": [
          "Documents — read"
        ],
        "responses": {
          "200": {
            "description": "Normalized Person/Firma contacts with their role on this document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Contact"
                      }
                    },
                    "error": {
                      "type": "null"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "patch": {
        "operationId": "patchDocumentContacts",
        "summary": "Replace the document's contact links",
        "tags": [
          "Documents — write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "contacts"
                ],
                "properties": {
                  "contacts": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "contact_id",
                        "role"
                      ],
                      "properties": {
                        "contact_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "role": {
                          "type": "string",
                          "enum": [
                            "absender",
                            "empfaenger",
                            "cc"
                          ]
                        }
                      }
                    }
                  },
                  "reason": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated contacts array (same shape as GET).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Contact"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v2/documents/{id}/tags": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "operationId": "addTags",
        "summary": "Add tags from the controlled taxonomy",
        "tags": [
          "Documents — write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tags"
                ],
                "properties": {
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/TagsResponse"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnknownTaxonomyKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "delete": {
        "operationId": "removeTags",
        "summary": "Remove tags",
        "tags": [
          "Documents — write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tags"
                ],
                "properties": {
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/TagsResponse"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v2/documents/{id}/download": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "operationId": "getDownloadUrl",
        "summary": "Get a short-lived signed download URL",
        "description": "File bytes never travel through the API — follow `url` directly to storage.",
        "tags": [
          "Documents — read"
        ],
        "parameters": [
          {
            "name": "ttl",
            "in": "query",
            "description": "Seconds; clamped to [60, 86400].",
            "schema": {
              "type": "integer",
              "default": 3600
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Signed URL plus file metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "filename": {
                          "type": "string"
                        },
                        "content_type": {
                          "type": "string"
                        },
                        "file_size": {
                          "type": "integer"
                        }
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "ttl_seconds": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v2/documents/upload": {
      "post": {
        "operationId": "uploadDocument",
        "summary": "Upload a single file or a ZIP container (multipart, ≤ 4 MB)",
        "description": "Max 4 MB per request (serverless body limit) — use the presigned flow (`/upload/init` + `/upload/finalize`) for files up to 250 MB. A `.zip` file is treated as a container: each entry becomes its own document with per-entry idempotency and per-entry accept/reject results. Microsoft Office lock files (\"~$…\") are rejected with 400.\n",
        "tags": [
          "Documents — upload"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "external_id": {
                    "type": "string",
                    "description": "Caller's stable id — primary idempotency key."
                  },
                  "metadata": {
                    "type": "string",
                    "description": "JSON string stored on the document."
                  },
                  "force": {
                    "type": "string",
                    "enum": [
                      "true"
                    ],
                    "description": "Testing aid — bypass content-hash idempotency and process as a new document (stored without a content hash; cannot reuse an existing external_id)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created document, idempotent replay, or per-entry ZIP results.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "title": "Single file",
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/UploadedDocument"
                        },
                        "meta": {
                          "$ref": "#/components/schemas/IdempotencyMeta"
                        }
                      }
                    },
                    {
                      "title": "ZIP container",
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "container_kind": {
                              "type": "string",
                              "const": "zip"
                            },
                            "container_filename": {
                              "type": "string"
                            },
                            "items": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/ZipItem"
                              }
                            }
                          }
                        },
                        "meta": {
                          "type": "object",
                          "properties": {
                            "container_kind": {
                              "type": "string",
                              "const": "zip"
                            },
                            "total_entries": {
                              "type": "integer"
                            },
                            "accepted": {
                              "type": "integer"
                            },
                            "idempotent_replay": {
                              "type": "integer"
                            },
                            "rejected": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "description": "ZIP container not readable, or over 50 entries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v2/documents/upload/init": {
      "post": {
        "operationId": "presignedUploadInit",
        "summary": "Presigned upload step 1: get a signed PUT URL (≤ 250 MB)",
        "tags": [
          "Documents — upload"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "filename",
                  "file_size",
                  "mime_type"
                ],
                "properties": {
                  "filename": {
                    "type": "string"
                  },
                  "file_size": {
                    "type": "integer",
                    "description": "Bytes; max 250 MB."
                  },
                  "mime_type": {
                    "type": "string"
                  },
                  "external_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed PUT URL (or idempotent replay of an existing document).",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "title": "New upload",
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "upload_url": {
                              "type": "string",
                              "format": "uri"
                            },
                            "upload_token": {
                              "type": "string"
                            },
                            "storage_path": {
                              "type": "string"
                            },
                            "expires_at": {
                              "type": "string",
                              "format": "date-time"
                            }
                          }
                        },
                        "meta": {
                          "type": "object",
                          "properties": {
                            "idempotent_replay": {
                              "type": "boolean",
                              "const": false
                            }
                          }
                        }
                      }
                    },
                    {
                      "title": "Idempotent replay",
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/UploadedDocument"
                        },
                        "meta": {
                          "$ref": "#/components/schemas/IdempotencyMeta"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v2/documents/upload/finalize": {
      "post": {
        "operationId": "presignedUploadFinalize",
        "summary": "Presigned upload step 2: register the uploaded file",
        "tags": [
          "Documents — upload"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "storage_path",
                  "filename",
                  "mime_type",
                  "file_size"
                ],
                "properties": {
                  "storage_path": {
                    "type": "string",
                    "description": "Exactly what /upload/init returned."
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mime_type": {
                    "type": "string"
                  },
                  "file_size": {
                    "type": "integer"
                  },
                  "external_id": {
                    "type": "string"
                  },
                  "content_hash": {
                    "type": "string",
                    "description": "SHA-256 hex, client-computed; enables content dedup."
                  },
                  "force": {
                    "type": "boolean",
                    "description": "Testing aid — bypass content-hash idempotency (row stored without a content hash)."
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created document or idempotent replay.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UploadedDocument"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/IdempotencyMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "storage_path not scoped to the caller's organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "description": "Storage object not found at storage_path (PUT incomplete or wrong path).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v2/documents/upload/batch": {
      "post": {
        "operationId": "uploadBatch",
        "summary": "Upload up to 50 files in one multipart request",
        "tags": [
          "Documents — upload"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "files[]"
                ],
                "properties": {
                  "files[]": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    },
                    "maxItems": 50
                  },
                  "external_ids[]": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Paired index-wise with files[]."
                  },
                  "metadata[]": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "JSON string per file, paired index-wise."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-file results; partial failures do not abort the batch.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "index": {
                            "type": "integer"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "created",
                              "idempotent_replay",
                              "failed"
                            ]
                          },
                          "doc": {
                            "$ref": "#/components/schemas/UploadedDocument"
                          },
                          "error": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "dedup_by": {
                            "type": "string",
                            "enum": [
                              "external_id",
                              "content_hash"
                            ]
                          }
                        }
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "created": {
                          "type": "integer"
                        },
                        "idempotent_replay": {
                          "type": "integer"
                        },
                        "failed": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v2/feedback": {
      "post": {
        "operationId": "postFeedback",
        "summary": "Report classification feedback (corrected field or rejected AI draft)",
        "description": "Intake only — feedback never mutates the document or its classification. Use event_type \"classification.changed\" with field_path/old_value/new_value when your user corrected a field, or \"ai_rejected\" (no field detail needed) when your user deleted the AI draft entirely.",
        "tags": [
          "Feedback"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "document_id",
                  "event_type"
                ],
                "properties": {
                  "document_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "iDMS document id; must belong to your organization."
                  },
                  "event_type": {
                    "type": "string",
                    "enum": [
                      "classification.changed",
                      "ai_rejected"
                    ]
                  },
                  "field_path": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "description": "Required for classification.changed."
                  },
                  "old_value": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "new_value": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Feedback stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "received": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed (details in error).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Document not found in your organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v2/taxonomy": {
      "get": {
        "operationId": "getTaxonomy",
        "summary": "Full classification taxonomy (Bereiche, types, subtypes, intents, tag groups, tags)",
        "tags": [
          "Taxonomy"
        ],
        "responses": {
          "200": {
            "description": "The complete controlled vocabulary with DE/EN labels.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "bereiche": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "label_de": {
                                "type": "string"
                              },
                              "label_en": {
                                "type": "string"
                              },
                              "sort": {
                                "type": "integer"
                              }
                            }
                          }
                        },
                        "doc_types": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "bereich_key": {
                                "type": "string"
                              },
                              "label_de": {
                                "type": "string"
                              },
                              "label_en": {
                                "type": "string"
                              },
                              "v1_projection": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "doc_subtypes": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "doc_type_key": {
                                "type": "string"
                              },
                              "label_de": {
                                "type": "string"
                              },
                              "label_en": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "intents": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "label_de": {
                                "type": "string"
                              },
                              "label_en": {
                                "type": "string"
                              },
                              "v1_projection": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "tag_groups": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "label_de": {
                                "type": "string"
                              },
                              "label_en": {
                                "type": "string"
                              },
                              "sort": {
                                "type": "integer"
                              }
                            }
                          }
                        },
                        "tags": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "tag_group_key": {
                                "type": "string"
                              },
                              "label_de": {
                                "type": "string"
                              },
                              "label_en": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v2/usage": {
      "get": {
        "operationId": "getUsage",
        "summary": "Usage snapshot for your organization (API calls, documents, storage)",
        "description": "Org-scoped, read-only. The period is the current calendar month, month-to-date (UTC); period_start is echoed back. api_calls.by_route is sourced from request telemetry with 90-day retention. limits.* are null (unlimited) — placeholders for future per-org quotas.",
        "tags": [
          "Usage"
        ],
        "responses": {
          "200": {
            "description": "Current usage for the authenticated organization.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "period_start": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "api_calls": {
                          "type": "object",
                          "properties": {
                            "this_period": {
                              "type": "integer"
                            },
                            "by_route": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "route": {
                                    "type": "string"
                                  },
                                  "request_count": {
                                    "type": "integer"
                                  },
                                  "last_seen": {
                                    "type": "string",
                                    "format": "date-time"
                                  }
                                }
                              }
                            }
                          }
                        },
                        "documents": {
                          "type": "object",
                          "properties": {
                            "total_stored": {
                              "type": "integer"
                            },
                            "ingested_this_period": {
                              "type": "integer"
                            }
                          }
                        },
                        "storage": {
                          "type": "object",
                          "properties": {
                            "bytes_used": {
                              "type": "integer",
                              "format": "int64"
                            }
                          }
                        },
                        "remaining": {
                          "type": "object",
                          "properties": {
                            "rate_limit": {
                              "type": "integer"
                            },
                            "rate_limit_reset": {
                              "type": "integer",
                              "description": "Unix timestamp (seconds)."
                            }
                          }
                        },
                        "limits": {
                          "type": "object",
                          "properties": {
                            "documents_max": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "null = unlimited."
                            },
                            "api_calls_max": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "null = unlimited."
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v2/search": {
      "post": {
        "operationId": "searchDocuments",
        "summary": "Hybrid semantic + keyword search over your organization's documents",
        "description": "Matches the query by meaning (semantic) and by exact string (keyword) and fuses the two ranked lists (reciprocal-rank fusion) into one. matched_via reports which leg(s) surfaced each hit. Read-only; the org is taken from the API key. Availability is per organization — returns 403 until search is enabled for your org. A hybrid request stays available if the semantic component is briefly unavailable (it returns keyword-only results and meta.mode becomes \"keyword\"); an explicit mode:\"semantic\" request returns 503 in that case.",
        "tags": [
          "Search"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "query"
                ],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "Natural-language query."
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "hybrid",
                      "semantic",
                      "keyword"
                    ],
                    "default": "hybrid"
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 25
                  },
                  "doc_type": {
                    "type": "string",
                    "description": "Restrict to a document type (same vocabulary as GET /documents)."
                  },
                  "bereich": {
                    "type": "string",
                    "description": "Restrict to a Bereich."
                  },
                  "intent": {
                    "type": "string",
                    "description": "Restrict to an intent."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Restrict to documents carrying ALL of these tag keys."
                  },
                  "created_after": {
                    "type": "string",
                    "format": "date",
                    "description": "Only documents created on or after this date (YYYY-MM-DD)."
                  },
                  "created_before": {
                    "type": "string",
                    "format": "date",
                    "description": "Only documents created before this date (YYYY-MM-DD)."
                  },
                  "sender": {
                    "type": "string",
                    "description": "Absender — substring match on the document's sender name."
                  },
                  "receiver": {
                    "type": "string",
                    "description": "Empfänger — substring match on the document's receiver name."
                  },
                  "property": {
                    "type": "string",
                    "description": "Liegenschaft — substring match on the document's property name or address."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ranked, fused results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "document_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "filename": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "doc_type": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "score": {
                            "type": "number",
                            "description": "Fused relevance score; higher is better. Use for ordering, not as an absolute threshold."
                          },
                          "snippet": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Passage centered on the first query-term match (≤ 280 chars), HTML-escaped with matched terms wrapped in <mark>…</mark>."
                          },
                          "chunk_index": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "matched_via": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "enum": [
                                "semantic",
                                "keyword"
                              ]
                            }
                          }
                        }
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "query_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "mode": {
                          "type": "string",
                          "enum": [
                            "hybrid",
                            "semantic",
                            "keyword"
                          ],
                          "description": "The mode that actually ran."
                        },
                        "took_ms": {
                          "type": "integer"
                        },
                        "total": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid query (details in error).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Semantic search is not enabled for this organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "Explicit mode:\"semantic\" request while the semantic component is temporarily unavailable. A hybrid request degrades to keyword instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v2/search/answer": {
      "post": {
        "operationId": "searchAnswer",
        "summary": "RAG synthesized answer with cited sources (optional, search phase 3)",
        "description": "Runs the same hybrid search as POST /api/v2/search, then asks a chat LLM to synthesize an answer over the top matching excerpts. Every factual claim in the answer is cited with a bracketed excerpt number; `sources` lists only the excerpts the model actually cited, so a citation the model invents never produces a fabricated source entry. Read-only; the org is taken from the API key. Availability is per organization — returns 403 until enabled, separately from plain search (an org can use POST /api/v2/search without opting into answer synthesis). No matching documents returns a fixed \"no information\" answer with zero LLM calls. The LLM provider is EU/CH-compliant (Switzerland North), matching the residency rules the rest of the AI pipeline follows.",
        "tags": [
          "Search"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "query"
                ],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "Natural-language question."
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "hybrid",
                      "semantic",
                      "keyword"
                    ],
                    "default": "hybrid"
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10,
                    "default": 5,
                    "description": "Number of context chunks fed to the LLM."
                  },
                  "doc_type": {
                    "type": "string",
                    "description": "Restrict to a document type (same vocabulary as GET /documents)."
                  },
                  "bereich": {
                    "type": "string",
                    "description": "Restrict to a Bereich."
                  },
                  "intent": {
                    "type": "string",
                    "description": "Restrict to an intent."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Restrict to documents carrying ALL of these tag keys."
                  },
                  "created_after": {
                    "type": "string",
                    "format": "date",
                    "description": "Only documents created on or after this date (YYYY-MM-DD)."
                  },
                  "created_before": {
                    "type": "string",
                    "format": "date",
                    "description": "Only documents created before this date (YYYY-MM-DD)."
                  },
                  "sender": {
                    "type": "string",
                    "description": "Absender — substring match on the document's sender name."
                  },
                  "receiver": {
                    "type": "string",
                    "description": "Empfänger — substring match on the document's receiver name."
                  },
                  "property": {
                    "type": "string",
                    "description": "Liegenschaft — substring match on the document's property name or address."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Synthesized answer with cited sources.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "answer": {
                          "type": "string",
                          "description": "The synthesized answer. Factual claims are cited inline as [1], [2], etc."
                        },
                        "sources": {
                          "type": "array",
                          "description": "Only excerpts the model actually cited — never a fabricated reference.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "marker": {
                                "type": "string",
                                "description": "The inline citation marker, e.g. \"[1]\"."
                              },
                              "document_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "chunk_index": {
                                "type": [
                                  "integer",
                                  "null"
                                ]
                              },
                              "filename": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            }
                          }
                        }
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "query_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "mode": {
                          "type": "string",
                          "enum": [
                            "hybrid",
                            "semantic",
                            "keyword"
                          ],
                          "description": "The search mode that actually ran."
                        },
                        "took_ms": {
                          "type": "integer"
                        },
                        "chunks_used": {
                          "type": "integer",
                          "description": "Number of context chunks fed to the LLM (0 = no matching documents, fixed answer, no LLM call)."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid query (details in error).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "RAG answer synthesis is not enabled for this organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "The LLM provider is temporarily unavailable (or an explicit mode:\"semantic\" request while the semantic component is down).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "document.processed": {
      "post": {
        "summary": "Legacy event — document finished processing (deprecated nested `data` wrapper preserved).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": {
                    "type": "string",
                    "const": "document.processed"
                  },
                  "timestamp": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "classification_v3": {
                    "description": "Present for v3-opt-in organizations.",
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/ClassificationV3"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "extracted_contacts": {
                    "description": "v3-opt-in orgs only; omitted when the document has no contacts. Deduplicated contact records for this document with per-contact extraction confidence and an explicit relationships pairing signal (contact_id references a sibling entry in this same array).\n",
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/ExtractedContact"
                    }
                  },
                  "data": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx within the delivery timeout."
          }
        }
      }
    },
    "document.failed": {
      "post": {
        "summary": "Pipeline exhausted retries for a document (opt-in; clean root format).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": {
                    "type": "string",
                    "const": "document.failed"
                  },
                  "timestamp": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "document_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "external_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "error_stage": {
                    "type": "string",
                    "enum": [
                      "download",
                      "extract",
                      "ai",
                      "persist",
                      "unknown"
                    ]
                  },
                  "error_message": {
                    "type": "string",
                    "description": "Sanitized — paths/URLs stripped, 500-char cap."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx."
          }
        }
      }
    },
    "classification.changed": {
      "post": {
        "summary": "A classification axis changed via the write API (opt-in; one event per touched axis).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": {
                    "type": "string",
                    "const": "classification.changed"
                  },
                  "timestamp": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "document_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "field": {
                    "type": "string",
                    "description": "The classification axis (or tag set) that changed.",
                    "enum": [
                      "doc_type",
                      "doc_subtype",
                      "doc_bereich",
                      "intent_v3",
                      "status_lifecycle",
                      "zahlungsstatus",
                      "tags"
                    ]
                  },
                  "old": {
                    "description": "Previous value: a string, null, or — for field=tags — an array of tag keys."
                  },
                  "new": {
                    "description": "New value: a string, null, or — for field=tags — an array of tag keys."
                  },
                  "actor": {
                    "type": "object",
                    "description": "Who made the change.",
                    "properties": {
                      "kind": {
                        "type": "string",
                        "enum": [
                          "user",
                          "api_key"
                        ]
                      },
                      "id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "User id or API-key id; null for system."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx."
          }
        }
      }
    },
    "batch.completed": {
      "post": {
        "summary": "Every document in an upload batch reached a terminal state (opt-in).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": {
                    "type": "string",
                    "const": "batch.completed"
                  },
                  "timestamp": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "batch_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "total_files": {
                    "type": "integer"
                  },
                  "processed_files": {
                    "type": "integer",
                    "description": "Documents that finished (completed or failed)."
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "completed",
                      "failed"
                    ]
                  },
                  "documents": {
                    "type": "array",
                    "description": "Per-document summary for the batch.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "filename": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "completed",
                            "failed"
                          ]
                        },
                        "doc_type": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx."
          }
        }
      }
    },
    "tag.suggested": {
      "post": {
        "summary": "AI proposed a tag outside the controlled taxonomy (opt-in).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": {
                    "type": "string",
                    "const": "tag.suggested"
                  },
                  "timestamp": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "document_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "suggested_tag": {
                    "type": "string",
                    "description": "The proposed tag key not yet in the org's allowed set."
                  },
                  "confidence": {
                    "type": "number",
                    "format": "float",
                    "description": "Model confidence on the suggestion (0..1)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key from Settings → API Keys (idms_…)."
      }
    },
    "headers": {
      "XRateLimitLimit": {
        "description": "Requests allowed per minute.",
        "schema": {
          "type": "integer"
        }
      },
      "XRateLimitRemaining": {
        "description": "Requests remaining in the current window.",
        "schema": {
          "type": "integer"
        }
      },
      "XRateLimitReset": {
        "description": "Unix timestamp (seconds) when the window resets.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "DocumentDetailResponse": {
        "description": "Full updated document detail.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "data": {
                  "$ref": "#/components/schemas/DocumentDetail"
                },
                "error": {
                  "type": "null"
                }
              }
            }
          }
        }
      },
      "TagsResponse": {
        "description": "Updated tag set for the document.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "data": {
                  "type": "object",
                  "properties": {
                    "document_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "tags": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "tag_key": {
                            "type": "string"
                          },
                          "confidence": {
                            "type": [
                              "number",
                              "null"
                            ]
                          }
                        }
                      }
                    }
                  }
                },
                "meta": {
                  "type": "object",
                  "properties": {
                    "added": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "removed": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "BadRequest": {
        "description": "Invalid input — bad JSON, missing field, bad enum, DOCUMENT_LOCKED, out-of-org contact_id, Office lock file, bulk over 100 items.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "NotFound": {
        "description": "Document not in the caller's organization or no such id (never 403).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "Over 4 MB (multipart /upload) or over 250 MB (presigned).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "UnknownTaxonomyKey": {
        "description": "Unknown doc_type / doc_subtype / intent / tag key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Over 100 requests/minute — wait until X-RateLimit-Reset.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "ServerError": {
        "description": "Unexpected internal failure — retry; contact support if persistent.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    },
    "schemas": {
      "ErrorEnvelope": {
        "type": "object",
        "properties": {
          "data": {
            "type": "null"
          },
          "meta": {
            "type": "null"
          },
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "description": "Machine-readable code on selected errors (e.g. DOCUMENT_LOCKED)."
          }
        }
      },
      "PageMeta": {
        "type": "object",
        "properties": {
          "page": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "total_pages": {
            "type": "integer"
          },
          "server_time": {
            "type": "string",
            "format": "date-time",
            "description": "Server query time — the delta-sync cursor; save it and pass as the next updated_since."
          }
        }
      },
      "IdempotencyMeta": {
        "type": "object",
        "properties": {
          "idempotent_replay": {
            "type": "boolean"
          },
          "dedup_by": {
            "type": "string",
            "enum": [
              "external_id",
              "content_hash"
            ]
          },
          "forced": {
            "type": "boolean",
            "description": "Present (true) when force=true created this document bypassing idempotency."
          }
        }
      },
      "EntityRef": {
        "type": "object",
        "description": "Lightweight extraction snapshot embedded on the document row.",
        "properties": {
          "name": {
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "reference_id": {
            "type": "string"
          }
        }
      },
      "RelatedContact": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "role": {
            "type": "string"
          },
          "reference_id": {
            "type": "string"
          }
        }
      },
      "Signature": {
        "type": "object",
        "properties": {
          "signed": {
            "type": "boolean"
          },
          "signed_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "signed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "confidence": {
            "type": [
              "number",
              "null"
            ]
          },
          "raw": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          }
        }
      },
      "ClassificationV3": {
        "type": "object",
        "description": "Current classification model record (nested on document rows).",
        "properties": {
          "doc_type": {
            "type": "string"
          },
          "doc_bereich": {
            "type": "string"
          },
          "doc_subtype": {
            "type": [
              "string",
              "null"
            ]
          },
          "intent_v3": {
            "type": "string"
          },
          "status_lifecycle": {
            "type": "string",
            "enum": [
              "neu",
              "in_bearbeitung",
              "abgeschlossen",
              "sonstiges",
              "finalized",
              "deleted"
            ]
          },
          "zahlungsstatus": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "offen",
              "bezahlt",
              "teilbezahlt",
              "ueberfaellig",
              "sonstiges",
              null
            ]
          }
        }
      },
      "Document": {
        "type": "object",
        "description": "List-row shape — legacy envelope plus nested current-model classification and tags.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "filename": {
            "type": "string"
          },
          "file_type": {
            "type": "string"
          },
          "file_size": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "description": "legacy pipeline status (pending / processing / completed / failed)."
          },
          "classification": {
            "type": [
              "string",
              "null"
            ]
          },
          "classification_confidence": {
            "type": [
              "number",
              "null"
            ]
          },
          "doc_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "legacy type (see v1_projection in the taxonomy)."
          },
          "doc_subtype": {
            "type": [
              "string",
              "null"
            ]
          },
          "doc_intent": {
            "type": [
              "string",
              "null"
            ]
          },
          "sender": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/EntityRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "receiver": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/EntityRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "related_contacts": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/RelatedContact"
            }
          },
          "property": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/EntityRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "unit": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "name": {
                "type": "string"
              },
              "type": {
                "type": "string"
              },
              "property_reference": {
                "type": "string"
              }
            }
          },
          "equipment": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "name": {
                "type": "string"
              },
              "type": {
                "type": "string"
              },
              "reference_id": {
                "type": "string"
              }
            }
          },
          "signature": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Signature"
              },
              {
                "type": "null"
              }
            ]
          },
          "language": {
            "type": [
              "string",
              "null"
            ]
          },
          "extracted_metadata": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "document_classification_v3": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ClassificationV3"
              },
              {
                "type": "null"
              }
            ]
          },
          "document_tags": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "tag_key": {
                  "type": "string"
                },
                "confidence": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          },
          "document_tags_v3": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "tag_key": {
                  "type": "string"
                },
                "confidence": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "DocumentDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Document"
          },
          {
            "type": "object",
            "properties": {
              "contacts": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Contact"
                }
              },
              "extracted_contacts": {
                "type": [
                  "array",
                  "null"
                ],
                "description": "Opted-in organizations only; omitted when the document has no contacts. Same array the document.processed webhook delivers.",
                "items": {
                  "$ref": "#/components/schemas/ExtractedContact"
                }
              }
            }
          }
        ]
      },
      "Contact": {
        "type": "object",
        "description": "Org-level deduplicated contact. Ids may disappear when duplicates are merged — treat document_id as the stable key.\n",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "person",
              "firma"
            ]
          },
          "role": {
            "type": "string",
            "enum": [
              "absender",
              "empfaenger",
              "cc"
            ],
            "description": "Role on THIS document."
          },
          "name": {
            "type": "string"
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Person only."
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Person only."
          },
          "salutation": {
            "type": [
              "string",
              "null"
            ],
            "description": "Person only."
          },
          "function": {
            "type": [
              "string",
              "null"
            ],
            "description": "Person only — role/job title as stated in documents (e.g. Geschäftsführerin)."
          },
          "date_of_birth": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Person only — ISO YYYY-MM-DD."
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "emails": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "phones": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "website": {
            "type": [
              "string",
              "null"
            ],
            "description": "Firma only."
          },
          "vat_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "Firma only — UID/VAT."
          },
          "hr_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "Firma only — commercial-register number."
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ExtractedContact": {
        "description": "A contact as delivered in `extracted_contacts` (the `document.processed` webhook and `GET /api/v2/documents/:id`): the base Contact plus per-document extraction confidence and relationship pairing signals. v3-opt-in orgs only.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/Contact"
          },
          {
            "type": "object",
            "properties": {
              "confidence": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "maximum": 1,
                "description": "Per-document extraction confidence (0..1)."
              },
              "relationships": {
                "type": "array",
                "description": "Directed links to sibling entries in this same array. An empty array is an explicit \"no relationships\", not \"unknown\".\n",
                "items": {
                  "type": "object",
                  "properties": {
                    "contact_id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "References another contact's id in this same array."
                    },
                    "relationship_type": {
                      "type": "string",
                      "enum": [
                        "employee_of",
                        "represents",
                        "related_to"
                      ]
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "UploadedDocument": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "filename": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "file_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "file_size": {
            "type": [
              "integer",
              "null"
            ]
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "content_hash": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ZipItem": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "accepted",
              "idempotent_replay",
              "rejected"
            ]
          },
          "id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "filename": {
            "type": "string"
          },
          "file_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "file_size": {
            "type": [
              "integer",
              "null"
            ]
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "content_hash": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "rejected_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "directory_entry",
              "nested_zip_not_supported",
              "entry_too_large",
              "unsupported_type",
              "empty_entry",
              "office_lock_file",
              null
            ]
          }
        }
      },
      "ClassificationPatch": {
        "type": "object",
        "description": "All fields optional; set a field to null to clear it.",
        "properties": {
          "doc_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "doc_subtype": {
            "type": [
              "string",
              "null"
            ]
          },
          "doc_bereich": {
            "type": [
              "string",
              "null"
            ],
            "description": "Auto-derived from doc_type when omitted."
          },
          "intent": {
            "type": [
              "string",
              "null"
            ]
          },
          "status_lifecycle": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "neu",
              "in_bearbeitung",
              "abgeschlossen",
              "sonstiges",
              "finalized",
              "deleted",
              null
            ]
          },
          "zahlungsstatus": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "offen",
              "bezahlt",
              "teilbezahlt",
              "ueberfaellig",
              "sonstiges",
              null
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Replaces the full tag set."
          }
        }
      }
    }
  }
}
