# DMS-API v1 Schnittstellenbeschreibung: `GET /api/dms/v1/openapi.json` (OpenAPI 3.0, Quelle `internal/openapi/openapi.json`). Die Oberfläche nutzt ausschließlich diese Schnittstelle. ## Versionierung und Routen Alle Endpunkte liegen unter `/api/dms/v1` (Registerpräfix aus `deploy/routes.tsv`, wie `/api/core/v1`). Es gibt keine DMS-eigene Versionierung: eine neue Version ist ein neues Präfix des zentralen Schemas, bestehende Pfade bleiben unberührt. ## Authentifizierung `Authorization: Bearer ` (EdDSA, `kid`-Header, Claims `sub` und `tenant`; Schlüssel per JWKS von Core, `NEXARCH_DMS_APP_CORE_JWKS_URL`). Der Mandant im Token muss dem Mandanten der Instanz (`NEXARCH_DMS_APP_TENANT_SLUG`) entsprechen. Die Identität kommt ausschließlich aus dem Token; ein mitgeschickter `caller`-Parameter oder `X-User-Id`-Header wird überschrieben. Ohne Token: `401`. Ohne Erreichbarkeit von Core und ohne bekannten Schlüsselsatz: `503`. Bereits bekannte Schlüssel gelten bei einem Core-Ausfall weiter. Ausnahmen ohne Nutzeridentität: `GET /download` (Signatur der zuvor angeforderten URL), `POST /retention/destroy-callback` (Archive, Dienst-zu-Dienst) und `GET /openapi.json`. Übergangsmodus: mit `NEXARCH_DMS_APP_ALLOW_CALLER_IDENTITY=true` gilt ohne Token der Parameter `caller` als Identität (nicht authentifiziert, nur für Entwicklung). Ohne JWKS-URL und ohne diesen Schalter startet der Dienst nicht. ## Rechte Objektzugriff (Eigentümer, Admin, Prüfer) erzwingt der Guard vor jedem Handler. Dokumentlisten eines Ordners (`GET /folders/documents`, `GET /folders/{id}/documents`) enthalten nur Dokumente des Aufrufers und verlangen eine Identität (`401` ohne). Mit `NEXARCH_DMS_APP_POLICY_URL` fragt das DMS zusätzlich je Route Core (`POST /authorize`, Header `X-Service-Token`) nach dem in der OpenAPI-Beschreibung als `x-permission` deklarierten Recht. Die Rolle stammt aus dem Token-Claim `role`, sonst gilt `NEXARCH_DMS_APP_POLICY_DEFAULT_ROLE` (Standard `user`), Admin-IDs erhalten `tenant_admin`. Fehler oder Ausfall von Core lehnen die Anfrage ab (`503`, nie stillschweigend erlaubt). ## Fehlerformat Jede Fehlerantwort (`>= 400`, auch `404`/`405` des Routers) hat das Core-Schema: ```json {"error": {"code": "not_found", "message": "dokument nicht gefunden"}} ``` `code` ist stabil und maschinenlesbar (`invalid_request`, `unauthenticated`, `forbidden`, `not_found`, `method_not_allowed`, `conflict`, `unprocessable`, `payload_too_large`, `locked`, `unavailable`, `internal_error`). Zusatzfelder (z. B. `documentId` beim Duplikat) stehen daneben. ## Beispiele Die folgenden Blöcke werden von `TestDocumentationExamples` gegen die verdrahtete Anwendung ausgeführt. `capture name=schluessel` merkt sich einen Wert der JSON-Antwort für spätere `{name}`-Platzhalter. Upload-Sitzung starten: ```example POST /uploads -> 201 capture sessionId=sessionId {"filename":"beleg.txt","mimeType":"text/plain","totalSize":16} ``` Datenblock senden (fortsetzbar über `offset`): ```example PUT /uploads/{sessionId}/chunk?offset=0 -> 200 Rechnung 4711 ok ``` Stand abfragen: ```example GET /uploads/{sessionId} -> 200 ``` Abschließen, Dokument und erste Revision entstehen: ```example POST /uploads/{sessionId}/complete -> 200 capture documentId=documentId ``` Dokument lesen: ```example GET /documents/{documentId} -> 200 ``` ```example GET /documents/{documentId}/content -> 200 ``` ```example GET /documents/{documentId}/revisions -> 200 ``` Tags und Metadaten: ```example GET /documents/{documentId}/tags -> 200 ``` ```example GET /documents/{documentId}/metadata -> 200 ``` ```example GET /metadata-fields -> 200 ``` Ordner und Papierkorb: ```example GET /folders/children -> 200 ``` ```example GET /trash -> 200 ``` Dokument in den Papierkorb verschieben (Auswahl von `document_ids` und/oder `folder_ids`; Antwort mit `trashed_*_ids` und `failed` je ID, ein von anderen ausgeliehenes Dokument und ein Ordner mit Dokumenten anderer Nutzer werden abgelehnt) und wiederherstellen: ```example POST /trash -> 200 {"document_ids":["{documentId}"]} ``` ```example POST /trash/restore -> 200 {"document_ids":["{documentId}"]} ``` Signierte Download-URL anfordern: ```example GET /documents/{documentId}/download-url -> 200 ``` Fehlerformat bei unbekanntem Dokument: ```example GET /documents/00000000-0000-0000-0000-000000000000/tags -> 403 ``` Schnittstellenbeschreibung (ohne Anmeldung): ```example GET /openapi.json -> 200 anonymous ``` ## Freigabelinks (DOC-22) Ein Dokument lässt sich ohne DMS-Konto teilen: `POST /documents/{id}/share-links` (nur der Eigentümer, Rechte `dms.sharelink.write`) legt einen Link mit Pflichtablauf, optionalem Passwort und optionalem Download-Limit an; die Antwort enthält den Token nur dieses eine Mal in der URL. `GET /documents/{id}/share-links` (Rechte `dms.sharelink.read`) listet bestehende Links ohne Token/Passwort, `DELETE /documents/{id}/share-links/{linkID}` widerruft sofort. Der Abruf läuft ohne Anmeldung über `GET /share/{token}` bzw. `POST /share/{token}` (Passwort im JSON-Body, nie in der URL oder im Query-String) und ist in der OpenAPI-Beschreibung als `"security": []` markiert. Jeder nicht nutzbare Ausgang (unbekannt, abgelaufen, widerrufen, Limit erreicht) liefert einheitlich `404`; ein gesetztes Passwort liefert ohne Angabe `401 password_required`, nach zu vielen Fehlversuchen `423 locked`. Konfiguration per Umgebungsvariable (siehe `BETRIEB-UMGEBUNGSVARIABLEN.md`): `NEXARCH_DMS_APP_SHARE_LINKS_ENABLED` (Mandanten-Schalter), `NEXARCH_DMS_APP_SHARE_LINK_DEFAULT_TTL_SECONDS`, `NEXARCH_DMS_APP_SHARE_LINK_MAX_TTL_SECONDS`, `NEXARCH_DMS_APP_SHARE_LINK_MAX_ATTEMPTS`, `NEXARCH_DMS_APP_SHARE_LINK_LOCKOUT_SECONDS`. ## Optionale CMIS-Schnittstelle Neben der REST-API gibt es eine abschaltbare CMIS-1.1-Schnittstelle (Browser Binding) unter `/api/dms/v1/cmis/browser`. Sie nutzt intern diese REST-API und damit dieselben Rechte- und Mandantenregeln; Einzelheiten in [CMIS.md](CMIS.md).