Dateien
Kurzantwort: Dateien gehen entweder als Multipart (Teil data mit dem
JSON, Teil files mit den Dateien) oder Base64 im JSON an die API. Die
Zuordnung läuft über den Dateinamen: Das Feld name im Datenobjekt muss dem
Namen der hochgeladenen Datei entsprechen. Heruntergeladen wird über
GET /{id}/file.
Multipart — der Regelweg
Abschnitt betitelt „Multipart — der Regelweg“curl -X POST ".../crm/document/create/upload" \ -H "Authorization: Bearer $TOKEN" \ -F 'data={"data":{"title":"Vertrag","file":{"name":"vertrag.pdf"}},"response":["id","file"]};type=application/json' \ -F "files=@vertrag.pdf"Im Browser:
const payload = { data: { title: 'Vertrag', file: { name: file.name } }, response: ['id', 'file'],};
const form = new FormData();form.append('data', new Blob([JSON.stringify(payload)], { type: 'application/json' }));form.append('files', file); // Feldname ist immer 'files'
await fetch(`${base}/crm/document/create/upload`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, // kein Content-Type setzen! body: form,});Drei Regeln, die sonst Zeit kosten:
- Kein
Content-Type-Header von Hand. Den setzt der Browser samt Boundary; setzen Sie ihn selbst, bricht der Upload. nameim Datenobjekt muss dem Dateinamen entsprechen. Darüber findet der Server die Bytes zum Feld.- Der Teil heißt
files, auch bei einer einzelnen Datei. Mehrere Dateien: mehrfachfilesanhängen, jede mit eindeutigem Namen.
Base64 — wenn Multipart nicht geht
Abschnitt betitelt „Base64 — wenn Multipart nicht geht“Jedes Objekt im Payload, das ein Feld content trägt, wird als Datei erkannt —
in beliebiger Verschachtelungstiefe:
POST /crm/document/create{ "data": { "title": "Vertrag", "file": { "name": "vertrag.pdf", "content": "JVBERi0xLjQKJcfs…" } }, "response": ["id", "file"]}Ein data:-Präfix (data:application/pdf;base64,…) wird abgeschnitten, Sie
können also direkt weiterreichen, was FileReader.readAsDataURL liefert.
Der Preis: Base64 bläht die Übertragung um rund ein Drittel auf und geht vollständig durch den Speicher. Für große Dateien nehmen Sie Multipart.
⚠️ Der Klassiker
Abschnitt betitelt „⚠️ Der Klassiker“{ "file": "[object File]" }Das entsteht, wenn ein File-Objekt in JSON.stringify landet. Eine Datei ist
kein serialisierbarer Wert — entweder echtes FormData oder Base64.
Herunterladen
Abschnitt betitelt „Herunterladen“const res = await fetch(`${base}/crm/document/${id}/file`, { headers: { Authorization: `Bearer ${token}` },});const blob = await res.blob();Der Endpunkt liefert die reinen Bytes. Zu beachten:
- Der Token muss in den Header — Sie können also keine
<img src>oder<a href>direkt auf diese URL setzen. Entweder Sie holen den Blob perfetchund erzeugen eine Object-URL, oder Sie lassen eine eigene Serverschicht ausliefern. - Die Datei wird serverseitig vollständig in den Speicher gelesen. Für sehr große Dateien ist das die praktische Grenze.
Anzeigen im Browser:
const url = URL.createObjectURL(blob);// … verwenden …URL.revokeObjectURL(url);Metadaten
Abschnitt betitelt „Metadaten“Nach dem Upload trägt das Datei-Objekt Angaben, die der Server ermittelt hat:
| Feld | Inhalt |
|---|---|
name | Anzeigename — den haben Sie geschickt |
mimeType | aus dem Anzeigenamen abgeleitet, sonst application/octet-stream |
fileSize | tatsächliche Größe der gespeicherten Datei |
fileVersion | Kennung des gespeicherten Inhalts |
Fordern Sie diese Felder im response an, wenn Sie sie in der Oberfläche
brauchen:
{ "response": ["id", { "field": "file", "response": ["name", "mimeType", "fileSize"] }] }Der mimeType kommt aus der Endung des Anzeigenamens, nicht aus dem, was
Ihr Client im Multipart deklariert. Eine Datei ohne Endung landet deshalb bei
application/octet-stream.
Ersetzen und Umbenennen
Abschnitt betitelt „Ersetzen und Umbenennen“| Absicht | so |
|---|---|
| Inhalt ersetzen | neuen Upload zum bestehenden Objekt schicken (PUT/PATCH /update/{id}/upload) |
| nur umbenennen | name ändern, ohne Datei — der Inhalt bleibt unberührt |
| Datei entfernen | Referenz auf null setzen (PATCH) oder das Objekt löschen |
Beim Ersetzen bleibt bei auditierten Modellen die vorherige Fassung als Version erhalten; bei nicht auditierten ist sie endgültig weg.
| Antwort | Ursache |
|---|---|
413 missing-filename | ein Teil ohne Dateinamen |
| 413 `duplicate-filenames | …` |
413 upload-failed | Übertragung abgebrochen |
500 file-not-found | beim Download: Datei fehlt im Speicher, Zugriffsfehler oder Volume nicht eingebunden |
| 403 | Ihnen fehlt die Download-Rolle |
Scheitert ein Upload, ist nichts gespeichert — weder Datensatz noch Datei. Wiederholen ist unbedenklich.
Details
Abschnitt betitelt „Details“Wie die Ablage funktioniert, wie Versionen entstehen und was bei einem Rollback mit dem Inhalt passiert, steht unter Dateien und Storage.