{
    "openapi": "3.1.0",
    "info": {
        "title": "Doclution Kunden-API",
        "version": "1.0 (2026-10-26)",
        "summary": "Aufträge, Fotos und Berichte aus Doclution in ERP/TMS/eigene Systeme – plus Webhooks.",
        "description": "Alle Aufrufe gehen an /api_jobs.php (bzw. /api_pallet_xml.php); die Aktion steht im Query-Parameter `action`. Damit jede Aktion ein eigenes Schema hat, ist `?action=…` hier Teil des Pfades.\n\nAnmeldung: `Authorization: Bearer dlk_…` (empfohlen) oder `X-Api-Key: dlk_…`. Limit: 200 Anfragen pro Stunde und Schlüssel (Header X-RateLimit-Limit / X-RateLimit-Remaining, sonst 429 mit Retry-After).\n\nAusführliche Doku mit Beispielen: https://doclution.de/api_docs.php",
        "contact": {
            "name": "Doclution",
            "url": "https://doclution.de/kontakt.php"
        }
    },
    "servers": [
        {
            "url": "https://doclution.de"
        }
    ],
    "security": [
        {
            "bearerAuth": []
        },
        {
            "apiKeyHeader": []
        }
    ],
    "tags": [
        {
            "name": "Aufträge"
        },
        {
            "name": "Fotos & Berichte"
        },
        {
            "name": "Stammdaten"
        },
        {
            "name": "Verlade-XML"
        }
    ],
    "paths": {
        "/api_jobs.php?action=list": {
            "get": {
                "tags": [
                    "Aufträge"
                ],
                "operationId": "auftraegeAuflisten",
                "summary": "Aufträge auflisten",
                "description": "Aufträge der Firma ohne Papierkorb, neueste zuerst. Ein GET ohne action liefert ebenfalls die Liste.",
                "parameters": [
                    {
                        "name": "status",
                        "in": "query",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "all",
                                "open",
                                "completed"
                            ],
                            "default": "all"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "default": 50,
                            "maximum": 200
                        }
                    },
                    {
                        "name": "offset",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "default": 0
                        }
                    },
                    {
                        "name": "updated_since",
                        "in": "query",
                        "schema": {
                            "type": "string"
                        },
                        "description": "Seit diesem Zeitpunkt angelegt/geändert/abgeschlossen oder neues Foto (ISO 8601 oder JJJJ-MM-TT hh:mm:ss)"
                    },
                    {
                        "name": "reference",
                        "in": "query",
                        "schema": {
                            "type": "string"
                        },
                        "description": "Genau diese Auftragsnummer"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Liste",
                        "headers": {
                            "X-RateLimit-Limit": {
                                "$ref": "#/components/headers/Limit"
                            },
                            "X-RateLimit-Remaining": {
                                "$ref": "#/components/headers/Rest"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "const": true
                                        },
                                        "total": {
                                            "type": "integer"
                                        },
                                        "limit": {
                                            "type": "integer"
                                        },
                                        "offset": {
                                            "type": "integer"
                                        },
                                        "jobs": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer"
                                                    },
                                                    "job_reference": {
                                                        "type": "string"
                                                    },
                                                    "status": {
                                                        "$ref": "#/components/schemas/Status"
                                                    },
                                                    "job_comment": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "created_at": {
                                                        "$ref": "#/components/schemas/Zeit"
                                                    },
                                                    "updated_at": {
                                                        "oneOf": [
                                                            {
                                                                "$ref": "#/components/schemas/Zeit"
                                                            },
                                                            {
                                                                "type": "null"
                                                            }
                                                        ]
                                                    },
                                                    "deleted_at": {
                                                        "oneOf": [
                                                            {
                                                                "$ref": "#/components/schemas/Zeit"
                                                            },
                                                            {
                                                                "type": "null"
                                                            }
                                                        ]
                                                    },
                                                    "total_required": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ],
                                                        "description": "Summe der geforderten Fotos (als Text)"
                                                    },
                                                    "total_photos": {
                                                        "type": "integer"
                                                    },
                                                    "assigned_users": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ],
                                                        "description": "Benutzernamen, kommagetrennt"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Fehler400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Fehler401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Fehler403"
                    },
                    "429": {
                        "$ref": "#/components/responses/Fehler429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Fehler500"
                    }
                }
            }
        },
        "/api_jobs.php?action=get": {
            "get": {
                "tags": [
                    "Aufträge"
                ],
                "operationId": "auftragAbrufen",
                "summary": "Auftrag mit Anforderungen, Mitarbeitern und Fotos",
                "parameters": [
                    {
                        "name": "id",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Auftrags-ID"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Auftrag",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "const": true
                                        },
                                        "job": {
                                            "$ref": "#/components/schemas/Auftrag"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/Fehler404"
                    },
                    "400": {
                        "$ref": "#/components/responses/Fehler400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Fehler401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Fehler403"
                    },
                    "429": {
                        "$ref": "#/components/responses/Fehler429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Fehler500"
                    }
                }
            }
        },
        "/api_jobs.php?action=photo": {
            "get": {
                "tags": [
                    "Fotos & Berichte"
                ],
                "operationId": "fotoLaden",
                "summary": "Foto herunterladen (Binärdatei)",
                "parameters": [
                    {
                        "name": "id",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Foto-ID"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Bild",
                        "content": {
                            "image/*": {
                                "schema": {
                                    "type": "string",
                                    "contentMediaType": "image/jpeg"
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/Fehler404"
                    },
                    "409": {
                        "description": "Foto liegt auf dem eigenen Speicher-Server der Firma",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Fehler"
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Fehler400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Fehler401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Fehler403"
                    },
                    "429": {
                        "$ref": "#/components/responses/Fehler429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Fehler500"
                    }
                }
            }
        },
        "/api_jobs.php?action=report": {
            "get": {
                "tags": [
                    "Fotos & Berichte"
                ],
                "operationId": "berichtLink",
                "summary": "Signierter, 15 Minuten gültiger PDF-Link zum Bericht",
                "parameters": [
                    {
                        "name": "id",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Auftrags-ID"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Link",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "const": true
                                        },
                                        "job_id": {
                                            "type": "integer"
                                        },
                                        "reference": {
                                            "type": "string"
                                        },
                                        "status": {
                                            "$ref": "#/components/schemas/Status"
                                        },
                                        "report_url": {
                                            "type": "string",
                                            "format": "uri"
                                        },
                                        "expires_in": {
                                            "type": "integer",
                                            "const": 900
                                        },
                                        "expires_at": {
                                            "type": "string",
                                            "format": "date-time"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/Fehler404"
                    },
                    "400": {
                        "$ref": "#/components/responses/Fehler400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Fehler401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Fehler403"
                    },
                    "429": {
                        "$ref": "#/components/responses/Fehler429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Fehler500"
                    }
                }
            }
        },
        "/api_jobs.php?action=create": {
            "post": {
                "tags": [
                    "Aufträge"
                ],
                "operationId": "auftragAnlegen",
                "summary": "Auftrag anlegen (Schreibrecht)",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/AuftragNeu"
                            },
                            "example": {
                                "reference": "LS-2026-10045",
                                "comment": "Bitte Nummernschild fotografieren",
                                "users": [
                                    "maxmuster"
                                ],
                                "requirements": [
                                    {
                                        "label": "Lieferschein",
                                        "type": "dokument",
                                        "count": 1
                                    }
                                ]
                            }
                        },
                        "application/x-www-form-urlencoded": {
                            "schema": {
                                "$ref": "#/components/schemas/AuftragNeu"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Angelegt",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "const": true
                                        },
                                        "message": {
                                            "type": "string"
                                        },
                                        "job_id": {
                                            "type": "integer"
                                        },
                                        "reference": {
                                            "type": "string"
                                        },
                                        "auto_ref": {
                                            "type": "boolean"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "Auftragsnummer existiert bereits",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Fehler"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Kurz ausgelastet – erneut senden",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "const": false
                                        },
                                        "retry": {
                                            "const": true
                                        },
                                        "error_code": {
                                            "const": "TRY_AGAIN"
                                        },
                                        "error": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Fehler400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Fehler401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Fehler403"
                    },
                    "429": {
                        "$ref": "#/components/responses/Fehler429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Fehler500"
                    }
                }
            }
        },
        "/api_jobs.php?action=update": {
            "post": {
                "tags": [
                    "Aufträge"
                ],
                "operationId": "auftragAendern",
                "summary": "Einzelne Felder ändern (Schreibrecht)",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "id"
                                ],
                                "properties": {
                                    "id": {
                                        "type": "integer"
                                    },
                                    "reference": {
                                        "type": "string"
                                    },
                                    "comment": {
                                        "type": "string"
                                    },
                                    "status": {
                                        "type": "string",
                                        "enum": [
                                            "open",
                                            "completed"
                                        ]
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "$ref": "#/components/responses/OkJob"
                    },
                    "404": {
                        "$ref": "#/components/responses/Fehler404"
                    },
                    "400": {
                        "$ref": "#/components/responses/Fehler400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Fehler401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Fehler403"
                    },
                    "429": {
                        "$ref": "#/components/responses/Fehler429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Fehler500"
                    }
                }
            }
        },
        "/api_jobs.php?action=complete": {
            "post": {
                "tags": [
                    "Aufträge"
                ],
                "operationId": "auftragAbschliessen",
                "summary": "Auftrag abschließen (Schreibrecht, ohne Pflichtfoto-Prüfung und Bericht-Mail)",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "id"
                                ],
                                "properties": {
                                    "id": {
                                        "type": "integer"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "$ref": "#/components/responses/OkJob"
                    },
                    "400": {
                        "$ref": "#/components/responses/Fehler400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Fehler401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Fehler403"
                    },
                    "429": {
                        "$ref": "#/components/responses/Fehler429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Fehler500"
                    }
                }
            }
        },
        "/api_jobs.php?action=delete": {
            "post": {
                "tags": [
                    "Aufträge"
                ],
                "operationId": "auftragLoeschen",
                "summary": "In den Papierkorb (Schreibrecht). Alternativ HTTP DELETE.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "id"
                                ],
                                "properties": {
                                    "id": {
                                        "type": "integer"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "$ref": "#/components/responses/OkJob"
                    },
                    "400": {
                        "$ref": "#/components/responses/Fehler400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Fehler401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Fehler403"
                    },
                    "429": {
                        "$ref": "#/components/responses/Fehler429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Fehler500"
                    }
                }
            }
        },
        "/api_jobs.php?action=users": {
            "get": {
                "tags": [
                    "Stammdaten"
                ],
                "operationId": "nutzerAuflisten",
                "summary": "Aktive App-Nutzer (nur ID und Benutzername)",
                "responses": {
                    "200": {
                        "description": "Nutzer",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "const": true
                                        },
                                        "users": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer"
                                                    },
                                                    "username": {
                                                        "type": "string"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Fehler400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Fehler401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Fehler403"
                    },
                    "429": {
                        "$ref": "#/components/responses/Fehler429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Fehler500"
                    }
                }
            }
        },
        "/api_jobs.php?action=docs": {
            "get": {
                "tags": [
                    "Stammdaten"
                ],
                "operationId": "selbstauskunft",
                "summary": "Kurze maschinenlesbare Übersicht",
                "responses": {
                    "200": {
                        "description": "Übersicht",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Fehler400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Fehler401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Fehler403"
                    },
                    "429": {
                        "$ref": "#/components/responses/Fehler429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Fehler500"
                    }
                }
            }
        },
        "/api_pallet_xml.php?action=list": {
            "get": {
                "tags": [
                    "Verlade-XML"
                ],
                "operationId": "verladeXmlListe",
                "summary": "Offene Verlade-XML-Exporte (Modul Paletten-Scan)",
                "parameters": [
                    {
                        "name": "alle",
                        "in": "query",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "1"
                            ]
                        },
                        "description": "auch bereits abgeholte"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Liste",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "const": true
                                        },
                                        "anzahl": {
                                            "type": "integer"
                                        },
                                        "exporte": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer"
                                                    },
                                                    "job_reference": {
                                                        "type": "string"
                                                    },
                                                    "dateiname": {
                                                        "type": "string"
                                                    },
                                                    "paletten": {
                                                        "type": "integer"
                                                    },
                                                    "mail_ok": {
                                                        "type": "boolean"
                                                    },
                                                    "abgeholt_am": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "abgeholt": {
                                                        "type": "boolean"
                                                    },
                                                    "created_at": {
                                                        "type": "string"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Fehler400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Fehler401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Fehler403"
                    },
                    "429": {
                        "$ref": "#/components/responses/Fehler429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Fehler500"
                    }
                }
            }
        },
        "/api_pallet_xml.php?action=get": {
            "get": {
                "tags": [
                    "Verlade-XML"
                ],
                "operationId": "verladeXmlLaden",
                "summary": "XML-Datei laden (ack=1 quittiert gleich, Schreibrecht)",
                "parameters": [
                    {
                        "name": "id",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "ack",
                        "in": "query",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "1"
                            ]
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "XML",
                        "content": {
                            "application/xml": {
                                "schema": {
                                    "type": "string"
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/Fehler404"
                    },
                    "400": {
                        "$ref": "#/components/responses/Fehler400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Fehler401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Fehler403"
                    },
                    "429": {
                        "$ref": "#/components/responses/Fehler429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Fehler500"
                    }
                }
            }
        },
        "/api_pallet_xml.php?action=ack": {
            "post": {
                "tags": [
                    "Verlade-XML"
                ],
                "operationId": "verladeXmlQuittieren",
                "summary": "Als abgeholt markieren (Schreibrecht)",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/x-www-form-urlencoded": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "id"
                                ],
                                "properties": {
                                    "id": {
                                        "type": "integer"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Quittiert",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "const": true
                                        },
                                        "quittiert": {
                                            "type": "boolean"
                                        },
                                        "message": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Fehler400"
                    },
                    "401": {
                        "$ref": "#/components/responses/Fehler401"
                    },
                    "403": {
                        "$ref": "#/components/responses/Fehler403"
                    },
                    "429": {
                        "$ref": "#/components/responses/Fehler429"
                    },
                    "500": {
                        "$ref": "#/components/responses/Fehler500"
                    }
                }
            }
        }
    },
    "webhooks": {
        "job.created": {
            "post": {
                "summary": "job.created",
                "description": "Auftrag angelegt (Portal, Leitstand, App, API). data.job.source nennt die Quelle.\n\nSignatur: Header X-Doclution-Signature `t=<unix>,v1=<hex>` mit v1 = HMAC-SHA256(Geheimnis, \"<t>.<roher Body>\"). Antwort innerhalb von 10 s mit 2xx; sonst neue Versuche nach 1 min, 5 min, 30 min, 2 h, 12 h.",
                "parameters": [
                    {
                        "name": "X-Doclution-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "const": "job.created"
                        }
                    },
                    {
                        "name": "X-Doclution-Delivery",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Zustell-ID, bei Wiederholungen gleich"
                    },
                    {
                        "name": "X-Doclution-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    },
                    {
                        "name": "X-Doclution-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WebhookHuelle"
                                    },
                                    {
                                        "type": "object",
                                        "properties": {
                                            "type": {
                                                "const": "job.created"
                                            },
                                            "data": {
                                                "$ref": "#/components/schemas/WebhookAuftragDaten"
                                            }
                                        }
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Empfang bestätigt (jeder 2xx-Status)"
                    }
                }
            }
        },
        "job.updated": {
            "post": {
                "summary": "job.updated",
                "description": "Auftrag bearbeitet oder Status ohne Abschluss geändert.\n\nSignatur: Header X-Doclution-Signature `t=<unix>,v1=<hex>` mit v1 = HMAC-SHA256(Geheimnis, \"<t>.<roher Body>\"). Antwort innerhalb von 10 s mit 2xx; sonst neue Versuche nach 1 min, 5 min, 30 min, 2 h, 12 h.",
                "parameters": [
                    {
                        "name": "X-Doclution-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "const": "job.updated"
                        }
                    },
                    {
                        "name": "X-Doclution-Delivery",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Zustell-ID, bei Wiederholungen gleich"
                    },
                    {
                        "name": "X-Doclution-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    },
                    {
                        "name": "X-Doclution-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WebhookHuelle"
                                    },
                                    {
                                        "type": "object",
                                        "properties": {
                                            "type": {
                                                "const": "job.updated"
                                            },
                                            "data": {
                                                "$ref": "#/components/schemas/WebhookAuftragDaten"
                                            }
                                        }
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Empfang bestätigt (jeder 2xx-Status)"
                    }
                }
            }
        },
        "job.completed": {
            "post": {
                "summary": "job.completed",
                "description": "Auftrag abgeschlossen.\n\nSignatur: Header X-Doclution-Signature `t=<unix>,v1=<hex>` mit v1 = HMAC-SHA256(Geheimnis, \"<t>.<roher Body>\"). Antwort innerhalb von 10 s mit 2xx; sonst neue Versuche nach 1 min, 5 min, 30 min, 2 h, 12 h.",
                "parameters": [
                    {
                        "name": "X-Doclution-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "const": "job.completed"
                        }
                    },
                    {
                        "name": "X-Doclution-Delivery",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Zustell-ID, bei Wiederholungen gleich"
                    },
                    {
                        "name": "X-Doclution-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    },
                    {
                        "name": "X-Doclution-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WebhookHuelle"
                                    },
                                    {
                                        "type": "object",
                                        "properties": {
                                            "type": {
                                                "const": "job.completed"
                                            },
                                            "data": {
                                                "$ref": "#/components/schemas/WebhookAuftragDaten"
                                            }
                                        }
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Empfang bestätigt (jeder 2xx-Status)"
                    }
                }
            }
        },
        "job.reopened": {
            "post": {
                "summary": "job.reopened",
                "description": "Erledigter Auftrag wieder geöffnet.\n\nSignatur: Header X-Doclution-Signature `t=<unix>,v1=<hex>` mit v1 = HMAC-SHA256(Geheimnis, \"<t>.<roher Body>\"). Antwort innerhalb von 10 s mit 2xx; sonst neue Versuche nach 1 min, 5 min, 30 min, 2 h, 12 h.",
                "parameters": [
                    {
                        "name": "X-Doclution-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "const": "job.reopened"
                        }
                    },
                    {
                        "name": "X-Doclution-Delivery",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Zustell-ID, bei Wiederholungen gleich"
                    },
                    {
                        "name": "X-Doclution-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    },
                    {
                        "name": "X-Doclution-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WebhookHuelle"
                                    },
                                    {
                                        "type": "object",
                                        "properties": {
                                            "type": {
                                                "const": "job.reopened"
                                            },
                                            "data": {
                                                "$ref": "#/components/schemas/WebhookAuftragDaten"
                                            }
                                        }
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Empfang bestätigt (jeder 2xx-Status)"
                    }
                }
            }
        },
        "photo.uploaded": {
            "post": {
                "summary": "photo.uploaded",
                "description": "Neues Foto zu einem Auftrag.\n\nSignatur: Header X-Doclution-Signature `t=<unix>,v1=<hex>` mit v1 = HMAC-SHA256(Geheimnis, \"<t>.<roher Body>\"). Antwort innerhalb von 10 s mit 2xx; sonst neue Versuche nach 1 min, 5 min, 30 min, 2 h, 12 h.",
                "parameters": [
                    {
                        "name": "X-Doclution-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "const": "photo.uploaded"
                        }
                    },
                    {
                        "name": "X-Doclution-Delivery",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Zustell-ID, bei Wiederholungen gleich"
                    },
                    {
                        "name": "X-Doclution-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    },
                    {
                        "name": "X-Doclution-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WebhookHuelle"
                                    },
                                    {
                                        "type": "object",
                                        "properties": {
                                            "type": {
                                                "const": "photo.uploaded"
                                            },
                                            "data": {
                                                "$ref": "#/components/schemas/WebhookFotoDaten"
                                            }
                                        }
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Empfang bestätigt (jeder 2xx-Status)"
                    }
                }
            }
        },
        "report.ready": {
            "post": {
                "summary": "report.ready",
                "description": "Bericht abrufbar (nach Abschluss oder neuem Bericht nach spätem Foto). PDF-Link per action=report.\n\nSignatur: Header X-Doclution-Signature `t=<unix>,v1=<hex>` mit v1 = HMAC-SHA256(Geheimnis, \"<t>.<roher Body>\"). Antwort innerhalb von 10 s mit 2xx; sonst neue Versuche nach 1 min, 5 min, 30 min, 2 h, 12 h.",
                "parameters": [
                    {
                        "name": "X-Doclution-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "const": "report.ready"
                        }
                    },
                    {
                        "name": "X-Doclution-Delivery",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Zustell-ID, bei Wiederholungen gleich"
                    },
                    {
                        "name": "X-Doclution-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    },
                    {
                        "name": "X-Doclution-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WebhookHuelle"
                                    },
                                    {
                                        "type": "object",
                                        "properties": {
                                            "type": {
                                                "const": "report.ready"
                                            },
                                            "data": {
                                                "$ref": "#/components/schemas/WebhookAuftragDaten"
                                            }
                                        }
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Empfang bestätigt (jeder 2xx-Status)"
                    }
                }
            }
        },
        "test.ping": {
            "post": {
                "summary": "test.ping",
                "description": "Test aus dem Portal.\n\nSignatur: Header X-Doclution-Signature `t=<unix>,v1=<hex>` mit v1 = HMAC-SHA256(Geheimnis, \"<t>.<roher Body>\"). Antwort innerhalb von 10 s mit 2xx; sonst neue Versuche nach 1 min, 5 min, 30 min, 2 h, 12 h.",
                "parameters": [
                    {
                        "name": "X-Doclution-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "const": "test.ping"
                        }
                    },
                    {
                        "name": "X-Doclution-Delivery",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Zustell-ID, bei Wiederholungen gleich"
                    },
                    {
                        "name": "X-Doclution-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    },
                    {
                        "name": "X-Doclution-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WebhookHuelle"
                                    },
                                    {
                                        "type": "object",
                                        "properties": {
                                            "type": {
                                                "const": "test.ping"
                                            },
                                            "data": {
                                                "type": "object",
                                                "properties": {
                                                    "message": {
                                                        "type": "string"
                                                    },
                                                    "webhook_id": {
                                                        "type": "integer"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Empfang bestätigt (jeder 2xx-Status)"
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "description": "API-Schlüssel dlk_… aus dem Portal (Einstellungen → API)"
            },
            "apiKeyHeader": {
                "type": "apiKey",
                "in": "header",
                "name": "X-Api-Key"
            },
            "apiKeyQuery": {
                "type": "apiKey",
                "in": "query",
                "name": "api_key",
                "description": "Veraltet – landet in Protokollen. Bitte Header verwenden."
            }
        },
        "headers": {
            "Limit": {
                "schema": {
                    "type": "integer",
                    "const": 200
                },
                "description": "Anfragen je Stunde und Schlüssel"
            },
            "Rest": {
                "schema": {
                    "type": "integer"
                },
                "description": "Verbleibend in dieser Stunde"
            }
        },
        "schemas": {
            "Zeit": {
                "type": "string",
                "description": "Ortszeit Europe/Berlin, JJJJ-MM-TT hh:mm:ss",
                "examples": [
                    "2026-10-26 09:30:00"
                ]
            },
            "Status": {
                "type": "string",
                "enum": [
                    "open",
                    "pending_review",
                    "partial",
                    "completed"
                ],
                "description": "open = offen, pending_review = zur Prüfung, partial = Tagesabschluss mit Folgeauftrag, completed = erledigt"
            },
            "Fehler": {
                "type": "object",
                "required": [
                    "success",
                    "error"
                ],
                "properties": {
                    "success": {
                        "const": false
                    },
                    "error": {
                        "type": "string"
                    }
                }
            },
            "Anforderung": {
                "type": "object",
                "required": [
                    "label"
                ],
                "properties": {
                    "label": {
                        "type": "string",
                        "description": "Bezeichnung (Alias label_name)"
                    },
                    "type": {
                        "type": "string",
                        "description": "Foto: normal, dokument, ladung, schaden – Datenfeld/Pruefpunkt: feld, datum, auswahl, check (Alias photo_type)",
                        "default": "normal"
                    },
                    "count": {
                        "type": "integer",
                        "minimum": 1,
                        "default": 1,
                        "description": "Anzahl Fotos (Alias req_count)"
                    },
                    "group": {
                        "type": "string",
                        "description": "Position / Gruppe"
                    },
                    "repeatable": {
                        "type": "integer",
                        "enum": [
                            0,
                            1
                        ]
                    },
                    "options": {
                        "type": "string",
                        "description": "Auswahl-Optionen, eine je Zeile (Typ auswahl)"
                    },
                    "reason_options": {
                        "type": "string"
                    },
                    "skip": {
                        "type": "boolean",
                        "description": "Kein-Befund-Schalter (Typ check)"
                    },
                    "in_email": {
                        "type": "boolean",
                        "description": "Wert in der Abschluss-Mail zeigen"
                    },
                    "unit_default": {
                        "type": "string"
                    }
                }
            },
            "AuftragNeu": {
                "type": "object",
                "properties": {
                    "reference": {
                        "type": "string",
                        "description": "Pflicht, außer die Vorlage (template_id) vergibt die Nummer automatisch. Alias job_reference."
                    },
                    "template_id": {
                        "type": "integer",
                        "description": "Nur für die automatische Auftragsnummer der Vorlage"
                    },
                    "comment": {
                        "type": "string",
                        "description": "Alias job_comment"
                    },
                    "users": {
                        "type": "array",
                        "items": {
                            "oneOf": [
                                {
                                    "type": "string"
                                },
                                {
                                    "type": "integer"
                                }
                            ]
                        },
                        "description": "Benutzernamen oder IDs aktiver Mitarbeiter der eigenen Firma"
                    },
                    "requirements": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/Anforderung"
                        }
                    }
                }
            },
            "Auftrag": {
                "type": "object",
                "additionalProperties": true,
                "description": "Alle Spalten des Auftrags; neue Felder können hinzukommen.",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "job_reference": {
                        "type": "string"
                    },
                    "status": {
                        "$ref": "#/components/schemas/Status"
                    },
                    "job_comment": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "customer_name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "due_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date"
                    },
                    "template_id": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "created_at": {
                        "$ref": "#/components/schemas/Zeit"
                    },
                    "updated_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "completed_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "deleted_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "requirements": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "label_name": {
                                    "type": "string"
                                },
                                "photo_type": {
                                    "type": "string"
                                },
                                "req_count": {
                                    "type": "integer"
                                }
                            }
                        }
                    },
                    "assigned_users": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "id": {
                                    "type": "integer"
                                },
                                "username": {
                                    "type": "string"
                                }
                            }
                        }
                    },
                    "photos": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "id": {
                                    "type": "integer"
                                },
                                "file_path": {
                                    "type": "string",
                                    "description": "nur Information – nicht direkt abrufbar"
                                },
                                "label_name": {
                                    "type": "string"
                                },
                                "photo_type": {
                                    "type": "string"
                                },
                                "captured_at": {
                                    "type": [
                                        "string",
                                        "null"
                                    ]
                                },
                                "lat": {
                                    "type": [
                                        "string",
                                        "number",
                                        "null"
                                    ]
                                },
                                "lng": {
                                    "type": [
                                        "string",
                                        "number",
                                        "null"
                                    ]
                                },
                                "download_url": {
                                    "type": "string",
                                    "format": "uri"
                                }
                            }
                        }
                    }
                }
            },
            "WebhookHuelle": {
                "type": "object",
                "required": [
                    "id",
                    "type",
                    "created",
                    "api_version",
                    "data"
                ],
                "properties": {
                    "id": {
                        "type": "string",
                        "description": "Ereignis-ID evt_…, zum Entdoppeln"
                    },
                    "type": {
                        "type": "string"
                    },
                    "created": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "api_version": {
                        "type": "string",
                        "const": "2026-10-26"
                    },
                    "data": {
                        "type": "object"
                    }
                }
            },
            "WebhookAuftrag": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "reference": {
                        "type": "string"
                    },
                    "status": {
                        "$ref": "#/components/schemas/Status"
                    },
                    "previous_status": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "template_id": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "due_date": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "updated_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "completed_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "source": {
                        "type": "string",
                        "enum": [
                            "portal",
                            "app",
                            "leitstand",
                            "nachtrag",
                            "api"
                        ]
                    },
                    "parent_job_id": {
                        "type": "integer"
                    },
                    "reason": {
                        "type": "string"
                    }
                }
            },
            "WebhookAuftragDaten": {
                "type": "object",
                "properties": {
                    "job": {
                        "$ref": "#/components/schemas/WebhookAuftrag"
                    },
                    "links": {
                        "type": "object",
                        "properties": {
                            "job": {
                                "type": "string",
                                "format": "uri"
                            },
                            "report": {
                                "type": "string",
                                "format": "uri"
                            }
                        }
                    }
                }
            },
            "WebhookFotoDaten": {
                "type": "object",
                "properties": {
                    "photo": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "job_id": {
                                "type": "integer"
                            },
                            "photo_type": {
                                "type": "string"
                            },
                            "label": {
                                "type": "string"
                            },
                            "group": {
                                "type": "string"
                            },
                            "unit_index": {
                                "type": "integer"
                            },
                            "captured_at": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            },
                            "storage": {
                                "type": "string",
                                "enum": [
                                    "doclution",
                                    "custom"
                                ]
                            }
                        }
                    },
                    "job": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "reference": {
                                "type": "string"
                            },
                            "status": {
                                "$ref": "#/components/schemas/Status"
                            }
                        }
                    },
                    "links": {
                        "type": "object",
                        "properties": {
                            "photo": {
                                "type": "string",
                                "format": "uri"
                            },
                            "job": {
                                "type": "string",
                                "format": "uri"
                            }
                        }
                    }
                }
            }
        },
        "responses": {
            "OkJob": {
                "description": "Erfolg",
                "content": {
                    "application/json": {
                        "schema": {
                            "type": "object",
                            "properties": {
                                "success": {
                                    "const": true
                                },
                                "message": {
                                    "type": "string"
                                },
                                "job_id": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                }
            },
            "Fehler400": {
                "description": "Parameter fehlt/ungültig oder unbekannte Aktion",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Fehler"
                        },
                        "example": {
                            "success": false,
                            "error": "id fehlt."
                        }
                    }
                }
            },
            "Fehler401": {
                "description": "Schlüssel fehlt, ungültig oder abgelaufen",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Fehler"
                        },
                        "example": {
                            "success": false,
                            "error": "Ungültiger oder abgelaufener API-Key."
                        }
                    }
                }
            },
            "Fehler403": {
                "description": "API nicht im Tarif oder Nur-Lesen-Schlüssel bei Schreibaktion",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Fehler"
                        },
                        "example": {
                            "success": false,
                            "error": "Dieser API-Schlüssel darf nur lesen."
                        }
                    }
                }
            },
            "Fehler404": {
                "description": "Nicht gefunden (auch: gehört einer anderen Firma)",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Fehler"
                        },
                        "example": {
                            "success": false,
                            "error": "Auftrag nicht gefunden."
                        }
                    }
                }
            },
            "Fehler429": {
                "description": "Mehr als 200 Anfragen in dieser Stunde",
                "headers": {
                    "Retry-After": {
                        "schema": {
                            "type": "integer"
                        }
                    }
                },
                "content": {
                    "application/json": {
                        "schema": {
                            "type": "object",
                            "properties": {
                                "success": {
                                    "const": false
                                },
                                "error": {
                                    "type": "string"
                                },
                                "retry_after": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                }
            },
            "Fehler500": {
                "description": "Serverfehler",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Fehler"
                        }
                    }
                }
            }
        }
    }
}
