Sviluppatori
Il motore di Euphona, come API
Un solo contratto REST su api.euphonaai.com/v1. Elaborazioni asincrone con avanzamento in tempo reale via SSE, trasferimento firmato dei file, chiavi con prefisso e una modalità test gratuita: un prodotto vero e proprio, con una sua finestra di supporto di dodici mesi.
Le chiavi si creano nelle impostazioni dello Studio: eu_test_… subito e gratis, eu_live_… dalla stessa schermata una volta registrata una carta, con conteggio a consumo dalla prima elaborazione. La modalità test esegue l’intero contratto.
Tutto è un’elaborazione
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
}
}Progettazione
Come è progettata
Quattro decisioni non negoziabili, perché ognuna fa la differenza tra un’integrazione che sopravvive al suo primo disservizio e una che non ci riesce.
Tutto è un’elaborazione
L’audio passa accanto all’API, non attraverso
Questa API è il prodotto, non un sottoprodotto
Il documento è il contratto
Procedura
La forma di un’integrazione
Crea un file, invia i byte alla destinazione firmata, invia un’elaborazione e seguila fino alla fine via SSE. Quattro passaggi, e il terzo è l’unico che cambia da una funzionalità all’altra.
1. Crea il file e ottieni una destinazione di caricamento
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. Invia i byte a files., non a noi
# Leggi upload.protocol, non darlo per scontato. Il meccanismo di ripresa è una scelta
# implementativa interna al contratto e può cambiare senza una nuova versione
# maggiore: è proprio per questo che è un campo e non documentazione.3. Segui l’elaborazione
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. Riconcilia con l’elaborazione stessa
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}}
# La risorsa job è la verità attuale della macchina a stati: qualunque cosa
# lo stream abbia mostrato o perso, rileggila prima di agire. Una connessione SSE
# interrotta si riconnette con Last-Event-ID e non perde nulla.Riferimento
La superficie
La superficie della piattaforma condivisa da tutte le funzionalità. Ogni funzionalità aggiunge un endpoint di creazione; l’elaborazione che restituisce passa per tutto ciò che c’è qui sotto: lo stesso ciclo di vita, gli stessi eventi, lo stesso report.
| Metodo | Percorso | Cosa fa |
|---|---|---|
| POST | /v1/files | Crea un file e ottieni una destinazione di caricamento firmata e riprendibile. |
| GET | /v1/files/{id} | Stato dell’acquisizione, durata misurata, frequenza di campionamento, canali, formato. |
| DELETE | /v1/files/{id} | Elimina un file e ciò che ne è stato ricavato; rifiutato finché un’elaborazione lo sta ancora usando. |
| GET | /v1/files/{id}/download | Un URL di download firmato e con scadenza. |
| GET | /v1/jobs | La superficie di lettura unificata, con paginazione a cursore. |
| GET | /v1/jobs/{id} | Stato, fase, avanzamento, conteggio e il report misurato. |
| POST | /v1/jobs/{id}/cancel | Annulla; nessun addebito prima che inizi la fase di elaborazione (process). |
| POST | /v1/jobs/{id}/input | Rispondi a ciò che chiede un’elaborazione in stato awaiting_input e riprendila. Non conteggiato. |
| GET | /v1/jobs/{id}/events | Server-sent events per tutta la vita dell’elaborazione. |
| POST | /v1/projects | Raggruppa file ed elaborazioni; il Track Graph è per progetto. |
| GET | /v1/projects/{id} | Un progetto, con gli ID dei suoi file e delle sue elaborazioni. |
| GET | /v1/usage | Lo stato della quota e le due dotazioni conteggiate. |
| GET | /v1/webhook-endpoints | I tuoi endpoint webhook, nella modalità di questa chiave. |
| POST | /v1/webhook-endpoints | Registra un URL https; risponde a una challenge firmata prima che venga salvato qualsiasi cosa. |
| GET | /v1/webhook-endpoints/{id} | Un endpoint. Il suo segreto non viene più mostrato. |
| PATCH | /v1/webhook-endpoints/{id} | Cambia eventi, descrizione o URL; un nuovo URL viene verificato di nuovo. |
| DELETE | /v1/webhook-endpoints/{id} | Eliminalo; gli eventi non ancora consegnati a lui vengono scartati. |
| POST | /v1/webhook-endpoints/{id}/rotate-secret | Un nuovo segreto di firma; quello vecchio continua a firmare durante un periodo di sovrapposizione. |
| GET | /v1/events | Trenta giorni di eventi, con l’esito di ogni consegna. |
| GET | /v1/events/{id} | Un evento così come è stato consegnato, con le sue consegne. |
Webhook
Sapere quando un’elaborazione è finita
Notifiche firmate quando un’elaborazione termina o aspetta la tua risposta, così un’integrazione non deve tenere aperto uno stream né fare polling.
- Eventi
job.completed,job.failed,job.canceledejob.awaiting_input. Ognuno è un evento il cuidataè la risorsa dell’elaborazione esattamente come la restituisce il polling.- Eventi dello spazio di lavoro
file.expiringquando a un file caricato e conservato viene assegnata una data a partire dalla quale può essere rimosso, con il file comedata, eusage.thresholdquando una riserva di utilizzo raggiunge l’80% o il 100% del suo limite. Entrambi raggiungono gli endpoint di qualsiasi modalità, una volta per file o una volta per soglia e periodo.- Registrazione
- Richiede una chiave con lo scope
webhooks:manage, che le nuove chiavi non hanno. L’URL deve usare https e risolversi solo in indirizzi pubblici, e deve rispondere a una challenge firmata con una risposta 2xx che la restituisca prima che venga salvato qualsiasi cosa. Le consegne vengono inviate solo via IPv4, quindi l’host deve avere un indirizzo IPv4. - Consegna
- Almeno una volta, quindi deduplica su
Euphona-Event-Id. Tutto ciò che non è una risposta 2xx entro 10 secondi viene ritentato con backoff esponenziale per 24 ore, poi finisce nella coda dei messaggi non recapitati e resta inGET /v1/events. I reindirizzamenti non vengono mai seguiti e l’ordine non è garantito: riconcilia con l’elaborazione. - Firme
Euphona-Signaturecontiene un timestamp e un HMAC-SHA256 del timestamp, di un punto e del corpo grezzo, con il segreto dell’endpoint come chiave. Verificalo prima del parsing, confronta in tempo costante e rifiuta un timestamp che si discosti più di cinque minuti dal tuo orologio.- Indirizzi di origine
- Le consegne partono da
65.21.69.162. Consentili se il tuo firewall filtra il traffico in entrata, e verifica comunque la firma: l’indirizzo da cui arriva una richiesta non dimostra chi l’ha inviata.
Verificare una consegna (Node.js)
import { createHmac, timingSafeEqual } from "node:crypto";
// Passa il corpo esattamente come è arrivato: un JSON analizzato e
// serializzato di nuovo non è il messaggio che è stato firmato.
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);
});
}Operatività
Le cose che ti mordono alle tre di notte
I sei comportamenti da leggere prima di scrivere l’integrazione, non dopo che ti ha svegliato.
- Chiavi
eu_live_…eeu_test_…, appartenenti a uno spazio di lavoro, mostrate una sola volta alla creazione e conservate come hash. Ruotale con una finestra di sovrapposizione fino a trenta giorni, così una rotazione non diventa un disservizio. Le chiavi di test sono gratuite entro un proprio tetto mensile e girano in una propria classe di scheduling; restituiscono un’anteprima invece di un master, così puoi costruire un’integrazione dall’inizio alla fine senza comprare nulla.- Errori
- RFC 9457
application/problem+json, sempre, con uncodestabile e leggibile dalle macchine, undetailper le persone e l’ID della richiesta. Basa le tue scelte sucode, mai sul testo: il testo è localizzato, il codice è un contratto. - Idempotenza
Idempotency-Keysu ogni POST. La stessa chiave con lo stesso corpo restituisce di nuovo la risposta originale finché esiste la risorsa che ha creato; la stessa chiave con un corpo diverso viene rifiutata. Un nuovo tentativo dopo un timeout restituisce quindi l’elaborazione originale invece di avviarne una seconda che ti verrebbe addebitata.- Limiti
- Finestra scorrevole per chiave con gli header standard
RateLimit-*; 60 richieste al minuto a consumo, di più con un contratto di impegno di volume. Il limitatore regola solo la frequenza delle richieste: il throughput dipende dalla tua quota e dalla coda. - Due contatori, mai uno
- Le elaborazioni con chiave API vengono conteggiate sulla dotazione a consumo; quelle avviate da una sessione dello Studio usano i minuti del piano. Nessuna delle due dotazioni può esaurire l’altra, e
GET /v1/usagele riporta entrambe. I tetti di spesa sono attivi per impostazione predefinita. Lo spazio di archiviazione è una sola dotazione, non due: i file caricati con una chiave e i risultati delle elaborazioni dell’API rientrano, insieme a tutto ciò che arriva dallo Studio, nella dotazione di spazio del piano dello spazio di lavoro, 2 GB sul piano gratuito, e un caricamento o un’elaborazione che non ci sta viene rifiutato constorage_full. - Request-Id
- In ogni risposta, che sia un successo o un errore. Indicalo in una richiesta di assistenza e lo si ricostruisce dall’inizio alla fine: attraverso l’elaborazione, attraverso ogni fase, fino a ciò che è successo davvero.
code | title |
|---|---|
| unauthenticated | Autenticazione richiesta |
| invalid_ | Chiave API non valida |
| key_ | Chiave API revocata |
| insufficient_ | Scope insufficiente |
| email_ | Verifica dell’email richiesta |
| malformed_ | Richiesta malformata |
| validation_ | Convalida della richiesta non riuscita |
| unsupported_ | Tipo di contenuto non supportato |
| idempotency_ | Chiave di idempotenza riutilizzata con un corpo diverso |
| not_ | Non trovato |
| job_ | Elaborazione non annullabile |
| job_ | L’elaborazione non è in attesa di input |
| file_ | File non pronto |
| file_ | File scaduto |
| file_ | File in uso |
| file_ | File non disponibile per motivi legali |
| unsupported_ | Formato audio non supportato |
| unsupported_ | Frequenza di campionamento non supportata |
| unsupported_ | Numero di canali non supportato |
| file_ | Audio troppo lungo |
| file_ | Audio troppo corto |
| file_ | File troppo grande |
| corrupt_ | Impossibile decodificare l’audio |
| rate_ | Limite di richieste superato |
| too_ | Troppe elaborazioni attive |
| quota_ | Quota superata |
| spend_ | Tetto di spesa raggiunto |
| test_ | Limite della modalità test raggiunto |
| storage_ | Spazio di archiviazione pieno |
| purchase_ | Acquisto richiesto |
| payment_ | Metodo di pagamento richiesto |
| format_ | Formato non incluso in questo piano |
| webhook_ | L’URL del webhook deve essere HTTPS |
| webhook_ | L’URL del webhook deve essere raggiungibile pubblicamente |
| webhook_ | Verifica della proprietà del webhook non riuscita |
| internal_ | Errore interno |
| service_ | Servizio non disponibile |
| processing_ | Elaborazione non riuscita |
| capacity_ | Capacità non disponibile |
| capability_ | Temporaneamente sospesa |
| workspace_ | Spazio di lavoro sospeso |
| uploads_ | Caricamenti sospesi |
| reauthentication_ | Conferma che sei tu |
Tariffe
Quanto costa
Per minuto elaborato, per il moltiplicatore della funzionalità, fatturato mensilmente. Nessun abbonamento richiesto: uno spazio di lavoro API può essere sul piano gratuito. La modalità test è gratuita.
- Primi 100 minuti al mese
- 0,35 USD
- Fino a 1000
- 0,25 USD
- Fino a 10.000
- 0,15 USD
- Oltre, o con impegno di volume
- Contattaci