Vai al contenuto
Euphona

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

Gli endpoint delle funzionalità restituiscono 202 e una risorsa job. Non esistono endpoint di elaborazione sincroni e non esisteranno mai: il mastering di un brano di tre minuti non è un ciclo richiesta/risposta, e un’API che finge il contrario insegna a ogni integrazione a essere scritta male.

L’audio passa accanto all’API, non attraverso

POST /v1/files restituisce una destinazione di caricamento firmata e riprendibile su files.euphonaai.com. I risultati tornano come URL di download firmati e con scadenza. L’API in sé è un piano di controllo JSON e non trasporta mai un solo byte di audio.

Questa API è il prodotto, non un sottoprodotto

Euphona Studio usa un’API diversa, tutta sua. È voluto: sono due prodotti separati che condividono un solo motore, così una modifica di cui ha bisogno l’app non può toccare questo contratto, e la finestra di supporto di dodici mesi di questo contratto non è una tassa che l’app deve pagare. La conseguenza, detta onestamente, è che l’app può fare cose che questa API non fa: quando per te conta, chiedilo, e diventa una voce della roadmap invece di una sorpresa.

Il documento è il contratto

Il documento OpenAPI 3.1 è la fonte di verità. Le route del server e i tipi vengono generati a partire da esso, e un test vincolante per il deploy mantiene le route servite uguali alle operazioni dichiarate - così una divergenza tra ciò che è documentato e ciò che viene servito fa fallire la build, invece di essere qualcosa che promettiamo di tenere d’occhio. Gli SDK generati dallo stesso documento arriveranno con la developer preview.

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.

Endpoint del contratto v1
MetodoPercorsoCosa fa
POST/v1/filesCrea 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}/downloadUn URL di download firmato e con scadenza.
GET/v1/jobsLa superficie di lettura unificata, con paginazione a cursore.
GET/v1/jobs/{id}Stato, fase, avanzamento, conteggio e il report misurato.
POST/v1/jobs/{id}/cancelAnnulla; nessun addebito prima che inizi la fase di elaborazione (process).
POST/v1/jobs/{id}/inputRispondi a ciò che chiede un’elaborazione in stato awaiting_input e riprendila. Non conteggiato.
GET/v1/jobs/{id}/eventsServer-sent events per tutta la vita dell’elaborazione.
POST/v1/projectsRaggruppa 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/usageLo stato della quota e le due dotazioni conteggiate.
GET/v1/webhook-endpointsI tuoi endpoint webhook, nella modalità di questa chiave.
POST/v1/webhook-endpointsRegistra 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-secretUn nuovo segreto di firma; quello vecchio continua a firmare durante un periodo di sovrapposizione.
GET/v1/eventsTrenta 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.canceled e job.awaiting_input. Ognuno è un evento il cui data è la risorsa dell’elaborazione esattamente come la restituisce il polling.
Eventi dello spazio di lavoro
file.expiring quando a un file caricato e conservato viene assegnata una data a partire dalla quale può essere rimosso, con il file come data, e usage.threshold quando 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 in GET /v1/events. I reindirizzamenti non vengono mai seguiti e l’ordine non è garantito: riconcilia con l’elaborazione.
Firme
Euphona-Signature contiene 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_… e eu_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 un code stabile e leggibile dalle macchine, un detail per le persone e l’ID della richiesta. Basa le tue scelte su code, mai sul testo: il testo è localizzato, il codice è un contratto.
Idempotenza
Idempotency-Key su 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/usage le 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 con storage_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.
Errori
codetitle
unauthenticatedAutenticazione richiesta
invalid_keyChiave API non valida
key_revokedChiave API revocata
insufficient_scopeScope insufficiente
email_verification_requiredVerifica dell’email richiesta
malformed_requestRichiesta malformata
validation_failedConvalida della richiesta non riuscita
unsupported_media_typeTipo di contenuto non supportato
idempotency_key_reuseChiave di idempotenza riutilizzata con un corpo diverso
not_foundNon trovato
job_not_cancelableElaborazione non annullabile
job_not_awaiting_inputL’elaborazione non è in attesa di input
file_not_readyFile non pronto
file_expiredFile scaduto
file_in_useFile in uso
file_disabledFile non disponibile per motivi legali
unsupported_formatFormato audio non supportato
unsupported_sample_rateFrequenza di campionamento non supportata
unsupported_channel_countNumero di canali non supportato
file_too_longAudio troppo lungo
file_too_shortAudio troppo corto
file_too_largeFile troppo grande
corrupt_mediaImpossibile decodificare l’audio
rate_limitedLimite di richieste superato
too_many_active_jobsTroppe elaborazioni attive
quota_exceededQuota superata
spend_cap_reachedTetto di spesa raggiunto
test_mode_cap_reachedLimite della modalità test raggiunto
storage_fullSpazio di archiviazione pieno
purchase_requiredAcquisto richiesto
payment_method_requiredMetodo di pagamento richiesto
format_not_in_planFormato non incluso in questo piano
webhook_url_not_httpsL’URL del webhook deve essere HTTPS
webhook_url_not_publicL’URL del webhook deve essere raggiungibile pubblicamente
webhook_verification_failedVerifica della proprietà del webhook non riuscita
internal_errorErrore interno
service_unavailableServizio non disponibile
processing_failedElaborazione non riuscita
capacity_unavailableCapacità non disponibile
capability_pausedTemporaneamente sospesa
workspace_suspendedSpazio di lavoro sospeso
uploads_blockedCaricamenti sospesi
reauthentication_requiredConferma 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