# Códigos de Erro

> Versão em Markdown da documentação pública da API v1 da Bifrost Crypto, para agentes de IA e geradores de código. Mesmo conteúdo das páginas HTML, gerado da mesma fonte, sem navegação nem estilo.
>
> Versão HTML: https://bifrostcrypto.com/pt-BR/docs/error-codes
> Arquivos relacionados: [ai.md](https://bifrostcrypto.com/ai/pt-BR.md) · [Postman](https://bifrostcrypto.com/postman-collection.json) · [OpenAPI](https://bifrostcrypto.com/openapi.json) · [llms.txt](https://bifrostcrypto.com/llms.txt)
> Outros idiomas: [English](https://bifrostcrypto.com/ai/en/error-codes.md) · [Español](https://bifrostcrypto.com/ai/es/error-codes.md) · [Français](https://bifrostcrypto.com/ai/fr/error-codes.md)
> API v1 · contrato alterado pela última vez em 2026-10-10

Todos os códigos de erro que a API devolve, agrupados por tipo, com o que fazer em cada um.

## Formato do erro

Todo erro da /v1 tem o mesmo formato, com o status HTTP das tabelas abaixo. Compare error.code, que é snake_case estável. error.message é texto livre em inglês para os seus logs. error.details só aparece quando há dado útil, como campos inválidos, limites ou escopos.

```json
{
  "error": {
    "code": "insufficient_total_balance",
    "message": "Insufficient balance to cover all payouts",
    "details": {
      "crypto_currency": "USDT",
      "available": "150.00000000",
      "required": "420.50000000"
    }
  }
}
```

Num lote de POST /v1/payout, cada item recusado traz o mesmo objeto em results[].error, ao lado do seu index e reference_id. A resposta é 200 quando todos os itens deram certo, 207 quando parte deu certo e parte falhou, e 400 quando nenhum deu certo:

```json
{
  "message": "payment_processing_completed",
  "summary": {
    "total_requested": 3,
    "successful": 1,
    "failed": 2
  },
  "results": [
    {
      "index": 0,
      "reference_id": "order_1001",
      "internal_reference": "BIFROST_REF_123456",
      "status": "created"
    },
    {
      "index": 1,
      "reference_id": "order_1002",
      "error": {
        "code": "invalid_parameters",
        "message": "Invalid parameters provided",
        "details": {
          "fields": {
            "wallet": "Invalid wallet format"
          }
        }
      }
    },
    {
      "index": 2,
      "reference_id": "order_1003",
      "error": {
        "code": "daily_payout_limit_exceeded",
        "message": "Daily payout limit for this API key exceeded"
      }
    }
  ]
}
```

- As mensagens de validação ficam sempre em error.details.fields, indexadas pelo nome do campo, tanto no topo quanto dentro de um item do lote.
- Um item de payout duplicado conta como falha: traz error.code duplicate_payment_reference mais duplicate, original_status e original_created_at no próprio item. Um lote só de duplicados responde 400.
- Decida por error.code, nunca por error.message: as mensagens podem ser reescritas, os códigos não mudam.
- 4xx significa que o pedido precisa mudar antes de ser reenviado. 5xx significa que a falha foi do nosso lado e o mesmo pedido pode ser repetido; no POST /v1/payout, o reference_id continua protegendo contra saque duplicado.
- Um caminho /v1 desconhecido devolve 404 route_not_found em JSON. Um cliente que pede text/html no Accept recebe uma página HTML.

## Erros de Autenticação e Acesso

| Código HTTP | Erro | Descrição |
| --- | --- | --- |
| `401` | `api_key_missing` | O cabeçalho X-Bifrost-Invoke não foi enviado. |
| `401` | `api_key_invalid` | API Key desconhecida, revogada ou expirada. Também devolvido enquanto a conta está suspensa. |
| `403` | `api_access_denied` | O acesso à API está desativado nesta conta. Conferido a cada requisição. |
| `403` | `email_not_verified` | O e-mail da conta ainda não foi verificado. |
| `403` | `ip_not_authorized` | O IP de origem não está na whitelist da chave. |
| `400` | `invalid_ip` | Não foi possível determinar o IP do cliente. |
| `401` | `signature_headers_missing` | A chave exige assinatura e falta X-Bifrost-Signature, X-Bifrost-Timestamp ou X-Bifrost-Nonce. |
| `401` | `signature_timestamp_invalid` | X-Bifrost-Timestamp não é composto só de dígitos (segundos Unix). |
| `401` | `signature_timestamp_out_of_range` | O timestamp difere do horário do servidor em mais de 300 segundos. |
| `401` | `signature_nonce_length_invalid` | O nonce tem menos de 16 ou mais de 80 caracteres. |
| `401` | `signature_nonce_charset_invalid` | O nonce tem caracteres fora de A-Z, a-z, 0-9, sublinhado e hífen. |
| `401` | `signature_invalid` | A assinatura não confere com a string canônica. |
| `401` | `signature_replayed` | Este nonce já foi usado por esta chave. |
| `401` | `signature_secret_unavailable` | A chave exige assinatura mas não tem segredo utilizável. Fale com o suporte. |
| `403` | `scope_denied` | A chave não tem o escopo que este endpoint exige. details.required_scopes lista o que serve e details.granted_scopes o que a chave tem. |
| `429` | `rate_limit_exceeded` | A cota da chave na janela atual acabou. Retry-After e details.retry_after_seconds dizem quando tentar de novo. |
| `429` | `too_many_failed_authentications` | Este IP está bloqueado temporariamente após falhas repetidas de autenticação. Vem com Retry-After. |
| `401` | `user_not_authenticated` | Não foi possível associar a requisição à sua chave de API. Tente de novo; se repetir, fale com o suporte. |

## Erros do Pedido

| Código HTTP | Erro | Descrição |
| --- | --- | --- |
| `404` | `route_not_found` | Nenhuma rota /v1 corresponde ao caminho e ao método. Respondido antes da autenticação. |
| `400` | `json_parse_exception` | O corpo da requisição não é JSON válido. Qualquer rota /v1. |
| `400` | `json_parse_failed` | POST /v1/payout: o corpo foi decodificado como null ou false. |
| `400` | `invalid_parameters` | Parâmetros inválidos ou ausentes. details.fields traz cada campo com sua mensagem (POST /v1/payout com todos os itens inválidos, POST /v1/payin, endereços fixos, filtros de extrato). |
| `400` | `invalid_payment_list` | POST /v1/payout: o corpo está vazio ou não é um objeto de payout nem uma lista de payouts. |
| `400` | `max_payouts_exceeded` | POST /v1/payout: mais de 50 payouts em uma requisição. |
| `409` | `idempotency_key_in_progress` | Uma requisição com esta Idempotency-Key ainda está em andamento. Aguarde e repita com a mesma chave. |
| `400` | `crypto_amount_required` | POST /v1/payin: process_by é crypto_amount e falta crypto_amount. |
| `400` | `fiat_amount_required` | POST /v1/payin: process_by é fiat_amount e falta fiat_amount. |
| `400` | `fiat_currency_required` | POST /v1/payin: process_by é fiat_amount e falta fiat_currency. |
| `400` | `invalid_page_number` | list-payout / list-payin: page menor que 1. |
| `400` | `invalid_per_page_limit` | list-payout / list-payin: per_page fora de 1 a 100. |
| `400` | `invalid_status_filter` | list-payout / list-payin: valor de status desconhecido. |
| `400` | `invalid_network_filter` | list-payout / list-payin: rede desconhecida. |
| `400` | `invalid_date_from` | list-payout / list-payin: date_from não é uma data. |
| `400` | `invalid_date_to` | list-payout / list-payin: date_to não é uma data. |
| `400` | `invalid_reference_id` | check-payout / check-payin: referência com menos de 10 ou mais de 756 caracteres. Extratos: reference_id com mais de 255. |
| `400` | `invalid_pagination` | Extratos: page ou per_page fora do intervalo. |
| `400` | `invalid_format` | Extratos: date_from ou date_to não é uma data (details.field). |
| `400` | `invalid_date_range` | Extratos: date_from é posterior a date_to. |
| `400` | `date_range_too_wide` | Extratos: o intervalo é maior que o permitido. details.max_days informa o teto. |
| `400` | `invalid_amount` | Extratos: amount_from ou amount_to inválido, ou amount_from maior que amount_to. |
| `400` | `missing_parameters` | get-price: falta currency1 ou currency2. |
| `400` | `invalid_currency` | get-price: currency1 ou currency2 não é um código de moeda válido. |
| `400` | `invalid_webhook_url` | PUT /v1/fixed-wallets/config: webhook_url aponta para um destino não permitido. |

## Erros de Negócio

| Código HTTP | Erro | Descrição |
| --- | --- | --- |
| `400` | `insufficient_total_balance` | POST /v1/payout: os itens de uma moeda somam mais que o saldo disponível. Nada é debitado. details traz crypto_currency, available e required. |
| `404` | `user_not_found` | POST /v1/payout: o cadastro da conta está incompleto. Fale com o suporte. |
| `404` | `record_not_found` | O payout ou payin consultado por check-payout / check-payin não existe nesta conta. Também devolvido quando falta um dado da conta de que a requisição precisa. |
| `400` | `duplicate_reference_id` | POST /v1/payin: já existe um payin com este reference_id na conta. |
| `400` | `invalid_network_or_deposits_disabled` | POST /v1/payin: rede desconhecida, ou depósitos desativados nela. |
| `400` | `unsupported_token_or_deposits_disabled` | POST /v1/payin: token não suportado, ou depósitos desativados para ele. |
| `400` | `token_network_mismatch` | POST /v1/payin: o token não está disponível nesta rede. |
| `400` | `memo_required` | POST /v1/payin: este token exige memo. |
| `400` | `amount_out_of_limits` | POST /v1/payin: o valor em cripto está fora dos limites do token. details traz min_allowed e max_allowed. |
| `400` | `value_below_minimum` | POST /v1/payin com fiat_amount: o valor convertido está abaixo do mínimo. |
| `400` | `value_above_maximum` | POST /v1/payin com fiat_amount: o valor convertido está acima do máximo. |
| `400` | `token_not_supported_or_inactive` | POST /v1/payin com fiat_amount: o token não é suportado ou está inativo para conversão. |
| `400` | `crypto_amount_does_not_correspond_to_fiat_amount_provided` | POST /v1/payin: crypto_amount e fiat_amount foram enviados juntos e não batem com a cotação atual. |
| `400` | `invalid_parameters_for_process_by` | POST /v1/payin: os valores enviados não combinam com o process_by escolhido. |
| `404` | `price_not_found` | get-price: não há cotação para este par de moedas. |
| `403` | `feature_disabled` | Endereços fixos não estão habilitados para a conta (config, atualização de config e criação). |
| `422` | `type_not_supported` | POST /v1/fixed-wallets: este type não está disponível para endereços fixos. |
| `403` | `limit_not_allowed` | POST /v1/fixed-wallets: o limite da conta para este type é zero. |
| `409` | `limit_reached` | POST /v1/fixed-wallets: a conta já tem o número máximo de endereços fixos. |
| `409` | `no_wallet_available` | POST /v1/fixed-wallets: nenhum endereço desse tipo disponível agora. Tente mais tarde. |

## Erros por Item do Lote de Payout (results[].error)

| Código HTTP | Erro | Descrição |
| --- | --- | --- |
| `207/400` | `invalid_parameters` | O item falhou na validação. error.details.fields traz cada campo com sua mensagem. |
| `207/400` | `missing_required_fields` | Falta um campo obrigatório do item. |
| `207/400` | `duplicate_reference_id_in_request` | O mesmo reference_id aparece antes no mesmo lote. |
| `207/400` | `duplicate_payment_reference` | O reference_id já foi usado nesta conta. O item traz duplicate, original_status, original_created_at e o internal_reference original; nenhuma segunda transferência é feita. |
| `207/400` | `invalid_crypto_amount` | process_by é crypto_amount e crypto_amount está ausente ou inválido. |
| `207/400` | `invalid_fiat_amount` | process_by é fiat_amount e fiat_amount está ausente ou inválido. |
| `207/400` | `fiat_currency_required` | process_by é fiat_amount e falta fiat_currency. |
| `207/400` | `fiat_currency_not_accepted` | A fiat_currency não é aceita. |
| `207/400` | `memo_required` | Este token exige memo. |
| `207/400` | `invalid_network_or_withdrawals_disabled` | Rede desconhecida, ou saques desativados nela. |
| `207/400` | `unsupported_token_or_withdrawals_disabled` | Token não suportado, ou saques desativados para ele. |
| `207/400` | `token_network_mismatch` | O token não está disponível nesta rede. |
| `207/400` | `token_not_active` | O token não está ativo para saque ou para conversão. |
| `207/400` | `invalid_format` | O endereço da wallet não é válido para a rede. |
| `207/400` | `limit_exceeded` | O valor está fora do mínimo e do máximo do token (também quando o valor fiat convertido cai fora deles). |
| `207/400` | `amount_does_not_cover_fees` | As taxas consomem todo o valor, e o destinatário não receberia nada. |
| `207/400` | `crypto_amount_does_not_correspond_to_fiat_amount_provided` | crypto_amount e fiat_amount não batem com a cotação atual. |
| `207/400` | `price_unavailable` | Sem cotação para converter o valor, ou a chave tem teto diário e o token não tem cotação em USD para contar o saque. |
| `207/400` | `insufficient_balance` | O saldo disponível não cobre este item. |
| `207/400` | `daily_payout_limit_exceeded` | O item ultrapassaria o teto diário configurado na chave. |
| `207/400` | `daily_payout_limit_unavailable` | A chave tem teto diário mas o consumo não pôde ser registrado. Reenvie o item. |
| `207/400` | `error_fetching_active_tokens · error_fetching_active_currencies` | Não foi possível carregar o catálogo de tokens ou moedas. Reenvie o item. |
| `207/400` | `user_not_found · record_not_found` | Falta um dado da conta de que o saque precisa. |
| `207/400` | `internal_error · database_error · transaction_failed · network_unavailable · duplicate_record` | A gravação do saque falhou do nosso lado e foi desfeita. Nada foi debitado por este item; confira com check-payout ou list-payout antes de reenviar. |

## Erros de Sistema

| Código HTTP | Erro | Descrição |
| --- | --- | --- |
| `500` | `internal_error` | Falha inesperada. No POST /v1/payout, uma falha no meio do lote também devolve summary (com not_processed) e results no topo, para você reenviar só o que faltou. |
| `500` | `database_error` | Extratos: a consulta falhou. |
| `500` | `error_fetching_active_currencies` | POST /v1/payout e POST /v1/payin: não foi possível carregar o catálogo de tokens ou fiats. Nada foi processado. |
| `500` | `token_type_not_found · receipt_creation_failed · database_transaction_failed` | POST /v1/payin: o payin não pôde ser criado do nosso lado. |
| `502` | `external_processor_error` | POST /v1/payin e POST /v1/fixed-wallets: o endereço de depósito não pôde ser criado agora. Nada foi criado; tente mais tarde. |
| `503` | `no_processor_configured · no_wallet_available` | POST /v1/payin: não há processador ou endereço de depósito disponível para este token e rede agora. Tente mais tarde. |
| `503` | `price_not_available_for_conversion · price_not_available_for_validation` | POST /v1/payin: não há cotação disponível para converter ou conferir o valor. Tente mais tarde. |
| `503` | `service_unavailable` | POST /v1/payin e endpoints de summary: uma dependência está temporariamente indisponível. |
| `500` | `config_update_failed` | PUT /v1/fixed-wallets/config: a configuração não pôde ser gravada. |
| `500` | `assign_failed` | POST /v1/fixed-wallets: o endereço não pôde ser atribuído. |

## Melhores Práticas

### Segurança

**Faça**

- Armazene sua API Key em variáveis de ambiente
- Configure IP Whitelist
- Verifique o X-Bifrost-Signature de todo webhook
- Use HTTPS em todas as requisições
- Implemente timeouts nas requisições

**Não Faça**

- Compartilhar API Key publicamente
- Commitar API Keys no Git
- Usar a mesma API Key em múltiplos ambientes

### Performance

- Os dados de extrato e de resumo podem ter até 5 minutos de atraso; não consulte com frequência maior que isso
- Implemente paginação adequada
- Evite requisições excessivas (respeite o rate limit)
- Processe webhooks de forma assíncrona

### Idempotência

- Use reference_id único e significativo: ele é a trava contra saque duplicado, e reapresentá-lo devolve o pagamento original em vez de criar outro
- Envie Idempotency-Key nos lotes: se a conexão cair, repetir com a mesma chave devolve a resposta original em vez de reprocessar
- POST /v1/payin não tem Idempotency-Key: depois de um timeout, reenvie com o mesmo reference_id e, se a resposta for duplicate_reference_id, busque o payin com GET /v1/list-payin?reference_id=…
- Trate 207 e 500 lendo results: os dois trazem o que já foi criado, para você reenviar apenas o que faltou
- Trate as retentativas de webhook deduplicando por data.internal_reference junto com event, para não processar o mesmo aviso duas vezes
