{"components":{"parameters":{"JobId":{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}},"responses":{"BadRequest":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Ung\u00fcltige Eingabe; JSON enth\u00e4lt error"},"NotFound":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Auftrag nicht gefunden oder geh\u00f6rt einem anderen Benutzer"},"Unauthorized":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bearer-Token fehlt, ist abgelaufen, widerrufen oder Benutzer ist gesperrt"}},"schemas":{"Error":{"properties":{"error":{"type":"string"}},"required":["error"],"type":"object"},"Job":{"properties":{"attempt":{"type":"integer"},"created_at":{"format":"date-time","type":"string"},"duration_seconds":{"type":"number"},"error":{"type":["string","null"]},"filename":{"type":"string"},"finished_at":{"format":"date-time","type":["string","null"]},"id":{"format":"uuid","type":"string"},"language":{"type":"string"},"size_bytes":{"type":"integer"},"speakers":{"type":"string"},"status":{"enum":["queued","importing","running","completed","failed"],"type":"string"},"status_url":{"format":"uri","type":"string"},"transcript_url":{"format":"uri","type":["string","null"]},"webhook":{"properties":{"attempts":{"type":"integer"},"event_id":{"type":["string","null"]},"status":{"enum":["waiting","pending","delivered","failed","cancelled"],"type":"string"}},"type":["object","null"]}},"required":["id","status","status_url"],"type":"object"},"JobList":{"properties":{"jobs":{"items":{"$ref":"#/components/schemas/Job"},"type":"array"},"page":{"minimum":1,"type":"integer"},"pages":{"minimum":1,"type":"integer"},"total":{"minimum":0,"type":"integer"}},"required":["jobs","page","pages","total"],"type":"object"},"User":{"properties":{"display_name":{"type":"string"},"id":{"format":"uuid","type":"string"},"role":{"enum":["user","admin"],"type":"string"}},"required":["id","display_name","role"],"type":"object"},"WebhookEvent":{"example":{"created_at":"2026-10-07T12:00:00+00:00","id":"d46f3d98-d76f-41f1-9328-eec431d22118","job":{"attempt":1,"id":"f55e4c38-d115-4b38-b62b-5b2dc605b9fa","status":"completed","status_url":"https://audio-zu-text.dini-it.ch/api/v1/jobs/f55e4c38-d115-4b38-b62b-5b2dc605b9fa","transcript_url":"https://audio-zu-text.dini-it.ch/api/v1/jobs/f55e4c38-d115-4b38-b62b-5b2dc605b9fa/transcript?format=txt"},"type":"transcription.completed"},"properties":{"created_at":{"format":"date-time","type":"string"},"id":{"description":"Stabile Ereignis-ID; bei mehrfacher Zustellung nur einmal verarbeiten.","format":"uuid","type":"string"},"job":{"properties":{"attempt":{"minimum":0,"type":"integer"},"id":{"format":"uuid","type":"string"},"status":{"enum":["completed","failed"],"type":"string"},"status_url":{"format":"uri","type":"string"},"transcript_url":{"description":"Bei Fehler null. Downloads ben\u00f6tigen einen pers\u00f6nlichen Bearer-Token.","format":"uri","type":["string","null"]}},"required":["id","status","attempt","status_url","transcript_url"],"type":"object"},"type":{"enum":["transcription.completed","transcription.failed"],"type":"string"}},"required":["id","type","created_at","job"],"type":"object"}},"securitySchemes":{"bearerAuth":{"description":"Pers\u00f6nlicher dit_-Token; wird unter /settings/api einmalig angezeigt. Keine Browser-Cookies.","scheme":"bearer","type":"http"}}},"info":{"description":"## Authentifizierung\nUnter [API-Zugang](/settings/api) nach der Entra-Anmeldung einen pers\u00f6nlichen Token erstellen. F\u00fcr jeden Aufruf `Authorization: Bearer DEIN_API_TOKEN` senden. In \u00abAuthorize\u00bb nur den Token eintragen. Alle Auftr\u00e4ge geh\u00f6ren dem Token-Besitzer; auch Administratoren k\u00f6nnen \u00fcber diese API keine fremden Auftr\u00e4ge lesen.\n\n## Ablauf\n1. `POST /jobs` mit der Audiodatei als Multipart-Feld `audio` senden. HTTP 202 best\u00e4tigt die Annahme; `Location` und `job.status_url` verweisen auf den Status.\n2. Den Status mit `GET /jobs/{id}` abfragen oder beim Upload `webhook_url` mitgeben.\n3. Bei `completed` das Transkript als TXT oder HTML herunterladen. Bis dahin liefert der Transkript-Endpunkt HTTP 409. Bei `failed` steht die Fehlermeldung in `job.error`.\n\nOptional `Idempotency-Key` senden: Wiederholungen mit demselben Schl\u00fcssel und identischen Daten liefern denselben Auftrag. Ge\u00e4nderte Daten bei gleichem Schl\u00fcssel ergeben HTTP 409. Ohne Schl\u00fcssel erzeugt jeder Upload einen neuen Auftrag. MCP-Zugang und Einrichtung: [/settings/mcp](/settings/mcp).\n\nOptional beim Upload eine `webhook_url` angeben. Payload, HMAC-Signaturpr\u00fcfung und Zustellversuche sind im Abschnitt **Webhooks** dokumentiert.","title":"dini-IT Audio zu Text API","version":"0.5.0"},"openapi":"3.1.0","paths":{"/jobs":{"get":{"operationId":"listJobs","parameters":[{"in":"query","name":"page","schema":{"default":1,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobList"}}},"description":"jobs, page, pages und total; 20 Auftr\u00e4ge pro Seite"},"401":{"$ref":"#/components/responses/Unauthorized"}},"summary":"Eigene Auftr\u00e4ge auflisten","tags":["Auftr\u00e4ge"]},"post":{"description":"Audio wird lokal gespeichert und zur Verarbeitung eingereiht. Sprache und Sprechererkennung sind optional. Der Webhook meldet sowohl Erfolg als auch Fehler.","operationId":"createJob","parameters":[{"description":"Eindeutiger Schl\u00fcssel je Benutzer und Upload. Wiederholungen ben\u00f6tigen identische Audiodaten, Dateinamen, Optionen und Webhook-Angaben. Ein neuer Auftrag ben\u00f6tigt einen neuen Schl\u00fcssel.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":128,"minLength":1,"pattern":"^[A-Za-z0-9_.:-]+$","type":"string"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"properties":{"audio":{"format":"binary","type":"string"},"language":{"default":"auto","enum":["auto","de","en","fr","it","es","pt","nl","tr","uk"],"type":"string"},"speakers":{"default":"none","enum":["none","auto","2","3","4"],"type":"string"},"webhook_secret":{"description":"Optionaler HMAC-Schl\u00fcssel: 32\u2013256 druckbare ASCII-Zeichen ohne Leerzeichen, ben\u00f6tigt webhook_url. Sonst wird der Schl\u00fcssel automatisch erzeugt. Er wird nur in der Upload-Antwort ausgegeben.","maxLength":256,"minLength":32,"type":"string"},"webhook_url":{"description":"Optionale HTTPS-URL f\u00fcr Erfolg und Fehler. Keine Weiterleitungen. Private Ziele ben\u00f6tigen eine Betreiberfreigabe.","format":"uri","type":"string"}},"required":["audio"],"type":"object"}}},"required":true},"responses":{"202":{"content":{"application/json":{"schema":{"properties":{"job":{"$ref":"#/components/schemas/Job"},"webhook_signing_secret":{"type":"string"}},"required":["job"],"type":"object"}}},"description":"Auftrag angenommen; Location enth\u00e4lt die Status-URL","headers":{"Location":{"schema":{"format":"uri","type":"string"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Idempotency-Key bereits mit anderen Daten verwendet"},"413":{"description":"Uploadgrenze \u00fcberschritten"},"415":{"description":"multipart/form-data erforderlich"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Aufnahme konnte nicht gespeichert werden"}},"summary":"Audio hochladen und Transkription starten","tags":["Auftr\u00e4ge"]}},"/jobs/{id}":{"get":{"operationId":"getJob","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"job":{"$ref":"#/components/schemas/Job"}},"type":"object"}}},"description":"Auftrag"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Status und Webhook-Zustellung","tags":["Auftr\u00e4ge"]},"parameters":[{"$ref":"#/components/parameters/JobId"}]},"/jobs/{id}/audio":{"get":{"operationId":"downloadAudio","responses":{"200":{"content":{"application/octet-stream":{"schema":{"format":"binary","type":"string"}}},"description":"Audio als Attachment"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Originalaufnahme herunterladen","tags":["Auftr\u00e4ge"]},"parameters":[{"$ref":"#/components/parameters/JobId"}]},"/jobs/{id}/transcript":{"get":{"operationId":"downloadTranscript","parameters":[{"in":"query","name":"format","schema":{"default":"txt","enum":["txt","html"],"type":"string"}}],"responses":{"200":{"content":{"text/html":{"schema":{"type":"string"}},"text/plain":{"schema":{"type":"string"}}},"description":"Datei als Attachment"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Transkript noch nicht verf\u00fcgbar"}},"summary":"Fertiges Datenbanktranskript herunterladen","tags":["Auftr\u00e4ge"]},"parameters":[{"$ref":"#/components/parameters/JobId"}]},"/me":{"get":{"operationId":"getMe","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}},"description":"Benutzer-ID, Anzeigename und Rolle"},"401":{"$ref":"#/components/responses/Unauthorized"}},"summary":"Token-Besitzer","tags":["Konto"]}},"/openapi.json":{"get":{"description":"Authentifizierte Version der Spezifikation. Die \u00f6ffentliche Kopie unter /api/openapi.json ist inhaltlich identisch.","operationId":"getOpenApi","responses":{"200":{"description":"OpenAPI 3.1 JSON"},"401":{"$ref":"#/components/responses/Unauthorized"}},"summary":"OpenAPI-Beschreibung","tags":["Spezifikation"]}}},"security":[{"bearerAuth":[]}],"servers":[{"url":"https://audio-zu-text.dini-it.ch/api/v1"}],"tags":[{"description":"Authentifizierten Benutzer pr\u00fcfen.","name":"Konto"},{"description":"Eigene Aufnahmen und Transkripte.","name":"Auftr\u00e4ge"},{"description":"Maschinenlesbare Schnittstellenbeschreibung.","name":"Spezifikation"}],"webhooks":{"transcriptionFinished":{"post":{"description":"Ausgehender POST an deine `webhook_url`. Diesen Endpunkt implementiert der Empf\u00e4nger.\n\nDie Anwendung sendet ein JSON-Ereignis vom Typ `transcription.completed` oder `transcription.failed` an die beim Upload angegebene HTTPS-URL. Der JSON-Body enth\u00e4lt die Ereignis-ID, den Zeitpunkt und den Auftrag mit Status und Download-Adresse. Das vollst\u00e4ndige Schema steht unter \u00abWebhooks\u00bb.\n\nOhne eigenen `webhook_secret` wird ein Schl\u00fcssel erzeugt. `webhook_signing_secret` wird nur in der Upload-Antwort ausgegeben und muss beim Empf\u00e4nger sicher gespeichert werden. Der Bearer-Token wird nie mitgesendet.\n\n- `X-Audio-Event-Id`: Ereignis-UUID (auch als `id` im signierten JSON-Body).\n- `X-Audio-Timestamp`: Unix-Zeitstempel in Sekunden.\n- `X-Audio-Signature`: `sha256=` gefolgt vom hexadezimalen HMAC-SHA256 \u00fcber `timestamp + \".\" + unver\u00e4nderte UTF-8-Body-Bytes`, mit dem Webhook-Schl\u00fcssel als UTF-8-Bytes.\n\nDie Signatur vor dem Parsen mit einem Vergleich in konstanter Zeit pr\u00fcfen. Nur Zeitstempel innerhalb von z. B. f\u00fcnf Minuten akzeptieren und die signierte Ereignis-ID zur Erkennung mehrfach zugestellter Nachrichten verwenden. Das Ereignis mit HTTP 2xx best\u00e4tigen; die Verarbeitung kann danach asynchron erfolgen.\n\nEs gibt bis zu sechs Zustellversuche, mit Abst\u00e4nden von 30 Sekunden, 2 Minuten, 10 Minuten, 30 Minuten und 2 Stunden. Wiederholungen behalten dieselbe Ereignis-ID und denselben Body, erhalten aber einen neuen Signatur-Zeitstempel. Weiterleitungen werden nicht verfolgt. Private Zieladressen ben\u00f6tigen eine Freigabe durch den Betreiber. `job.webhook` zeigt den Zustellstatus; eine fehlgeschlagene Zustellung \u00e4ndert den Transkriptionsstatus nicht.","operationId":"receiveTranscriptionEvent","parameters":[{"in":"header","name":"X-Audio-Event-Id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"header","name":"X-Audio-Timestamp","required":true,"schema":{"pattern":"^[0-9]+$","type":"string"}},{"in":"header","name":"X-Audio-Signature","required":true,"schema":{"pattern":"^sha256=[0-9a-f]{64}$","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}},"required":true},"responses":{"2XX":{"description":"Ereignis angenommen. Mehrfachzustellungen mit derselben signierten Ereignis-ID ebenfalls best\u00e4tigen."},"default":{"description":"Zustellung wird innerhalb des Retry-Limits wiederholt."}},"security":[],"summary":"Transkription abgeschlossen oder fehlgeschlagen"}}}}
