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
O áudio passa ao lado da API, não por ela
Esta API é o produto, não um subproduto
O documento é o contrato
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.
| Método | Caminho | O que faz |
|---|---|---|
| POST | /v1/files | Cria 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}/download | Uma URL de download assinada e com prazo de validade. |
| GET | /v1/jobs | A 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}/cancel | Cancela; sem cobrança antes do início da etapa de processamento. |
| POST | /v1/jobs/{id}/input | Responde ao que uma tarefa em awaiting_input pergunta e a retoma. Não é cobrado. |
| GET | /v1/jobs/{id}/events | Server-sent events durante toda a vida da tarefa. |
| POST | /v1/projects | Agrupa arquivos e tarefas; o Track Graph é por projeto. |
| GET | /v1/projects/{id} | Um projeto, com os IDs dos seus arquivos e tarefas. |
| GET | /v1/usage | O estado da cota e os dois saldos de consumo. |
| GET | /v1/webhook-endpoints | Seus endpoints de webhook, no modo desta chave. |
| POST | /v1/webhook-endpoints | Registre 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-secret | Um novo segredo de assinatura; o antigo continua assinando durante um período de transição. |
| GET | /v1/events | Trinta 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.canceledejob.awaiting_input. Cada um é um evento cujodataé o recurso da tarefa exatamente como o polling o retorna.- Eventos do espaço de trabalho
file.expiringquando um upload armazenado recebe uma data a partir da qual pode ser removido, com o arquivo comodata, eusage.thresholdquando 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 emGET /v1/events. Redirecionamentos nunca são seguidos e a ordem não é garantida: confira com a tarefa. - Assinaturas
Euphona-Signaturetraz 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_…eeu_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 umcodeestável e legível por máquina, umdetaillegível por pessoas e o ID da requisição. Tome decisões com base emcode, nunca no texto: o texto é traduzido e o código é um contrato. - Idempotência
Idempotency-Keyem 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/usageinforma 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 comstorage_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.
code | title |
|---|---|
| unauthenticated | Autenticação necessária |
| invalid_ | Chave de API inválida |
| key_ | Chave de API revogada |
| insufficient_ | Escopo insuficiente |
| email_ | Verificação de e-mail necessária |
| malformed_ | Requisição malformada |
| validation_ | Falha na validação da requisição |
| unsupported_ | Tipo de mídia não aceito |
| idempotency_ | Chave de idempotência reutilizada com outro corpo |
| not_ | Não encontrado |
| job_ | A tarefa não pode ser cancelada |
| job_ | A tarefa não está aguardando resposta |
| file_ | O arquivo não está pronto |
| file_ | O arquivo expirou |
| file_ | Arquivo em uso |
| file_ | Arquivo indisponível por motivos legais |
| unsupported_ | Formato de áudio não aceito |
| unsupported_ | Taxa de amostragem não aceita |
| unsupported_ | Número de canais não aceito |
| file_ | Áudio longo demais |
| file_ | Áudio curto demais |
| file_ | Arquivo grande demais |
| corrupt_ | Não foi possível decodificar o áudio |
| rate_ | Limite de requisições excedido |
| too_ | Tarefas ativas demais |
| quota_ | Cota excedida |
| spend_ | Limite de gastos atingido |
| test_ | Limite do modo de teste atingido |
| storage_ | Armazenamento cheio |
| purchase_ | Compra necessária |
| payment_ | Forma de pagamento necessária |
| format_ | Formato não incluído neste plano |
| webhook_ | A URL do webhook precisa usar HTTPS |
| webhook_ | A URL do webhook precisa ser roteável publicamente |
| webhook_ | Falha na verificação de propriedade do webhook |
| internal_ | Erro interno |
| service_ | Serviço indisponível |
| processing_ | Falha no processamento |
| capacity_ | Capacidade indisponível |
| capability_ | Pausa temporária |
| workspace_ | Espaço de trabalho suspenso |
| uploads_ | Uploads pausados |
| reauthentication_ | Confirme 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