Dla programistów
Silnik Euphona jako API
Jeden kontrakt REST pod adresem api.euphonaai.com/v1. Asynchroniczne zadania z postępem na żywo przez SSE, podpisany transfer plików, klucze z prefiksem i bezpłatny tryb testowy: pełnoprawny produkt z własnym dwunastomiesięcznym okresem wsparcia.
Klucze tworzy się w ustawieniach Studia: eu_test_… od razu i bezpłatnie, eu_live_… na tym samym ekranie, gdy zapiszesz kartę, naliczany od pierwszego zadania. Tryb testowy obsługuje cały kontrakt.
Wszystko jest zadaniem
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
}
}Projekt
Jak to zaprojektowano
Cztery decyzje, które nie podlegają negocjacji, bo każda z nich oddziela integrację, która przetrwa pierwszą awarię, od takiej, która jej nie przetrwa.
Wszystko jest zadaniem
Dźwięk omija API, a nie przez nie przechodzi
To API jest produktem, a nie produktem ubocznym
Dokument jest kontraktem
Przewodnik
Kształt integracji
Utwórz plik, wyślij dane do podpisanego celu, zleć zadanie i obserwuj jego zakończenie przez SSE. Cztery kroki, a tylko trzeci różni się w zależności od funkcji.
1. Utwórz plik i uzyskaj cel przesyłania
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. Wyślij dane na files., nie do nas
# Odczytaj upload.protocol, nie zakładaj go z góry. Mechanizm wznawiania to wybór
# implementacyjny wewnątrz kontraktu i może się zmienić bez podbicia wersji
# głównej: właśnie dlatego jest polem, a nie dokumentacją.3. Obserwuj zadanie
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. Uzgodnij stan z samym zadaniem
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}}
# Zasób zadania to bieżąca prawda maszyny stanów: cokolwiek strumień
# pokazał lub pominął, odczytaj go ponownie, zanim zaczniesz działać. Zerwane
# połączenie SSE wznawia się z Last-Event-ID i niczego nie traci.Dokumentacja
Powierzchnia API
Wspólna powierzchnia platformy dla wszystkich funkcji. Każda funkcja dodaje jeden endpoint tworzący; zadanie, które zwraca, przechodzi przez wszystko poniżej: ten sam cykl życia, te same zdarzenia, ten sam raport.
| Metoda | Ścieżka | Co robi |
|---|---|---|
| POST | /v1/files | Tworzy plik i zwraca podpisany, wznawialny cel przesyłania. |
| GET | /v1/files/{id} | Stan przyjęcia, zmierzony czas trwania, częstotliwość próbkowania, kanały, format. |
| DELETE | /v1/files/{id} | Usuwa plik i wszystko, co z niego powstało; odrzucane, dopóki zadanie nadal go używa. |
| GET | /v1/files/{id}/download | Podpisany, wygasający URL do pobrania. |
| GET | /v1/jobs | Ujednolicony odczyt, ze stronicowaniem kursorem. |
| GET | /v1/jobs/{id} | Status, etap, postęp, naliczanie i zmierzony raport. |
| POST | /v1/jobs/{id}/cancel | Anuluje; bez opłaty przed rozpoczęciem etapu przetwarzania. |
| POST | /v1/jobs/{id}/input | Odpowiada na pytanie zadania w stanie awaiting_input i wznawia je. Nienaliczane. |
| GET | /v1/jobs/{id}/events | Zdarzenia wysyłane przez serwer przez cały czas życia zadania. |
| POST | /v1/projects | Grupuje pliki i zadania; Track Graph istnieje osobno dla każdego projektu. |
| GET | /v1/projects/{id} | Jeden projekt z identyfikatorami jego plików i zadań. |
| GET | /v1/usage | Stan limitu i dwie pule naliczania. |
| GET | /v1/webhook-endpoints | Twoje endpointy webhooków w trybie tego klucza. |
| POST | /v1/webhook-endpoints | Zarejestruj URL https; zanim cokolwiek zostanie zapisane, odpowiada on na podpisane wyzwanie. |
| GET | /v1/webhook-endpoints/{id} | Jeden endpoint. Jego sekret nie jest już nigdy pokazywany. |
| PATCH | /v1/webhook-endpoints/{id} | Zmień zdarzenia, opis lub URL; nowy URL jest weryfikowany ponownie. |
| DELETE | /v1/webhook-endpoints/{id} | Usuń go; niedostarczone do niego zdarzenia przepadają. |
| POST | /v1/webhook-endpoints/{id}/rotate-secret | Nowy sekret podpisu; stary podpisuje dalej przez okres przejściowy. |
| GET | /v1/events | Zdarzenia z trzydziestu dni z wynikiem każdej dostawy. |
| GET | /v1/events/{id} | Jedno zdarzenie w postaci, w jakiej zostało dostarczone, z jego dostawami. |
Webhooki
Dowiedz się, kiedy zadanie jest gotowe
Podpisane powiadomienia, gdy zadanie się kończy albo czeka na twoją odpowiedź: integracja nie musi trzymać otwartego strumienia ani odpytywać API.
- Zdarzenia
job.completed,job.failed,job.canceledijob.awaiting_input. Każde to zdarzenie, któregodatajest zasobem zadania dokładnie takim, jaki zwraca odpytywanie.- Zdarzenia obszaru roboczego
file.expiring, gdy przechowywany przesłany plik otrzymuje datę, od której może zostać usunięty, z plikiem jakodata, orazusage.threshold, gdy pula wykorzystania osiąga 80% lub 100% swojego limitu. Oba trafiają do endpointów w obu trybach, raz na plik albo raz na próg i okres.- Rejestracja
- Wymaga klucza z zakresem
webhooks:manage, którego nowe klucze nie mają. URL musi używać https i rozwiązywać się wyłącznie na adresy publiczne, a zanim cokolwiek zostanie zapisane, musi odpowiedzieć na podpisane wyzwanie odpowiedzią 2xx, która je odsyła. Dostawy są wysyłane wyłącznie przez IPv4, więc host musi mieć adres IPv4. - Dostawa
- Co najmniej raz, więc usuwaj duplikaty według
Euphona-Event-Id. Wszystko poza odpowiedzią 2xx w ciągu 10 sekund jest ponawiane z wykładniczym odstępem przez 24 godziny, potem trafia do kolejki martwych wiadomości i zostaje wGET /v1/events. Przekierowania nigdy nie są wykonywane, a kolejność nie jest gwarantowana: uzgadniaj stan z zadaniem. - Podpisy
Euphona-Signaturezawiera znacznik czasu i HMAC-SHA256 znacznika czasu, kropki i surowej treści, z sekretem endpointu jako kluczem. Sprawdź go przed parsowaniem, porównuj w stałym czasie i odrzucaj znacznik czasu odbiegający od twojego zegara o więcej niż pięć minut.- Adresy źródłowe
- Dostawy są wysyłane z
65.21.69.162. Zezwól na nie, jeśli twoja zapora filtruje ruch przychodzący, i mimo to weryfikuj podpis: adres, z którego przychodzi żądanie, nie dowodzi, kto je wysłał.
Weryfikacja dostawy (Node.js)
import { createHmac, timingSafeEqual } from "node:crypto";
// Przekaż treść dokładnie w takiej postaci, w jakiej przyszła: JSON sparsowany
// i zserializowany ponownie nie jest wiadomością, którą podpisano.
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);
});
}Eksploatacja
Rzeczy, które gryzą o trzeciej w nocy
Sześć zachowań, o których warto przeczytać, zanim napiszesz integrację, a nie wtedy, gdy cię obudzi.
- Klucze
eu_live_…ieu_test_…, należące do przestrzeni roboczej, pokazywane raz przy tworzeniu i przechowywane jako skrót. Rotacja z okresem nakładania się do trzydziestu dni, więc rotacja nie oznacza przerwy w działaniu. Klucze testowe są bezpłatne w ramach własnego miesięcznego limitu i działają we własnej klasie harmonogramowania; zwracają podgląd zamiast masteru, więc integrację można zbudować od początku do końca, niczego nie kupując.- Błędy
- Zawsze RFC 9457
application/problem+json, ze stałym, czytelnym maszynowocode, czytelnym dla ludzidetaili identyfikatorem żądania. Rozgałęziaj logikę wedługcode, nigdy według treści: treść jest tłumaczona, a kod jest kontraktem. - Idempotencja
Idempotency-Keyprzy każdym POST. Ten sam klucz z tą samą treścią odtwarza pierwotną odpowiedź, dopóki istnieje utworzony zasób; ten sam klucz z inną treścią jest odrzucany. Ponowienie po przekroczeniu czasu zwraca więc pierwotne zadanie, a nie uruchamia drugiego, za które zapłacisz.- Limity
- Przesuwne okno dla każdego klucza ze standardowymi nagłówkami
RateLimit-*; 60 żądań na minutę przy płatności za zużycie, więcej w ramach umowy z zobowiązaniem wolumenowym. Limiter ogranicza tylko częstotliwość żądań: przepustowość zależy od twojego limitu minut i kolejki. - Dwa liczniki, nigdy jeden
- Zadania z kluczem API są naliczane z puli płatności za zużycie; zadania z sesji Studia zużywają minuty z planu. Żadna pula nie może wyczerpać drugiej, a
GET /v1/usageraportuje obie. Limity wydatków są domyślnie włączone. Miejsce na dane to jeden limit, nie dwa: pliki przesłane z kluczem i wyniki zadań API wliczają się, razem ze wszystkim ze Studia, do limitu miejsca na dane w planie przestrzeni roboczej, 2 GB w bezpłatnym planie, a przesyłanie lub zadanie, które się nie zmieści, jest odrzucane z kodemstorage_full. - Request-Id
- Przy każdej odpowiedzi, udanej lub nie. Podaj go w zgłoszeniu do wsparcia, a pozwoli prześledzić wszystko od początku do końca: przez zadanie, przez każdy etap, aż do tego, co faktycznie się stało.
code | title |
|---|---|
| unauthenticated | Wymagane uwierzytelnienie |
| invalid_ | Nieprawidłowy klucz API |
| key_ | Klucz API unieważniony |
| insufficient_ | Niewystarczający zakres |
| email_ | Wymagane potwierdzenie adresu e-mail |
| malformed_ | Nieprawidłowo sformułowane żądanie |
| validation_ | Walidacja żądania nie powiodła się |
| unsupported_ | Nieobsługiwany typ nośnika |
| idempotency_ | Klucz idempotencji użyty ponownie z inną treścią |
| not_ | Nie znaleziono |
| job_ | Zadania nie można anulować |
| job_ | Zadanie nie czeka na odpowiedź |
| file_ | Plik nie jest gotowy |
| file_ | Plik wygasł |
| file_ | Plik jest w użyciu |
| file_ | Plik niedostępny z przyczyn prawnych |
| unsupported_ | Nieobsługiwany format audio |
| unsupported_ | Nieobsługiwana częstotliwość próbkowania |
| unsupported_ | Nieobsługiwana liczba kanałów |
| file_ | Nagranie jest za długie |
| file_ | Nagranie jest za krótkie |
| file_ | Plik jest za duży |
| corrupt_ | Nie udało się zdekodować nagrania |
| rate_ | Przekroczono limit żądań |
| too_ | Za dużo aktywnych zadań |
| quota_ | Przekroczono limit minut |
| spend_ | Osiągnięto limit wydatków |
| test_ | Osiągnięto limit trybu testowego |
| storage_ | Brak miejsca na dane |
| purchase_ | Wymagany zakup |
| payment_ | Wymagana metoda płatności |
| format_ | Format niedostępny w tym planie |
| webhook_ | URL webhooka musi używać HTTPS |
| webhook_ | URL webhooka musi być dostępny publicznie |
| webhook_ | Weryfikacja własności webhooka nie powiodła się |
| internal_ | Błąd wewnętrzny |
| service_ | Usługa niedostępna |
| processing_ | Przetwarzanie nie powiodło się |
| capacity_ | Brak mocy przetwarzania |
| capability_ | Tymczasowo wstrzymane |
| workspace_ | Przestrzeń robocza zawieszona |
| uploads_ | Przesyłanie wstrzymane |
| reauthentication_ | Potwierdź, że to ty |
Stawki
Ile to kosztuje
Za każdą przetworzoną minutę, razy mnożnik funkcji, rozliczane co miesiąc. Subskrypcja nie jest wymagana: przestrzeń robocza API może być w bezpłatnym planie. Tryb testowy jest bezpłatny.
- Pierwsze 100 min w miesiącu
- 0,35 USD
- Do 1000 min
- 0,25 USD
- Do 10 000 min
- 0,15 USD
- Powyżej tego progu albo zobowiązanie wolumenowe
- Porozmawiaj z nami