Pular para o conteúdo
Euphona

Desenvolvedores

O motor da Euphona, como API

Um só contrato REST em api.euphonaai.com/v1. Tarefas assíncronas com progresso ao vivo por SSE, transferência de arquivos assinada, chaves com prefixo e um modo de teste gratuito: um produto próprio, com a sua própria janela de suporte de doze meses.

As chaves são criadas nas configurações do Studio: eu_test_… na hora e de graça, eu_live_… na mesma tela depois que houver um cartão cadastrado, cobrada a partir da primeira tarefa. O modo de teste executa o contrato completo.

Tudo é uma tarefa

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

Como ela foi projetada

Quatro decisões inegociáveis, porque cada uma delas é a diferença entre uma integração que sobrevive à primeira queda e uma que não sobrevive.

Tudo é uma tarefa

Os endpoints das funções retornam 202 e um recurso de tarefa. Não há endpoints de processamento síncrono e nunca haverá: uma música de três minutos com qualidade de masterização não é um ciclo de requisição e resposta, e uma API que finge o contrário ensina todas as integrações a serem escritas do jeito errado.

O áudio passa ao lado da API, não por ela

POST /v1/files retorna um destino de upload assinado e retomável em files.euphonaai.com. Os resultados voltam como URLs de download assinadas e com prazo de validade. A API em si é um plano de controle JSON e nunca transporta um byte de áudio.

Esta API é o produto, não um subproduto

O Euphona Studio usa outra API, própria. Isso é deliberado: os dois são produtos separados que compartilham um motor, então uma mudança que o app queira não chega a este contrato, e a janela de suporte de doze meses deste contrato não é um custo que o app precise pagar. A consequência honesta é que o app pode fazer coisas que esta API não faz: quando isso importar para você, peça, e o pedido entra no roadmap em vez de virar uma surpresa.

O documento é o contrato

O documento OpenAPI 3.1 é a fonte da verdade. As rotas do servidor e os tipos são gerados a partir dele, e um teste que bloqueia o deploy mantém as rotas servidas iguais às operações declaradas: então uma divergência entre o que está documentado e o que é servido quebra o build, em vez de ser algo que prometemos vigiar. SDKs gerados a partir do mesmo documento chegam com a prévia para desenvolvedores.

Passo a passo

O formato de uma integração

Crie um arquivo, envie os bytes para o destino assinado, envie uma tarefa e acompanhe a conclusão por SSE. Quatro passos, e o terceiro é a única parte que muda de uma função para outra.

1. Crie o arquivo e obtenha um destino de upload

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. Envie os bytes para files., não para nós

# Leia upload.protocol, não presuma um valor. O mecanismo retomável é uma
# escolha de implementação dentro do contrato e pode mudar sem uma nova
# versão major, e é exatamente por isso que ele é um campo, e não documentação.

3. Acompanhe a tarefa

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. Confira com a própria tarefa

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

# O recurso da tarefa é a verdade atual da máquina de estados: o que quer
# que um stream tenha mostrado ou perdido, releia o recurso antes de agir.
# Uma conexão SSE que cai se reconecta com Last-Event-ID e não perde nada.

Referência

A superfície

A superfície da plataforma que todas as funções compartilham. Cada função acrescenta um endpoint de criação; a tarefa que ele retorna passa por tudo o que está abaixo: o mesmo ciclo de vida, os mesmos eventos, o mesmo relatório.

Endpoints do contrato v1
MétodoCaminhoO que faz
POST/v1/filesCria um arquivo e obtém um destino de upload assinado e retomável.
GET/v1/files/{id}Status da ingestão, duração medida, taxa de amostragem, canais, formato.
DELETE/v1/files/{id}Exclui um arquivo e o que foi feito a partir dele; recusado enquanto uma tarefa ainda o estiver usando.
GET/v1/files/{id}/downloadUma URL de download assinada e com prazo de validade.
GET/v1/jobsA superfície de leitura unificada, com paginação por cursor.
GET/v1/jobs/{id}Status, etapa, progresso, contabilização e o relatório medido.
POST/v1/jobs/{id}/cancelCancela; sem cobrança antes do início da etapa de processamento.
POST/v1/jobs/{id}/inputResponde ao que uma tarefa em awaiting_input pergunta e a retoma. Não é cobrado.
GET/v1/jobs/{id}/eventsServer-sent events durante toda a vida da tarefa.
POST/v1/projectsAgrupa arquivos e tarefas; o Track Graph é por projeto.
GET/v1/projects/{id}Um projeto, com os IDs dos seus arquivos e tarefas.
GET/v1/usageO estado da cota e os dois saldos de consumo.
GET/v1/webhook-endpointsSeus endpoints de webhook, no modo desta chave.
POST/v1/webhook-endpointsRegistre uma URL https; ela responde a um desafio assinado antes que qualquer coisa seja guardada.
GET/v1/webhook-endpoints/{id}Um endpoint. O segredo dele nunca mais é mostrado.
PATCH/v1/webhook-endpoints/{id}Altere os eventos, a descrição ou a URL; uma URL nova é verificada de novo.
DELETE/v1/webhook-endpoints/{id}Exclua; os eventos ainda não entregues para ele são descartados.
POST/v1/webhook-endpoints/{id}/rotate-secretUm novo segredo de assinatura; o antigo continua assinando durante um período de transição.
GET/v1/eventsTrinta dias de eventos, com o resultado de cada entrega.
GET/v1/events/{id}Um evento como foi entregue, com as entregas dele.

Webhooks

Saiba quando uma tarefa termina

Notificações assinadas quando uma tarefa termina ou espera sua resposta, para que uma integração não precise manter um stream aberto nem fazer polling.

Eventos
job.completed, job.failed, job.canceled e job.awaiting_input. Cada um é um evento cujo data é o recurso da tarefa exatamente como o polling o retorna.
Eventos do espaço de trabalho
file.expiring quando um upload armazenado recebe uma data a partir da qual pode ser removido, com o arquivo como data, e usage.threshold quando um saldo de uso chega a 80% ou 100% do seu limite. Os dois chegam a endpoints de qualquer modo, uma vez por arquivo ou uma vez por limite e período.
Registro
Exige uma chave com o escopo webhooks:manage, que as chaves novas não têm. A URL precisa usar https e resolver apenas para endereços públicos, e precisa responder a um desafio assinado com uma resposta 2xx que o devolva antes que qualquer coisa seja guardada. As entregas são enviadas apenas por IPv4, então o host precisa ter um endereço IPv4.
Entrega
Pelo menos uma vez, então elimine duplicatas por Euphona-Event-Id. Qualquer coisa que não seja um 2xx em 10 segundos é tentada de novo com backoff exponencial por 24 horas; depois vai para a fila de mensagens mortas e fica em GET /v1/events. Redirecionamentos nunca são seguidos e a ordem não é garantida: confira com a tarefa.
Assinaturas
Euphona-Signature traz um carimbo de data/hora e um HMAC-SHA256 do carimbo, de um ponto e do corpo bruto, com o segredo do endpoint como chave. Verifique antes de interpretar o JSON, compare em tempo constante e recuse um carimbo a mais de cinco minutos do seu relógio.
Endereços de origem
As entregas são enviadas de 65.21.69.162. Libere esses endereços se o seu firewall filtra o tráfego de entrada e, mesmo assim, verifique a assinatura: o endereço de onde vem uma requisição não prova quem a enviou.

Verificar uma entrega (Node.js)

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

// Passe o corpo exatamente como chegou: um JSON interpretado e serializado
// de novo não é a mensagem que foi assinada.
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);
  });
}

Operação

O que pega você às três da manhã

Os seis comportamentos que vale a pena ler antes de escrever a integração, e não depois que ela acordar você.

Chaves
eu_live_… e eu_test_…, pertencentes a um espaço de trabalho, mostradas uma única vez na criação e armazenadas como hash. Faça a rotação com uma janela de sobreposição de até trinta dias, para que uma rotação não vire uma queda. As chaves de teste são gratuitas dentro do próprio limite mensal e rodam na sua própria classe de agendamento; elas retornam uma prévia em vez de um master, para que uma integração possa ser construída de ponta a ponta sem comprar nada.
Erros
RFC 9457 application/problem+json, sempre, com um code estável e legível por máquina, um detail legível por pessoas e o ID da requisição. Tome decisões com base em code, nunca no texto: o texto é traduzido e o código é um contrato.
Idempotência
Idempotency-Key em todo POST. A mesma chave com o mesmo corpo repete a resposta original enquanto o recurso criado existir; a mesma chave com um corpo diferente é recusada. Por isso, uma nova tentativa depois de um timeout retorna a tarefa original em vez de iniciar uma segunda, que seria cobrada.
Limites
Janela deslizante por chave, com os cabeçalhos padrão RateLimit-*; 60 requisições por minuto no pagamento conforme o uso, mais com um contrato com compromisso de uso. O limitador controla apenas a frequência das requisições: o volume processado é controlado pela sua cota e pela fila.
Dois medidores, nunca um
As tarefas feitas com chave de API são contabilizadas no saldo de pagamento conforme o uso; as tarefas de sessões do Studio consomem os minutos do plano. Nenhum saldo pode esvaziar o outro, e GET /v1/usage informa os dois. Os limites de gastos vêm ativados por padrão. O armazenamento é uma franquia só, não duas: os arquivos enviados com uma chave e os resultados das tarefas da API contam, junto com tudo o que está no Studio, para a franquia de armazenamento do plano do espaço de trabalho, 2 GB no plano gratuito, e um upload ou uma tarefa que não caiba é recusado com storage_full.
Request-Id
Em toda resposta, de sucesso ou de falha. Informe-o em um pedido de suporte e ele é rastreado de ponta a ponta: pela tarefa, por cada etapa, até o que realmente aconteceu.
Erros
codetitle
unauthenticatedAutenticação necessária
invalid_keyChave de API inválida
key_revokedChave de API revogada
insufficient_scopeEscopo insuficiente
email_verification_requiredVerificação de e-mail necessária
malformed_requestRequisição malformada
validation_failedFalha na validação da requisição
unsupported_media_typeTipo de mídia não aceito
idempotency_key_reuseChave de idempotência reutilizada com outro corpo
not_foundNão encontrado
job_not_cancelableA tarefa não pode ser cancelada
job_not_awaiting_inputA tarefa não está aguardando resposta
file_not_readyO arquivo não está pronto
file_expiredO arquivo expirou
file_in_useArquivo em uso
file_disabledArquivo indisponível por motivos legais
unsupported_formatFormato de áudio não aceito
unsupported_sample_rateTaxa de amostragem não aceita
unsupported_channel_countNúmero de canais não aceito
file_too_longÁudio longo demais
file_too_shortÁudio curto demais
file_too_largeArquivo grande demais
corrupt_mediaNão foi possível decodificar o áudio
rate_limitedLimite de requisições excedido
too_many_active_jobsTarefas ativas demais
quota_exceededCota excedida
spend_cap_reachedLimite de gastos atingido
test_mode_cap_reachedLimite do modo de teste atingido
storage_fullArmazenamento cheio
purchase_requiredCompra necessária
payment_method_requiredForma de pagamento necessária
format_not_in_planFormato não incluído neste plano
webhook_url_not_httpsA URL do webhook precisa usar HTTPS
webhook_url_not_publicA URL do webhook precisa ser roteável publicamente
webhook_verification_failedFalha na verificação de propriedade do webhook
internal_errorErro interno
service_unavailableServiço indisponível
processing_failedFalha no processamento
capacity_unavailableCapacidade indisponível
capability_pausedPausa temporária
workspace_suspendedEspaço de trabalho suspenso
uploads_blockedUploads pausados
reauthentication_requiredConfirme que é você

Tarifas

Quanto custa

Por minuto processado, vezes o multiplicador da função, cobrado mensalmente. Não é preciso assinatura: um espaço de trabalho de API pode estar no plano gratuito. O modo de teste é grátis.

Primeiros 100 minutos por mês
US$ 0,35
Até 1.000
US$ 0,25
Até 10.000
US$ 0,15
Acima disso, ou uso contratado
Fale conosco