Zum Inhalt springen

Anhänge-API

Status: Entwurf · Spec-Kandidat: ja

Zweck

REST-Endpunkte für Datei-Anhänge (DDM_ATTACHMENT). Verhalten (Upload-Phasen, Virus-Scan, Cleanup) liegt in Anhänge-Verhalten.

Endpunkt-Übersicht

Methode + PfadZweckPermission
POST /attachments:initiateUpload-Session anlegen, Pre-Signed-PUT-URL erhaltenupdate auf Entity / manage_metadata falls entity_id null
POST /attachments:commitUpload abschließen, DDM_ATTACHMENT-Zeile anlegendito
GET /entities/{id}/attachmentsAnhänge einer Entity listen (paginiert, gefiltert nach logical_key, is_current)read auf Entity
GET /attachments/{id}Pre-Signed-GET-URL (302-Redirect)read
GET /attachments/{id}/contentDirect-Stream durch MDM (optional, große Dateien vermeiden)read
GET /attachments/{id}/metadataReine Metadaten (JSON)read
DELETE /attachments/{id}Soft-Deleteupdate auf Entity
POST /attachments/{id}:restoreSoft-Delete rückgängigupdate
POST /attachments/{id}:purgeHard-Delete (Storage + Zeile)manage_metadata

Alle Pfade sind tenant-scoped — tenant_id aus dem Auth-Kontext, kein Pfad-Parameter.

Initiate

POST /attachments:initiate
Content-Type: application/json
{
"entity_id": "c-1001-uuid-...", // optional
"logical_key": "vertragsscan", // optional, regex ^[a-z][a-z0-9_]*$
"filename": "vertrag_2026.pdf",
"mime": "application/pdf",
"size_bytes": 1248192
}
200 OK
{
"upload_session_id": "ups-...",
"presigned_put_url": "https://minio.acme.example.com/...",
"expires_at": "2026-04-27T13:05:00Z",
"storage_uri": "s3://mdm-acme/acme/<uuid>",
"max_size_bytes": 104857600
}

Fehlerfälle:

  • 403 — Permission-Defizit (nach den Standard-403/404-Regeln, siehe Autorisierung).
  • 413 Payload Too Largesize_bytes überschreitet Tenant- oder Plattform-Limit.
  • 415 Unsupported Media Typemime nicht in Tenant-Whitelist.
  • 400 — Format-Verletzung von logical_key/filename.

Hinweis: Der Server vergibt bereits hier die zukünftige attachment.id und schreibt sie in die AA_UPLOAD_SESSION-Zeile. Damit ist die Storage-URI deterministisch — ein nachgelagertes commit kann sie nicht beliebig ändern.

Direkter Upload

Client PUTtet die Bytes zur presigned_put_url. Erfolg = 200 OK vom Storage. MDM ist nicht beteiligt. Etag aus Storage-Antwort sollte der Client behalten, ist aber nicht Pflicht — commit validiert über separate Hash-Übermittlung.

Commit

POST /attachments:commit
{
"upload_session_id": "ups-...",
"sha256": "9f86d0...", // hex, 64 Zeichen
"size_bytes": 1248192 // optional, Server validiert
}
201 Created
{
"attachment": {
"id": "...",
"tenant_id": "...",
"entity_id": "...",
"logical_key": "vertragsscan",
"version_no": 3,
"is_current": true,
"filename": "vertrag_2026.pdf",
"mime": "application/pdf",
"size_bytes": 1248192,
"sha256": "9f86d0...",
"storage_backend": "minio",
"virus_scan_status": "pending",
"created_at": "2026-04-27T13:01:42Z",
...
}
}

Fehlerfälle:

  • 404upload_session_id unbekannt oder abgelaufen.
  • 409 Conflict — Session bereits committed; oder Storage-Object fehlt; oder size_bytes weicht von Phase-1-Wert ab.
  • 410 Gone — Session expired.

Listing

GET /entities/{id}/attachments?logical_key=vertragsscan&include_history=true&page_size=50
→ 200 OK
{
"items": [ ... DDM_ATTACHMENT objects ... ],
"next_cursor": "..."
}

Default-Filter: deleted_at IS NULL und is_current=true (nur jüngste Slot-Version je logical_key). include_history=true zeigt alle Versionen. include_deleted=true zeigt Soft-Deleted (Steward-Sicht, manage_metadata-only).

Download

GET /attachments/{id}
→ 302 Found
Location: https://minio.acme.example.com/...?X-Amz-Signature=...

Headers Content-Disposition und Content-Type sind im Pre-Signed-Response signiert.

Spezial-Stati:

  • 423 Lockedvirus_scan_status='pending' oder failed. Body: { "error": "virus_scan_pending", "scan_status": "..." }.
  • 403/404 nach Standardregel.
  • 410 Gone — Storage-Object fehlt (Cleanup-Job ist gelaufen, Restore ist nicht mehr möglich).

GET /attachments/{id}/content liefert die Bytes direkt aus dem MDM (Streaming-Proxy). Default deaktiviert für Dateien > 5 MB (Konfig).

Delete / Restore / Purge

DELETE /attachments/{id}
→ 204 No Content // soft-delete
POST /attachments/{id}:restore
→ 200 OK { "attachment": { ... } }
POST /attachments/{id}:purge
{ "reason": "GDPR Erasure Request #4711" }
→ 204 No Content // hard-delete + Storage-Object weg

purge schreibt reason in AA_AUDIT_LOG. Versuch auf nicht-soft-deleted: 409 (zwingt zum bewussten Soft-Delete-First, außer per Query-Param force=true für Datenschutz-Notfälle).

Idempotenz

  • initiate: optionaler Header Idempotency-Key. Bei Wiederholung mit gleichem Key + gleichen Feldern → dieselbe Session zurückgegeben.
  • commit: idempotent über upload_session_id — zweiter Commit liefert die existierende DDM_ATTACHMENT-Zeile statt Konflikt.

Webhook / Event

Outbox-Events nach DDM_ATTACHMENT-Mutation:

  • attachment.uploaded — neue Zeile, vor Scan-Outcome.
  • attachment.scannedvirus_scan_status final (clean/infected/skipped/failed).
  • attachment.deleted — Soft-Delete.
  • attachment.restored — Restore.
  • attachment.purged — Hard-Delete.

payload enthält attachment_id, entity_id (falls gesetzt), tenant_id, logical_key, version_no, virus_scan_status. Externe Konsumenten lernen so über den Lebenszyklus, ohne MDM-DB lesen zu müssen.

Verwandte Dokumente