Dieser Artikel führt einen vollständigen Ablauf durch: Akte suchen,
Dokument hochladen, Dokument herunterladen. Zuerst mit
curl, damit jeder Schritt sichtbar bleibt, danach dasselbe
in C#.
Er richtet sich an alle, die selbst gegen die Schnittstelle entwickeln. Voraussetzung ist eine registrierte Applikation und ein gültiges Zugriffstoken – beides beschreibt Eine eigene Anwendung anbinden.
BASE="https://<mandant>.stp-cloud.de/documents/dms-cloud-api/api"
TOKEN="eyJhbGciOi..."Eine Akte oder einen Ordner finden
Drei Suchvarianten stehen zur Verfügung, die sich gegenseitig ausschließen:
| Parameter | Wirkung |
|---|---|
name |
Exakte Suche über den Aktennamen |
fileReference |
Exakte Suche über das Aktenzeichen |
searchTerm |
Präfixsuche über beides |
| (keiner davon) | Seitenweises Durchblättern aller Akten und Ordner |
curl -sS -H "Authorization: Bearer $TOKEN" \
"$BASE/v1/dms/documents/container?searchTerm=Mustermann&pageSize=25"{
"containers": [
{
"containerId": "1f0c9d1e-6f3b-4a2c-9d20-6b0f4f8a1c77",
"name": "Mustermann ./. Musterbank",
"fileReference": "2026-0042",
"type": "Akte"
}
],
"totalCount": 1,
"nextContinuationToken": null
}Enthält nextContinuationToken einen Wert, gibt es
weitere Seiten. Er wird beim nächsten Aufruf unverändert als
continuationToken mitgeschickt.
Die Dokumente der Akte auflisten
CONTAINER="1f0c9d1e-6f3b-4a2c-9d20-6b0f4f8a1c77"
curl -sS -H "Authorization: Bearer $TOKEN" \
"$BASE/v1/dms/documents/container/$CONTAINER/documents?pageSize=50"{
"documents": [
{
"documentId": "8a3d2f10-77bc-4de1-9a55-2b6f9ac13d04",
"name": "Klageschrift.pdf",
"documentType": "pdf",
"sizeBytes": 184320,
"modifiedAt": "2026-08-19T14:02:11Z"
}
],
"totalCount": 1,
"nextContinuationToken": null
}Die Register einer Akte – ihre Untergliederung, in die Dokumente einsortiert werden – und deren Inhalt lassen sich analog abrufen:
curl -sS -H "Authorization: Bearer $TOKEN" \
"$BASE/v1/dms/documents/container/$CONTAINER/filing-tray"
curl -sS -H "Authorization: Bearer $TOKEN" \
"$BASE/v1/dms/documents/container/$CONTAINER/filing-tray/17/documents"Ein Dokument hochladen
Der Upload läuft in vier Schritten, weil die Dateibytes nicht durch die Schnittstelle selbst laufen: Ablageplatz anfordern, Datei ablegen, Import beauftragen, Ergebnis abholen.
Erstens: Ablageplatz anfordern
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"fileName":"Schriftsatz.pdf"}' \
"$BASE/v1/platform/uploads/single"{
"objectKey": "kanzlei-mustermann/staging/9c1f.../Schriftsatz.pdf",
"putUrl": "https://...s3.eu-central-1.amazonaws.com/...&X-Amz-Signature=...",
"expiresAt": "2026-08-24T10:12:44Z"
}Zweitens: Die Datei dort ablegen
Die beiden Werte aus der Antwort werden gleich noch gebraucht:
PUT_URL="https://...s3.eu-central-1.amazonaws.com/...&X-Amz-Signature=..." # putUrl
OBJECT_KEY="kanzlei-mustermann/staging/9c1f.../Schriftsatz.pdf" # objectKeyDer Aufruf geht direkt an die Ablage, ohne das Zugriffstoken – die Adresse ist bereits signiert und zeitlich befristet:
curl -sS -X PUT --upload-file ./Schriftsatz.pdf "$PUT_URL"Für sehr große Dateien gibt es statt uploads/single die
mehrteilige Variante unter platform/multipart/…, bei der
die Datei in Teilen abgelegt und am Ende zusammengeführt wird.
Drittens: Den Import beauftragen
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: import-schriftsatz-2026-0042-001" \
-d '{
"fileName": "Schriftsatz.pdf",
"objectKey": "kanzlei-mustermann/staging/9c1f.../Schriftsatz.pdf",
"containerId": "1f0c9d1e-6f3b-4a2c-9d20-6b0f4f8a1c77",
"trayId": 17,
"comment": "Eingang über Partneranwendung"
}' \
"$BASE/v1/dms/documents"Die Antwort ist 202 Accepted:
{
"operationId": "7f3a2b9e4c1d40e8a5b26f0913c47ade",
"statusUrl": "/v1/platform/operations/7f3a2b9e4c1d40e8a5b26f0913c47ade"
}
202heißt angenommen, nicht erledigt. Der Auftrag ist in der Cloud eingegangen und geprüft – mehr sagt diese Antwort nicht. Ob das Dokument tatsächlich im Dokumentenmanagement angekommen ist, steht erst im Ergebnis des Vorgangs. Dazwischen kann noch einiges schiefgehen: Der Connector kann die Verbindung verlieren, das Dokumentenmanagement kann die Rechte verweigern, der Import kann in eine Zeitüberschreitung laufen. Eine Integration, die nach dem202aufhört, meldet Erfolge, die keine sind.
Die
operationIdist eine 32-stellige Hexadezimalkennung, kein selbst gewähltes Format. Sie wird von der Schnittstelle vergeben und unverändert weiterverwendet.
Viertens: Auf das Ergebnis warten
Die statusUrl aus der Antwort lässt sich direkt an
$BASE hängen – genau dafür trägt $BASE die
Version nicht und jeder Pfad sie selbst:
curl -sS -H "Authorization: Bearer $TOKEN" "$BASE$STATUS_URL"Oder, gleichbedeutend, aus der operationId
zusammengesetzt:
curl -sS -H "Authorization: Bearer $TOKEN" \
"$BASE/v1/platform/operations/7f3a2b9e4c1d40e8a5b26f0913c47ade"Solange der Vorgang läuft:
{ "operationId": "7f3a2b9e4c1d40e8a5b26f0913c47ade", "status": "pending" }Nach Abschluss:
{
"operationId": "7f3a2b9e4c1d40e8a5b26f0913c47ade",
"status": "succeeded",
"result": {
"documentId": "b41e77a0-2c33-4f9e-b0a1-5d70e2f6ac18",
"importedBytes": 184320
}
}Bei einem Fehlschlag:
{
"operationId": "7f3a2b9e4c1d40e8a5b26f0913c47ade",
"status": "failed",
"reason": "Der Benutzer besitzt keine Schreibrechte auf dieser Akte.",
"errorCode": "ON_PREM_PERMISSION_DENIED"
}Der Abrufzeitraum beträgt sieben Tage. Danach antwortet die Vorgangs-Adresse mit
404. EinfailedmiterrorCode: "OPERATION_TIMEOUT"bedeutet, dass die Verarbeitungsfrist abgelaufen ist, ohne dass ein Ergebnis eingetroffen wäre.
Ein sinnvoller Abfragerhythmus ist etwa jede Sekunde für die ersten zehn Sekunden, danach alle fünf Sekunden.
Ein Dokument herunterladen
Spiegelbildlich zum Upload:
DOC="b41e77a0-2c33-4f9e-b0a1-5d70e2f6ac18"
curl -sS -H "Authorization: Bearer $TOKEN" \
"$BASE/v1/dms/documents/$DOC/content"{
"operationId": "op_2d90ff41ab",
"statusUrl": "/v1/platform/operations/op_2d90ff41ab"
}Nach Abschluss liefert die Vorgangsabfrage die Download-Adresse:
{
"operationId": "op_2d90ff41ab",
"status": "succeeded",
"result": {
"downloadUrl": "https://...s3.eu-central-1.amazonaws.com/...&X-Amz-Signature=...",
"sizeBytes": 184320,
"fileName": "Schriftsatz.pdf",
"version": 3
}
}DOWNLOAD_URL="https://...s3.eu-central-1.amazonaws.com/...&X-Amz-Signature=..." # result.downloadUrl
curl -sS -o Schriftsatz.pdf "$DOWNLOAD_URL"Die Download-Adresse wird bei jeder Abfrage neu erzeugt und ist nur kurz gültig. Sie sollte nicht zwischengespeichert werden – stattdessen die Vorgangsabfrage erneut aufrufen. Eine bestimmte Fassung eines Dokuments lässt sich über
?version=2anfordern.
Ein Dokument umlegen
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: move-doc-b41e77a0-001" \
-d '{"targetContainerId":"3b21...","targetTrayId":4}' \
"$BASE/v1/dms/documents/$DOC/move"Diese Antwort kommt synchron:
{
"documentId": "b41e77a0-2c33-4f9e-b0a1-5d70e2f6ac18",
"previousContainerId": "1f0c9d1e-6f3b-4a2c-9d20-6b0f4f8a1c77",
"previousTrayId": 17,
"currentContainerId": "3b21...",
"currentTrayId": 4
}Dasselbe in C
Ein minimaler Client, der eine Akte sucht und ein Dokument importiert. Das Zugriffstoken stammt aus der Anmeldung und wird hier als bereits vorhanden angenommen.
using System.Net.Http.Headers;
using System.Net.Http.Json;
var http = new HttpClient
{
BaseAddress = new Uri("https://<mandant>.stp-cloud.de/documents/dms-cloud-api/api/")
};
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
http.DefaultRequestHeaders.Add("X-Correlation-Id", Guid.NewGuid().ToString("N"));
// 1) Akte suchen
var search = await http.GetFromJsonAsync<SearchResponse>(
"v1/dms/documents/container?searchTerm=Mustermann&pageSize=25");
var container = search!.Containers[0];
// 2) Ablageplatz anfordern
var slot = await (await http.PostAsJsonAsync(
"v1/platform/uploads/single", new { fileName = "Schriftsatz.pdf" }))
.Content.ReadFromJsonAsync<UploadSlot>();
// 3) Datei direkt in die Ablage schreiben - ohne Token, die Adresse ist signiert
using (var raw = new HttpClient())
using (var content = new StreamContent(File.OpenRead("Schriftsatz.pdf")))
{
(await raw.PutAsync(slot!.PutUrl, content)).EnsureSuccessStatusCode();
}
// 4) Import beauftragen - derselbe Schlüssel bei jedem Wiederholungsversuch
var import = new HttpRequestMessage(HttpMethod.Post, "v1/dms/documents")
{
Content = JsonContent.Create(new
{
fileName = "Schriftsatz.pdf",
objectKey = slot.ObjectKey,
containerId = container.ContainerId,
trayId = 17,
}),
};
import.Headers.Add("Idempotency-Key", "import-schriftsatz-2026-0042-001");
var accepted = await (await http.SendAsync(import))
.Content.ReadFromJsonAsync<AcceptedResponse>();
// 5) Ergebnis abholen - mit Abbruch, sonst läuft die Schleife bei einer
// Störung endlos weiter
OperationResponse status;
var deadline = DateTimeOffset.UtcNow.AddMinutes(10);
while (true)
{
await Task.Delay(TimeSpan.FromSeconds(1));
status = (await http.GetFromJsonAsync<OperationResponse>(
$"v1/platform/operations/{accepted!.OperationId}"))!;
if (status.Status != "pending")
{
break;
}
if (DateTimeOffset.UtcNow > deadline)
{
throw new TimeoutException(
$"Vorgang {accepted.OperationId} ist nach 10 Minuten noch pending.");
}
}
Console.WriteLine(status.Status == "succeeded"
? $"Importiert als {status.Result!.Value.GetProperty("documentId")}"
: $"Fehlgeschlagen: {status.ErrorCode} - {status.Reason}");Die vier Datenklassen dazu:
using System.Text.Json;
sealed record SearchResponse(List<ContainerItem> Containers, int? TotalCount,
string? NextContinuationToken);
sealed record ContainerItem(string ContainerId, string Name, string? FileReference,
string? Type);
sealed record UploadSlot(string ObjectKey, string PutUrl, DateTimeOffset ExpiresAt);
sealed record AcceptedResponse(string OperationId, string StatusUrl);
sealed record OperationResponse(string OperationId, string Status, string? Reason,
string? ErrorCode, JsonElement? Result);HttpClient bindet die Namen ohne weiteres Zutun, weil
die Schnittstelle in camelCase antwortet und der Standardvergleich Groß-
und Kleinschreibung ignoriert.
In einer produktiven Integration kommen zwei Dinge hinzu, die dieses Beispiel der Kürze halber auslässt:
-
Wiederholungen mit Wartezeitverlängerung bei den
Antwortcodes
429,502,503und504– siehe Idempotenz und Wiederholungen. -
Auswertung des Fehlerumschlags, um
codeundsupportCodemitzuprotokollieren – siehe Fehlercodes.
Ein Wiederholungsversuch liefert nicht das ursprüngliche Ergebnis. Wird derselbe
Idempotency-Keyerneut geschickt, antwortet die Schnittstelle mit409 IDEMPOTENCY_KEY_REUSEDbzw.504 IDEMPOTENCY_KEY_INFLIGHT_TIMEOUT– der Auftrag wird also kein zweites Mal ausgeführt, aber sein Ausgang wird auch nicht nachgeliefert. Den muss die Anwendung über die Vorgangsabfrage klären.