Entwickler
Die Euphona Engine als API
Ein REST-Vertrag über api.euphonaai.com/v1. Asynchrone Jobs mit Live-Fortschritt per SSE, signierte Dateiübertragung, Schlüssel mit Präfix und ein kostenloser Testmodus: ein eigenes Produkt mit einem eigenen Supportzeitraum von zwölf Monaten.
Schlüssel werden in den Studio-Einstellungen erstellt: eu_test_… sofort und kostenlos, eu_live_… auf derselben Seite, sobald eine Karte hinterlegt ist, abgerechnet ab dem ersten Job. Der Testmodus durchläuft den vollständigen Vertrag.
Alles ist ein Job
curl -X POST https://api.euphonaai.com/v1/masters \
-H "Authorization: Bearer $EUPHONA_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"file_id": "file_8Kd2mQTz3XaV7pLw"}'
HTTP/2 202
{
"id": "job_7Vd2mHs5Yq8Tz3Xa",
"object": "job",
"type": "mastering",
"status": "queued",
"stage": null,
"metering": {
"pool": "payg",
"charged_minutes": 0,
"test_mode": false
}
}Design
Wie sie gebaut ist
Vier Entscheidungen, die nicht verhandelbar sind, weil jede von ihnen den Unterschied macht zwischen einer Integration, die ihren ersten Ausfall übersteht, und einer, die das nicht tut.
Alles ist ein Job
Audio geht um die API herum, nicht durch sie hindurch
Diese API ist das Produkt, kein Nebenprodukt
Das Dokument ist der Vertrag
Schritt für Schritt
Wie eine Integration aussieht
Datei erstellen, die Bytes an das signierte Ziel senden, einen Job einreichen und per SSE zusehen, wie er fertig wird. Vier Schritte, und nur der dritte unterscheidet sich je nach Funktion.
1. Datei erstellen und ein Upload-Ziel holen
curl -X POST https://api.euphonaai.com/v1/files \
-H "Authorization: Bearer $EUPHONA_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"filename":"take-07.wav","bytes":58210044,"content_type":"audio/wav"}'
{
"id": "file_8Kd2mQ...",
"object": "file",
"status": "awaiting_upload",
"upload": { "protocol": "...", "url": "https://files.euphonaai.com/...", "expires_at": "..." }
}2. Die Bytes an files. senden, nicht an uns
# Lies upload.protocol aus, nimm keinen Wert einfach an. Der Mechanismus für
# fortsetzbare Uploads ist eine Implementierungsentscheidung innerhalb des
# Vertrags und kann sich ohne neue Hauptversion ändern: genau deshalb ist
# er ein Feld und keine Dokumentation.3. Den Job beobachten
curl -N https://api.euphonaai.com/v1/jobs/job_4Rb.../events \
-H "Authorization: Bearer $EUPHONA_API_KEY"
event: job.progress
data: {"status":"analyzing","stage":"analyze","progress":{"fraction":0.4}}
event: job.completed
data: {"status":"succeeded","result":{...},"metering":{"pool":"payg","charged_minutes":3.4}}4. Mit dem Job selbst abgleichen
curl https://api.euphonaai.com/v1/jobs/job_4Rb... \
-H "Authorization: Bearer $EUPHONA_API_KEY"
{"id":"job_4Rb...","status":"succeeded","result":{...},
"metering":{"pool":"payg","charged_minutes":3.4}}
# Die Job-Ressource ist der aktuelle Stand der Zustandsmaschine: Was auch
# immer ein Stream gezeigt oder verpasst hat, lies sie erneut, bevor du
# danach handelst. Eine abgebrochene SSE-Verbindung verbindet sich mit
# Last-Event-ID neu und verliert nichts.Referenz
Die Schnittstelle
Die Plattform-Schnittstelle, die alle Funktionen teilen. Jede Funktion fügt einen Endpunkt zum Erstellen hinzu; der Job, den er zurückgibt, durchläuft alles darunter: derselbe Lebenszyklus, dieselben Events, derselbe Bericht.
| Methode | Pfad | Was er macht |
|---|---|---|
| POST | /v1/files | Eine Datei erstellen und ein signiertes, fortsetzbares Upload-Ziel holen. |
| GET | /v1/files/{id} | Einlesestatus, gemessene Dauer, Samplerate, Kanäle, Format. |
| DELETE | /v1/files/{id} | Eine Datei löschen und alles, was daraus entstanden ist; abgelehnt, solange ein Job sie noch verwendet. |
| GET | /v1/files/{id}/download | Eine signierte, ablaufende Download-URL. |
| GET | /v1/jobs | Die einheitliche Leseschnittstelle, mit Cursor-Paginierung. |
| GET | /v1/jobs/{id} | Status, Schritt, Fortschritt, Abrechnung und der gemessene Bericht. |
| POST | /v1/jobs/{id}/cancel | Abbrechen; keine Kosten, bevor der Verarbeitungsschritt (process) beginnt. |
| POST | /v1/jobs/{id}/input | Beantworten, was ein Job im Status awaiting_input fragt, und ihn fortsetzen. Wird nicht berechnet. |
| GET | /v1/jobs/{id}/events | Server-Sent Events über die gesamte Lebensdauer des Jobs. |
| POST | /v1/projects | Dateien und Jobs gruppieren; der Track Graph gilt pro Projekt. |
| GET | /v1/projects/{id} | Ein Projekt, mit den IDs seiner Dateien und Jobs. |
| GET | /v1/usage | Kontingentstand und die beiden Abrechnungstöpfe. |
| GET | /v1/webhook-endpoints | Deine Webhook-Endpunkte im Modus dieses Schlüssels. |
| POST | /v1/webhook-endpoints | Registriere eine HTTPS-URL; sie beantwortet eine signierte Challenge, bevor irgendetwas gespeichert wird. |
| GET | /v1/webhook-endpoints/{id} | Ein Endpunkt. Sein Secret wird nie wieder angezeigt. |
| PATCH | /v1/webhook-endpoints/{id} | Ändere Ereignisse, Beschreibung oder URL; eine neue URL wird erneut verifiziert. |
| DELETE | /v1/webhook-endpoints/{id} | Löschen; nicht zugestellte Ereignisse für ihn werden verworfen. |
| POST | /v1/webhook-endpoints/{id}/rotate-secret | Ein neues Signing-Secret; das alte signiert während einer Übergangszeit weiter. |
| GET | /v1/events | Ereignisse der letzten dreißig Tage, mit dem Ergebnis jeder Zustellung. |
| GET | /v1/events/{id} | Ein Ereignis so, wie es zugestellt wurde, mit seinen Zustellungen. |
Webhooks
Erfahre, wann ein Job fertig ist
Signierte Benachrichtigungen, wenn ein Job fertig ist oder auf deine Antwort wartet, so muss eine Integration weder einen Stream offen halten noch pollen.
- Ereignisse
job.completed,job.failed,job.canceledundjob.awaiting_input. Jedes ist ein Ereignis, dessendatadie Job-Ressource genau so ist, wie Polling sie liefert.- Workspace-Ereignisse
file.expiring, wenn für einen gespeicherten Upload ein Datum feststeht, ab dem er entfernt werden kann, mit der Datei alsdata, undusage.threshold, wenn ein Nutzungskontingent 80 % oder 100 % seines Limits erreicht. Beide erreichen Endpunkte in jedem Modus, einmal pro Datei bzw. einmal pro Schwelle und Zeitraum.- Registrierung
- Erfordert einen Schlüssel mit dem Scope
webhooks:manage, den neue Schlüssel nicht haben. Die URL muss HTTPS verwenden und darf nur in öffentliche Adressen aufgelöst werden, und sie muss eine signierte Challenge mit einer 2xx-Antwort zurückgeben, bevor irgendetwas gespeichert wird. Zustellungen werden nur über IPv4 gesendet, daher muss der Host eine IPv4-Adresse haben. - Zustellung
- Mindestens einmal, dedupliziere also anhand von
Euphona-Event-Id. Alles außer einer 2xx-Antwort innerhalb von 10 Sekunden wird 24 Stunden lang mit exponentiellem Backoff wiederholt, danach als unzustellbar markiert und inGET /v1/eventsaufbewahrt. Weiterleitungen werden nie verfolgt, und die Reihenfolge ist nicht garantiert: Gleiche mit dem Job ab. - Signaturen
Euphona-Signatureenthält einen Zeitstempel und einen HMAC-SHA256 über Zeitstempel, Punkt und den unveränderten Body, mit dem Secret des Endpunkts als Schlüssel. Prüfe ihn vor dem Parsen, vergleiche in konstanter Zeit und lehne Zeitstempel ab, die mehr als fünf Minuten von deiner Uhr abweichen.- Absenderadressen
- Zustellungen werden von
65.21.69.162gesendet. Lass diese Adressen zu, wenn deine Firewall eingehenden Datenverkehr filtert, und prüfe die Signatur trotzdem: Die Adresse, von der eine Anfrage kommt, beweist nicht, wer sie gesendet hat.
Eine Zustellung prüfen (Node.js)
import { createHmac, timingSafeEqual } from "node:crypto";
// Übergib den Body genau so, wie er angekommen ist: Geparstes und neu
// serialisiertes JSON ist nicht die Nachricht, die signiert wurde.
export function verifyEuphonaSignature(rawBody, header, secret, now = Date.now() / 1000) {
const parts = header.split(",").map((part) => part.trim().split("="));
const t = Number(parts.find(([key]) => key === "t")?.[1]);
const signatures = parts.filter(([key]) => key === "v1").map(([, value]) => value);
if (!Number.isFinite(t) || Math.abs(now - t) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
return signatures.some((value) => {
const given = Buffer.from(value, "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
});
}Betrieb
Was dich um drei Uhr morgens erwischt
Die sechs Verhaltensweisen, die du lesen solltest, bevor du die Integration schreibst: nicht erst, nachdem sie dich geweckt hat.
- Schlüssel
eu_live_…undeu_test_…, im Besitz eines Workspaces, einmal bei der Erstellung angezeigt und gehasht gespeichert. Rotiere mit einem Überlappungsfenster von bis zu dreißig Tagen, damit eine Rotation kein Ausfall ist. Testschlüssel sind kostenlos bis zu ihrem eigenen monatlichen Limit und laufen in ihrer eigenen Scheduling-Klasse; sie liefern eine Vorschau statt eines Masters, sodass sich eine Integration von Anfang bis Ende bauen lässt, ohne etwas zu kaufen.- Fehler
- Immer RFC 9457
application/problem+json, mit einem stabilen, maschinenlesbarencode, einem menschenlesbarendetailund der Request-ID. Verzweige nachcode, nie nach dem Text: der Text ist lokalisiert, der Code ist Vertrag. - Idempotenz
Idempotency-Keybei jedem POST. Derselbe Schlüssel mit demselben Body liefert die ursprüngliche Antwort erneut, solange die Ressource existiert, die er erstellt hat; derselbe Schlüssel mit einem anderen Body wird abgelehnt. Ein erneuter Versuch nach einem Timeout gibt also den ursprünglichen Job zurück, statt einen zweiten zu starten, der dir berechnet würde.- Limits
- Gleitendes Fenster pro Schlüssel mit den Standard-Headern
RateLimit-*; 60 Anfragen pro Minute bei Abrechnung nach Verbrauch, mehr mit einem Vertrag über eine Mindestabnahme. Der Limiter regelt nur die Anfragehäufigkeit: den Durchsatz regeln dein Kontingent und die Warteschlange. - Zwei Zähler, nie einer
- Jobs mit API-Schlüssel werden über den Topf für Abrechnung nach Verbrauch abgerechnet; Jobs aus einer Studio-Sitzung verbrauchen Planminuten. Kein Topf kann den anderen leeren, und
GET /v1/usagemeldet beide. Ausgabenlimits sind standardmäßig aktiv. Speicherplatz ist ein Kontingent, nicht zwei: Mit einem Schlüssel hochgeladene Dateien und die Ergebnisse von API-Jobs werden zusammen mit allem aus dem Studio auf das Speicherkontingent des Workspace-Plans angerechnet, 2 GB im kostenlosen Plan, und ein Upload oder Job, der nicht mehr hineinpasst, wird mitstorage_fullabgelehnt. - Request-Id
- Bei jeder Antwort, ob Erfolg oder Fehler. Nenn sie in einer Support-Anfrage, und sie lässt sich von Anfang bis Ende zurückverfolgen: durch den Job, durch jeden Schritt, bis zu dem, was tatsächlich passiert ist.
code | title |
|---|---|
| unauthenticated | Authentifizierung erforderlich |
| invalid_ | Ungültiger API-Schlüssel |
| key_ | API-Schlüssel widerrufen |
| insufficient_ | Unzureichender Scope |
| email_ | E-Mail-Bestätigung erforderlich |
| malformed_ | Fehlerhafte Anfrage |
| validation_ | Validierung der Anfrage fehlgeschlagen |
| unsupported_ | Nicht unterstützter Medientyp |
| idempotency_ | Idempotenzschlüssel mit anderem Body wiederverwendet |
| not_ | Nicht gefunden |
| job_ | Job kann nicht abgebrochen werden |
| job_ | Job wartet nicht auf eine Eingabe |
| file_ | Datei ist nicht bereit |
| file_ | Datei ist abgelaufen |
| file_ | Datei wird verwendet |
| file_ | Datei aus rechtlichen Gründen nicht verfügbar |
| unsupported_ | Nicht unterstütztes Audioformat |
| unsupported_ | Nicht unterstützte Samplerate |
| unsupported_ | Nicht unterstützte Kanalzahl |
| file_ | Audio ist zu lang |
| file_ | Audio ist zu kurz |
| file_ | Datei ist zu groß |
| corrupt_ | Audio konnte nicht dekodiert werden |
| rate_ | Rate-Limit überschritten |
| too_ | Zu viele aktive Jobs |
| quota_ | Kontingent überschritten |
| spend_ | Ausgabenlimit erreicht |
| test_ | Testmodus-Limit erreicht |
| storage_ | Speicherplatz voll |
| purchase_ | Kauf erforderlich |
| payment_ | Zahlungsmethode erforderlich |
| format_ | Format in diesem Plan nicht enthalten |
| webhook_ | Webhook-URL muss HTTPS verwenden |
| webhook_ | Webhook-URL muss öffentlich erreichbar sein |
| webhook_ | Überprüfung der Webhook-Inhaberschaft fehlgeschlagen |
| internal_ | Interner Fehler |
| service_ | Dienst nicht verfügbar |
| processing_ | Verarbeitung fehlgeschlagen |
| capacity_ | Kapazität nicht verfügbar |
| capability_ | Vorübergehend pausiert |
| workspace_ | Workspace gesperrt |
| uploads_ | Uploads pausiert |
| reauthentication_ | Bestätige, dass du es bist |
Tarife
Was es kostet
Pro verarbeiteter Minute, mal dem Faktor der Funktion, monatlich abgerechnet. Kein Abo nötig, ein API-Workspace kann im kostenlosen Plan sein. Der Testmodus ist kostenlos.
- Die ersten 100 Minuten im Monat
- 0,35 $
- Bis 1.000
- 0,25 $
- Bis 10.000
- 0,15 $
- Darüber hinaus oder mit Mindestabnahme
- Sprich mit uns