Aller au contenu
Euphona

Développeurs

Le moteur Euphona, en API

Un seul contrat REST sur api.euphonaai.com/v1. Des tâches asynchrones avec progression SSE en direct, un transfert de fichiers signé, des clés préfixées et un mode test gratuit : un produit à part entière, avec sa propre fenêtre de support de douze mois.

Les clés se créent dans les paramètres du Studio : eu_test_… immédiatement et gratuitement, eu_live_… depuis le même écran une fois une carte enregistrée, avec un décompte dès sa première tâche. Le mode test exécute l’intégralité du contrat.

Tout est une tâche

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
  }
}

Conception

Comment elle est conçue

Quatre décisions non négociables, parce que chacune fait la différence entre une intégration qui survit à sa première panne et une intégration qui n’y survit pas.

Tout est une tâche

Les points de terminaison des traitements renvoient 202 et une ressource de tâche. Il n’existe aucun point de terminaison de traitement synchrone, et il n’y en aura jamais : un morceau de trois minutes en qualité mastering ne tient pas dans un cycle requête/réponse, et une API qui prétend le contraire apprend à chaque intégration à être mal écrite.

L’audio contourne l’API, il ne la traverse pas

POST /v1/files renvoie une destination d’import signée, avec reprise, sur files.euphonaai.com. Les résultats reviennent sous forme d’URL de téléchargement signées qui expirent. L’API elle-même est un plan de contrôle JSON et ne transporte jamais un seul octet d’audio.

Cette API est le produit, pas un sous-produit

Euphona Studio s’appuie sur une autre API, qui lui est propre. C’est voulu : les deux sont des produits distincts qui partagent un même moteur, si bien qu’un changement voulu par l’application ne peut pas atteindre ce contrat, et que la fenêtre de support de douze mois de ce contrat n’est pas un impôt que l’application doit payer. La conséquence, honnêtement : l’application peut faire des choses que cette API ne fait pas, quand cela compte pour vous, demandez-le, et cela devient un point de la feuille de route plutôt qu’une surprise.

Le document est le contrat

Le document OpenAPI 3.1 est la source de vérité. Les routes du serveur et les types en sont générés, et un test bloquant au déploiement maintient les routes servies identiques aux opérations déclarées : un écart entre ce qui est documenté et ce qui est servi fait donc échouer le build, au lieu d’être une chose que nous promettons de surveiller. Les SDK générés à partir du même document arriveront avec la préversion développeur.

Pas à pas

La forme d’une intégration

Créez un fichier, envoyez les octets vers la destination signée, soumettez une tâche et suivez-la jusqu’au bout via SSE. Quatre étapes, et la troisième est la seule qui change d’un traitement à l’autre.

1. Créer le fichier et obtenir une destination d’import

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. Envoyer les octets à files., pas à nous

# Lisez upload.protocol, ne supposez rien. Le mécanisme de reprise est un choix
# d’implémentation interne au contrat, qui peut changer sans nouvelle version
# majeure : c’est précisément pour cela que c’est un champ, pas de la doc.

3. Suivre la tâche

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. Revérifier auprès de la tâche elle-même

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 ressource de tâche est la vérité actuelle de la machine à états : quoi
# qu’un flux ait montré ou manqué, relisez-la avant d’agir. Une connexion SSE
# interrompue se reconnecte avec Last-Event-ID et ne perd rien.

Référence

La surface

La surface de la plateforme, commune à tous les traitements. Chaque traitement ajoute un point de terminaison de création ; la tâche qu’il renvoie passe par tout ce qui suit : le même cycle de vie, les mêmes événements, le même rapport.

Points de terminaison du contrat v1
MéthodeCheminRôle
POST/v1/filesCréer un fichier et obtenir une destination d’import signée, avec reprise.
GET/v1/files/{id}État de l’ingestion, durée mesurée, fréquence d’échantillonnage, canaux, format.
DELETE/v1/files/{id}Supprimer un fichier et ce qui en a été produit ; refusé tant qu’une tâche l’utilise encore.
GET/v1/files/{id}/downloadUne URL de téléchargement signée qui expire.
GET/v1/jobsLa surface de lecture unifiée, paginée par curseur.
GET/v1/jobs/{id}État, étape, progression, décompte et rapport mesuré.
POST/v1/jobs/{id}/cancelAnnuler ; rien n’est facturé avant le début de l’étape de traitement.
POST/v1/jobs/{id}/inputRépondre à ce que demande une tâche à l’état awaiting_input, et la reprendre. Non décompté.
GET/v1/jobs/{id}/eventsDes événements envoyés par le serveur (SSE) pendant toute la vie de la tâche.
POST/v1/projectsRegrouper des fichiers et des tâches ; le Track Graph est propre à chaque projet.
GET/v1/projects/{id}Un projet, avec les identifiants de ses fichiers et de ses tâches.
GET/v1/usageL’état du quota et les deux réserves de décompte.
GET/v1/webhook-endpointsVos points de terminaison de webhook, dans le mode de cette clé.
POST/v1/webhook-endpointsEnregistrez une URL https ; elle répond à un challenge signé avant que quoi que ce soit ne soit enregistré.
GET/v1/webhook-endpoints/{id}Un point de terminaison. Son secret n’est plus jamais affiché.
PATCH/v1/webhook-endpoints/{id}Modifiez ses événements, sa description ou son URL ; une nouvelle URL est vérifiée à nouveau.
DELETE/v1/webhook-endpoints/{id}Le supprimer ; les événements non livrés qui lui étaient destinés sont abandonnés.
POST/v1/webhook-endpoints/{id}/rotate-secretUn nouveau secret de signature ; l’ancien continue de signer pendant une période de chevauchement.
GET/v1/eventsTrente jours d’événements, avec le résultat de chaque livraison.
GET/v1/events/{id}Un événement tel qu’il a été livré, avec ses livraisons.

Webhooks

Savoir quand une tâche est terminée

Des notifications signées quand une tâche se termine ou attend votre réponse, pour qu’une intégration n’ait ni à garder un flux ouvert ni à interroger l’API en boucle.

Événements
job.completed, job.failed, job.canceled et job.awaiting_input. Chacun est un événement dont data est la ressource de la tâche exactement telle que l’interrogation la renvoie.
Événements de l’espace de travail
file.expiring lorsqu’un fichier importé et stocké reçoit une date à partir de laquelle il peut être supprimé, avec le fichier comme data, et usage.threshold lorsqu’un volume d’utilisation atteint 80 % ou 100 % de sa limite. Les deux parviennent aux points de terminaison des deux modes, une fois par fichier ou une fois par seuil et par période.
Enregistrement
Nécessite une clé dotée de la portée webhooks:manage, que les nouvelles clés n’ont pas. L’URL doit utiliser https et ne se résoudre qu’en adresses publiques, et elle doit répondre à un challenge signé par une réponse 2xx qui le renvoie avant que quoi que ce soit ne soit enregistré. Les livraisons sont envoyées uniquement en IPv4 : l’hôte doit donc avoir une adresse IPv4.
Livraison
Au moins une fois : dédupliquez donc sur Euphona-Event-Id. Tout ce qui n’est pas une réponse 2xx en 10 secondes est retenté avec un backoff exponentiel pendant 24 heures, puis placé en lettre morte et conservé dans GET /v1/events. Les redirections ne sont jamais suivies et l’ordre n’est pas garanti : rapprochez-vous de la tâche.
Signatures
Euphona-Signature contient un horodatage et un HMAC-SHA256 de l’horodatage, d’un point et du corps brut, avec le secret du point de terminaison pour clé. Vérifiez-le avant l’analyse, comparez en temps constant et refusez un horodatage éloigné de plus de cinq minutes de votre horloge.
Adresses d’origine
Les livraisons sont envoyées depuis 65.21.69.162. Autorisez-les si votre pare-feu filtre le trafic entrant, et vérifiez quand même la signature : l’adresse d’où provient une requête ne prouve pas qui l’a envoyée.

Vérifier une livraison (Node.js)

import { createHmac, timingSafeEqual } from "node:crypto";

// Passez le corps exactement tel qu’il est arrivé : du JSON analysé puis
// resérialisé n’est pas le message qui a été signé.
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);
  });
}

Exploitation

Les pièges de trois heures du matin

Les six comportements à lire avant d’écrire l’intégration, plutôt qu’après qu’elle vous a réveillé en pleine nuit.

Clés
eu_live_… et eu_test_…, rattachées à un espace de travail, affichées une seule fois à la création et stockées sous forme hachée. Faites-les tourner avec une fenêtre de chevauchement allant jusqu’à trente jours : une rotation n’est donc pas une panne. Les clés de test sont gratuites dans la limite de leur propre plafond mensuel et s’exécutent dans leur propre classe de planification ; elles renvoient une pré-écoute plutôt qu’un master, si bien qu’une intégration peut être construite de bout en bout sans rien acheter.
Erreurs
RFC 9457 application/problem+json, toujours, avec un code stable et lisible par machine, un detail lisible par un humain et l’identifiant de requête. Basez votre logique sur code, jamais sur le texte : le texte est traduit, le code est un contrat.
Idempotence
Idempotency-Key sur chaque POST. La même clé avec le même corps rejoue la réponse d’origine tant que la ressource créée existe ; la même clé avec un corps différent est refusée. Une nouvelle tentative après un dépassement de délai renvoie donc la tâche d’origine au lieu d’en lancer une seconde qui vous serait facturée.
Limites
Fenêtre glissante par clé, avec les en-têtes standard RateLimit-* ; 60 requêtes par minute en paiement à l’usage, davantage avec un contrat d’engagement de volume. Le limiteur ne régit que la fréquence des requêtes : le débit dépend de votre quota et de la file d’attente.
Deux compteurs, jamais un seul
Les tâches lancées avec une clé API sont décomptées sur la réserve à l’usage ; les tâches lancées depuis une session Studio consomment les minutes de la formule. Aucune réserve ne peut vider l’autre, et GET /v1/usage indique les deux. Les plafonds de dépenses sont activés par défaut. Le stockage est un seul quota, pas deux : les fichiers importés avec une clé et les résultats des tâches de l’API sont décomptés, avec tout ce qui vient du Studio, du quota de stockage de la formule de l’espace de travail, 2 Go en formule gratuite, et un import ou une tâche qui n’y tiendrait pas est refusé avec storage_full.
Request-Id
Sur chaque réponse, succès comme échec. Indiquez-le dans une demande d’assistance et elle se résout de bout en bout : à travers la tâche, à travers chaque étape, jusqu’à ce qui s’est réellement passé.
Erreurs
codetitle
unauthenticatedAuthentification requise
invalid_keyClé API non valide
key_revokedClé API révoquée
insufficient_scopePortée insuffisante
email_verification_requiredVérification de l’adresse e-mail requise
malformed_requestRequête mal formée
validation_failedÉchec de la validation de la requête
unsupported_media_typeType de média non pris en charge
idempotency_key_reuseClé d’idempotence réutilisée avec un autre corps
not_foundIntrouvable
job_not_cancelableLa tâche ne peut pas être annulée
job_not_awaiting_inputLa tâche n’attend pas de réponse
file_not_readyLe fichier n’est pas prêt
file_expiredLe fichier a expiré
file_in_useFichier en cours d’utilisation
file_disabledFichier indisponible pour des raisons juridiques
unsupported_formatFormat audio non pris en charge
unsupported_sample_rateFréquence d’échantillonnage non prise en charge
unsupported_channel_countNombre de canaux non pris en charge
file_too_longAudio trop long
file_too_shortAudio trop court
file_too_largeFichier trop volumineux
corrupt_mediaL’audio n’a pas pu être décodé
rate_limitedLimite de requêtes dépassée
too_many_active_jobsTrop de tâches en cours
quota_exceededQuota dépassé
spend_cap_reachedPlafond de dépenses atteint
test_mode_cap_reachedPlafond du mode test atteint
storage_fullStockage plein
purchase_requiredAchat requis
payment_method_requiredMoyen de paiement requis
format_not_in_planFormat non inclus dans cette formule
webhook_url_not_httpsL’URL du webhook doit être en HTTPS
webhook_url_not_publicL’URL du webhook doit être accessible publiquement
webhook_verification_failedÉchec de la vérification de propriété du webhook
internal_errorErreur interne
service_unavailableService indisponible
processing_failedÉchec du traitement
capacity_unavailableCapacité indisponible
capability_pausedTemporairement suspendu
workspace_suspendedEspace de travail suspendu
uploads_blockedImports suspendus
reauthentication_requiredConfirmez qu’il s’agit bien de vous

Tarifs

Ce que cela coûte

À la minute traitée, multipliée par le coefficient du traitement, facturée chaque mois. Aucun abonnement requis, un espace de travail API peut être en formule gratuite. Le mode test est gratuit.

100 premières minutes du mois
0,35 $US
Jusqu’à 1 000
0,25 $US
Jusqu’à 10 000
0,15 $US
Au-delà, ou avec engagement de volume
Nous contacter