Conceitos

Erros

O formato dos erros, o catálogo de códigos e quando repetir um pedido.

Todo o erro é application/problem+json com a mesma forma. O que o seu programa deve testar é o codigo: o texto de detail está em português, é para pessoas e pode mudar; o codigo não muda.

CampoConteúdo
statusO código HTTP.
titleUm título curto.
detailA explicação, para uma pessoa ler.
codigoO código estável do erro (ver o catálogo abaixo). É este que se testa.
traceIdO identificador do pedido. Indique-o ao suporte para encontrarmos o que se passou.
errorsSó nos erros de validação: o nome de cada campo e as suas mensagens.

Um erro de validação

Pedido
curl -X POST 'https://<endereço-da-API>/api/clientes' \
  -H 'Idempotency-Key: f52d026a-aad8-49b3-b880-14e18844e02c' \
  -H 'X-Api-Key: assw_v1_<chave>' \
  -H 'Content-Type: application/json' \
  -d '{
  "nome": "",
  "endereco": "",
  "numeroContribuinte": "5520879633",
  "isConsumidorFinal": false,
  "email": "compras@mercearia-cazenga.example",
  "telefone": null,
  "telemovel": "926000111",
  "fax": null,
  "website": null,
  "municipioId": null,
  "taxaRetencaoId": null,
  "enviarAlertas": false,
  "enviarDocumentos": true,
  "observacao": null,
  "especial": false,
  "pvpPreferido": "Pvp1",
  "condicaoPagamentoId": null,
  "desconto": null,
  "limiteCredito": null
}'
Pedido
const resposta = await fetch('https://<endereço-da-API>/api/clientes', {
  method: 'POST',
  headers: {
    "Idempotency-Key": "f52d026a-aad8-49b3-b880-14e18844e02c",
    "X-Api-Key": "assw_v1_<chave>",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "nome": "",
    "endereco": "",
    "numeroContribuinte": "5520879633",
    "isConsumidorFinal": false,
    "email": "compras@mercearia-cazenga.example",
    "telefone": null,
    "telemovel": "926000111",
    "fax": null,
    "website": null,
    "municipioId": null,
    "taxaRetencaoId": null,
    "enviarAlertas": false,
    "enviarDocumentos": true,
    "observacao": null,
    "especial": false,
    "pvpPreferido": "Pvp1",
    "condicaoPagamentoId": null,
    "desconto": null,
    "limiteCredito": null
  }),
})
const dados = await resposta.json()
Pedido
import requests

resposta = requests.post(
    'https://<endereço-da-API>/api/clientes',
    headers={
        'Idempotency-Key': 'f52d026a-aad8-49b3-b880-14e18844e02c',
        'X-Api-Key': 'assw_v1_<chave>',
        'Content-Type': 'application/json'
    },
    json={
        'nome': '',
        'endereco': '',
        'numeroContribuinte': '5520879633',
        'isConsumidorFinal': False,
        'email': 'compras@mercearia-cazenga.example',
        'telefone': None,
        'telemovel': '926000111',
        'fax': None,
        'website': None,
        'municipioId': None,
        'taxaRetencaoId': None,
        'enviarAlertas': False,
        'enviarDocumentos': True,
        'observacao': None,
        'especial': False,
        'pvpPreferido': 'Pvp1',
        'condicaoPagamentoId': None,
        'desconto': None,
        'limiteCredito': None
    },
)
dados = resposta.json()
Pedido
using var cliente = new HttpClient { BaseAddress = new Uri("https://<endereço-da-API>") };
using var pedido = new HttpRequestMessage(HttpMethod.Post, "/api/clientes");
pedido.Headers.Add("Idempotency-Key", "f52d026a-aad8-49b3-b880-14e18844e02c");
pedido.Headers.Add("X-Api-Key", "assw_v1_<chave>");
pedido.Content = new StringContent("""
{
  "nome": "",
  "endereco": "",
  "numeroContribuinte": "5520879633",
  "isConsumidorFinal": false,
  "email": "compras@mercearia-cazenga.example",
  "telefone": null,
  "telemovel": "926000111",
  "fax": null,
  "website": null,
  "municipioId": null,
  "taxaRetencaoId": null,
  "enviarAlertas": false,
  "enviarDocumentos": true,
  "observacao": null,
  "especial": false,
  "pvpPreferido": "Pvp1",
  "condicaoPagamentoId": null,
  "desconto": null,
  "limiteCredito": null
}
""", Encoding.UTF8, "application/json");
using var resposta = await cliente.SendAsync(pedido);
var texto = await resposta.Content.ReadAsStringAsync();

400 Bad Request Resposta

Resposta
content-type: application/problem+json
x-mucanda-api-version: 1

{
  "title": "Erro de validação",
  "status": 400,
  "detail": "Há campos em falta ou com valores inválidos. Os detalhes estão em «errors», por campo.",
  "codigo": "ERRO_DE_VALIDACAO",
  "traceId": "00-5b1b93ffc01094d8beee70087fbf2de9-3f74bbd7bed94d3c-01",
  "errors": {
    "nome": [
      "'Nome' deve ser definido."
    ],
    "endereco": [
      "'Endereco' deve ser definido."
    ]
  }
}

Uma regra de negócio

Pedido
curl -X POST 'https://<endereço-da-API>/api/documentos' \
  -H 'Idempotency-Key: 43c76086-b23f-4c8f-a105-33e042ea66fb' \
  -H 'X-Api-Key: assw_v1_<chave>' \
  -H 'Content-Type: application/json' \
  -d '{
  "tipoDocumentoId": "6d3f9c9b-6d0a-4e4d-9a9a-2c9f8b1e4d52",
  "clienteId": "01a12f69-98f8-7688-85b9-8d829c101c8b",
  "armazemId": "01a12201-f4d4-78a0-8dde-1efc41b94776",
  "lojaId": "01a12201-f4d4-7c07-86b7-e5b24035993e",
  "observacao": "Pedido #4821 da loja online",
  "itens": [
    {
      "produtoId": "01a12201-fa97-7f9b-8eec-2f731b91a53b",
      "quantidade": 100000,
      "precoUnitario": 6500,
      "descontoPercentual": 0
    }
  ],
  "pagamentos": [
    {
      "metodoPagamentoId": "9dde532b-0092-4a68-b2dc-18649f0c97c4",
      "valor": 1
    }
  ]
}'
Pedido
const resposta = await fetch('https://<endereço-da-API>/api/documentos', {
  method: 'POST',
  headers: {
    "Idempotency-Key": "43c76086-b23f-4c8f-a105-33e042ea66fb",
    "X-Api-Key": "assw_v1_<chave>",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "tipoDocumentoId": "6d3f9c9b-6d0a-4e4d-9a9a-2c9f8b1e4d52",
    "clienteId": "01a12f69-98f8-7688-85b9-8d829c101c8b",
    "armazemId": "01a12201-f4d4-78a0-8dde-1efc41b94776",
    "lojaId": "01a12201-f4d4-7c07-86b7-e5b24035993e",
    "observacao": "Pedido #4821 da loja online",
    "itens": [
      {
        "produtoId": "01a12201-fa97-7f9b-8eec-2f731b91a53b",
        "quantidade": 100000,
        "precoUnitario": 6500,
        "descontoPercentual": 0
      }
    ],
    "pagamentos": [
      {
        "metodoPagamentoId": "9dde532b-0092-4a68-b2dc-18649f0c97c4",
        "valor": 1
      }
    ]
  }),
})
const dados = await resposta.json()
Pedido
import requests

resposta = requests.post(
    'https://<endereço-da-API>/api/documentos',
    headers={
        'Idempotency-Key': '43c76086-b23f-4c8f-a105-33e042ea66fb',
        'X-Api-Key': 'assw_v1_<chave>',
        'Content-Type': 'application/json'
    },
    json={
        'tipoDocumentoId': '6d3f9c9b-6d0a-4e4d-9a9a-2c9f8b1e4d52',
        'clienteId': '01a12f69-98f8-7688-85b9-8d829c101c8b',
        'armazemId': '01a12201-f4d4-78a0-8dde-1efc41b94776',
        'lojaId': '01a12201-f4d4-7c07-86b7-e5b24035993e',
        'observacao': 'Pedido #4821 da loja online',
        'itens': [
            {
                'produtoId': '01a12201-fa97-7f9b-8eec-2f731b91a53b',
                'quantidade': 100000,
                'precoUnitario': 6500,
                'descontoPercentual': 0
            }
        ],
        'pagamentos': [
            {
                'metodoPagamentoId': '9dde532b-0092-4a68-b2dc-18649f0c97c4',
                'valor': 1
            }
        ]
    },
)
dados = resposta.json()
Pedido
using var cliente = new HttpClient { BaseAddress = new Uri("https://<endereço-da-API>") };
using var pedido = new HttpRequestMessage(HttpMethod.Post, "/api/documentos");
pedido.Headers.Add("Idempotency-Key", "43c76086-b23f-4c8f-a105-33e042ea66fb");
pedido.Headers.Add("X-Api-Key", "assw_v1_<chave>");
pedido.Content = new StringContent("""
{
  "tipoDocumentoId": "6d3f9c9b-6d0a-4e4d-9a9a-2c9f8b1e4d52",
  "clienteId": "01a12f69-98f8-7688-85b9-8d829c101c8b",
  "armazemId": "01a12201-f4d4-78a0-8dde-1efc41b94776",
  "lojaId": "01a12201-f4d4-7c07-86b7-e5b24035993e",
  "observacao": "Pedido #4821 da loja online",
  "itens": [
    {
      "produtoId": "01a12201-fa97-7f9b-8eec-2f731b91a53b",
      "quantidade": 100000,
      "precoUnitario": 6500,
      "descontoPercentual": 0
    }
  ],
  "pagamentos": [
    {
      "metodoPagamentoId": "9dde532b-0092-4a68-b2dc-18649f0c97c4",
      "valor": 1
    }
  ]
}
""", Encoding.UTF8, "application/json");
using var resposta = await cliente.SendAsync(pedido);
var texto = await resposta.Content.ReadAsStringAsync();

400 Bad Request Resposta

Resposta
content-type: application/problem+json
x-mucanda-api-version: 1

{
  "title": "Regra de negócio violada",
  "status": 400,
  "detail": "Quantidade em estoque insuficiente: pedido 100000, disponível 536.0000.",
  "codigo": "STOCK_INSUFICIENTE",
  "traceId": "00-f99b8131b53c363f3f67803ec00c2c18-dc5b5e48be71fa88-01"
}

#Quando repetir

RespostaO que fazer
400, 403, 404, 409Não repita igual: o pedido (ou a situação) tem de mudar primeiro. Excepto IDEMPOTENCIA_CONFLITO: use uma chave nova.
401A credencial tem de mudar (chave nova ou activa).
429Espere os segundos de Retry-After e repita o mesmo pedido.
5xx, ou sem resposta (tempo esgotado, ligação perdida)Repita o mesmo pedido com a mesma Idempotency-Key, com espera crescente (1 s, 2 s, 4 s… até um limite). É seguro: se o primeiro chegou, recebe o mesmo resultado.

#Catálogo de códigos

Pedido

CódigoHTTPQuandoO que fazer
PEDIDO_INVALIDO400O pedido está mal formado: JSON inválido, valor de enumeração que não existe, parâmetro fora do intervalo (por exemplo tamanho).Corrigir o pedido.
ERRO_DE_VALIDACAO400Campos em falta ou com valores inválidos. errors diz quais.Corrigir os campos indicados.
REGRA_DE_NEGOCIO400O pedido é válido mas viola uma regra do negócio (a mensagem explica). É o código geral quando não há um mais específico.Ler detail e corrigir a situação.
REFERENCIA_NAO_ENCONTRADA400Um identificador enviado no corpo (cliente, artigo, método de pagamento, documento) não existe nesta empresa.Conferir os identificadores.
NAO_ENCONTRADO404O recurso pedido no endereço não existe, ou não pertence a esta empresa (as duas coisas respondem igual, por segurança).Conferir o identificador.
DUPLICADO400 / 409Já existe um registo igual (número de contribuinte, código de barras).Usar o registo existente.
PEDIDO_DEMASIADO_GRANDE413O corpo excede o limite (2 MB).Dividir em pedidos mais pequenos.

Idempotência e conflitos

CódigoHTTPQuandoO que fazer
IDEMPOTENCIA_CONFLITO409A Idempotency-Key já foi usada com um corpo diferente.Usar uma chave nova para o pedido novo.
IDEMPOTENCIA_CHAVE_INVALIDA400A Idempotency-Key tem caracteres ou tamanho inválidos.Usar um GUID.
CONFLITO409O registo foi alterado ao mesmo tempo por outro pedido.Repetir o pedido.

Autenticação e chave

CódigoHTTPQuandoO que fazer
NAO_AUTENTICADO401Falta a credencial.Enviar X-Api-Key.
CHAVE_INVALIDA401A chave não existe (ou não é desta empresa).Conferir o segredo.
CHAVE_REVOGADA401A chave foi revogada.Pedir uma chave nova.
CHAVE_EXPIRADA401A chave passou a data de validade.Pedir uma chave nova.
CHAVE_SEM_TITULAR_ACTIVO401A pessoa que criou a chave já não está activa.Um administrador assume a titularidade.
ORIGEM_NAO_PERMITIDA403A chave tem uma lista de endereços e este não está nela.Usar um endereço permitido, ou acrescentá-lo.
DADOS_SENSIVEIS_NAO_AUTORIZADOS403A operação é de dados sensíveis (recursos humanos) e a chave não tem acesso reforçado.O dono da empresa cria uma chave com acesso a dados sensíveis, com consentimento. Ver Dados sensíveis.
CONTA_SUSPENSA403A empresa está suspensa (por exemplo, por falta de pagamento). A resposta traz suspensao com o detalhe.Regularizar a subscrição.
QUOTA_DE_DOCUMENTOS_ESGOTADA400O plano da empresa tem a quota mensal de documentos a bloquear e o mês já a esgotou: não se emitem mais facturas nem facturas/recibo. Notas de crédito, cotações, consultas, exportações e SAF-T continuam a funcionar.Esperar pelo mês seguinte ou pedir à plataforma para mudar de plano.

Autorização

CódigoHTTPQuandoO que fazer
SEM_PERMISSAO403O perfil da chave não tem a permissão que a operação exige (o detail diz qual).Acrescentar a permissão ao perfil.
RECURSO_FORA_DO_ALCANCE_DA_CHAVE403A operação não faz parte da API de integração.Ver a referência.
ESTABELECIMENTO_NAO_PERMITIDO403A chave (ou o titular) não tem acesso a esse estabelecimento.Usar o estabelecimento da chave.
ACESSO_NEGADO403Acesso recusado a um recurso (por exemplo, um armazém de outro estabelecimento).Ler detail.

Documentos, stock e pagamentos

CódigoHTTPQuandoO que fazer
ESTABELECIMENTO_OBRIGATORIO400A empresa tem vários estabelecimentos e o pedido não indicou qual.Enviar lojaId.
ARMAZEM_OBRIGATORIO400O documento movimenta stock e não indica o armazém.Enviar armazemId.
ARMAZEM_DE_OUTRO_ESTABELECIMENTO400O armazém não pertence ao estabelecimento do documento.Usar um armazém desse estabelecimento.
STOCK_INSUFICIENTE400Não há quantidade suficiente (ou, com um lote, nesse lote). Nada foi emitido.Reduzir a quantidade ou repor o stock.
LOTE_EXPIRADO400O lote de onde sairia o stock já expirou e não se vende.Dar baixa do lote (saída manual) ou escolher outro.
PAGAMENTO_NAO_COBRE_TOTAL400A soma dos pagamentos não é igual ao total do documento.Corrigir os valores.
METODO_PAGAMENTO_INACTIVO400O método de pagamento está desactivado.Escolher um método activo.
CLIENTE_SEM_CONTA_CORRENTE400Só um cliente com conta corrente pode receber uma factura a crédito ou pagar por «Conta Corrente».Usar um cliente com conta corrente.
LIMITE_CREDITO_EXCEDIDO400O documento ultrapassa o limite de crédito do cliente.Receber o que está em dívida, ou aumentar o limite.
DOCUMENTO_JA_CREDITADO400A linha já foi creditada por uma nota de crédito anterior.Creditar só o que falta.
PERIODO_FECHADO400A data (da despesa, do pagamento, do documento do fornecedor…) cai num mês que a empresa já fechou na contabilidade: o que foi entregue à AGT para esse mês não muda. Hoje e os meses abertos aceitam.Usar uma data do período aberto (um erro corrige-se agora, com um estorno ou uma nota de crédito), ou pedir a quem responde pela contabilidade que reabra o mês.

Restauração

CódigoHTTPQuandoO que fazer
MESA_RESERVADA400Essa mesa já tem uma reserva nessa hora. A mesma mesa não se reserva a duas pessoas ao mesmo tempo.Escolher outra mesa ou outra hora.

Limites e servidor

CódigoHTTPQuandoO que fazer
LIMITE_DE_PEDIDOS429Excedeu o limite de pedidos por segundo. Retry-After diz quanto esperar.Esperar e repetir; reduzir o ritmo.
ERRO_INTERNO500Erro nosso.Repetir com a mesma Idempotency-Key; se persistir, contactar o suporte com o traceId.

O catálogo só cresce: pode aparecer um código novo. Trate um código desconhecido como o erro geral do seu status (um 400 desconhecido é uma regra de negócio; um 5xx, uma falha a repetir).