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.
| Campo | Conteúdo |
|---|---|
status | O código HTTP. |
title | Um título curto. |
detail | A explicação, para uma pessoa ler. |
codigo | O código estável do erro (ver o catálogo abaixo). É este que se testa. |
traceId | O identificador do pedido. Indique-o ao suporte para encontrarmos o que se passou. |
errors | Só nos erros de validação: o nome de cada campo e as suas mensagens. |
Um erro de validação
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
}'
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()
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()
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
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
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
}
]
}'
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()
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()
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
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
| Resposta | O que fazer |
|---|---|
400, 403, 404, 409 | Não repita igual: o pedido (ou a situação) tem de mudar primeiro. Excepto IDEMPOTENCIA_CONFLITO: use uma chave nova. |
401 | A credencial tem de mudar (chave nova ou activa). |
429 | Espere 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ódigo | HTTP | Quando | O que fazer |
|---|---|---|---|
PEDIDO_INVALIDO | 400 | O 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_VALIDACAO | 400 | Campos em falta ou com valores inválidos. errors diz quais. | Corrigir os campos indicados. |
REGRA_DE_NEGOCIO | 400 | O 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_ENCONTRADA | 400 | Um identificador enviado no corpo (cliente, artigo, método de pagamento, documento) não existe nesta empresa. | Conferir os identificadores. |
NAO_ENCONTRADO | 404 | O 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. |
DUPLICADO | 400 / 409 | Já existe um registo igual (número de contribuinte, código de barras). | Usar o registo existente. |
PEDIDO_DEMASIADO_GRANDE | 413 | O corpo excede o limite (2 MB). | Dividir em pedidos mais pequenos. |
Idempotência e conflitos
| Código | HTTP | Quando | O que fazer |
|---|---|---|---|
IDEMPOTENCIA_CONFLITO | 409 | A Idempotency-Key já foi usada com um corpo diferente. | Usar uma chave nova para o pedido novo. |
IDEMPOTENCIA_CHAVE_INVALIDA | 400 | A Idempotency-Key tem caracteres ou tamanho inválidos. | Usar um GUID. |
CONFLITO | 409 | O registo foi alterado ao mesmo tempo por outro pedido. | Repetir o pedido. |
Autenticação e chave
| Código | HTTP | Quando | O que fazer |
|---|---|---|---|
NAO_AUTENTICADO | 401 | Falta a credencial. | Enviar X-Api-Key. |
CHAVE_INVALIDA | 401 | A chave não existe (ou não é desta empresa). | Conferir o segredo. |
CHAVE_REVOGADA | 401 | A chave foi revogada. | Pedir uma chave nova. |
CHAVE_EXPIRADA | 401 | A chave passou a data de validade. | Pedir uma chave nova. |
CHAVE_SEM_TITULAR_ACTIVO | 401 | A pessoa que criou a chave já não está activa. | Um administrador assume a titularidade. |
ORIGEM_NAO_PERMITIDA | 403 | A chave tem uma lista de endereços e este não está nela. | Usar um endereço permitido, ou acrescentá-lo. |
DADOS_SENSIVEIS_NAO_AUTORIZADOS | 403 | A 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_SUSPENSA | 403 | A empresa está suspensa (por exemplo, por falta de pagamento). A resposta traz suspensao com o detalhe. | Regularizar a subscrição. |
QUOTA_DE_DOCUMENTOS_ESGOTADA | 400 | O 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ódigo | HTTP | Quando | O que fazer |
|---|---|---|---|
SEM_PERMISSAO | 403 | O 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_CHAVE | 403 | A operação não faz parte da API de integração. | Ver a referência. |
ESTABELECIMENTO_NAO_PERMITIDO | 403 | A chave (ou o titular) não tem acesso a esse estabelecimento. | Usar o estabelecimento da chave. |
ACESSO_NEGADO | 403 | Acesso recusado a um recurso (por exemplo, um armazém de outro estabelecimento). | Ler detail. |
Documentos, stock e pagamentos
| Código | HTTP | Quando | O que fazer |
|---|---|---|---|
ESTABELECIMENTO_OBRIGATORIO | 400 | A empresa tem vários estabelecimentos e o pedido não indicou qual. | Enviar lojaId. |
ARMAZEM_OBRIGATORIO | 400 | O documento movimenta stock e não indica o armazém. | Enviar armazemId. |
ARMAZEM_DE_OUTRO_ESTABELECIMENTO | 400 | O armazém não pertence ao estabelecimento do documento. | Usar um armazém desse estabelecimento. |
STOCK_INSUFICIENTE | 400 | Não há quantidade suficiente (ou, com um lote, nesse lote). Nada foi emitido. | Reduzir a quantidade ou repor o stock. |
LOTE_EXPIRADO | 400 | O 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_TOTAL | 400 | A soma dos pagamentos não é igual ao total do documento. | Corrigir os valores. |
METODO_PAGAMENTO_INACTIVO | 400 | O método de pagamento está desactivado. | Escolher um método activo. |
CLIENTE_SEM_CONTA_CORRENTE | 400 | Só 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_EXCEDIDO | 400 | O documento ultrapassa o limite de crédito do cliente. | Receber o que está em dívida, ou aumentar o limite. |
DOCUMENTO_JA_CREDITADO | 400 | A linha já foi creditada por uma nota de crédito anterior. | Creditar só o que falta. |
PERIODO_FECHADO | 400 | A 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ódigo | HTTP | Quando | O que fazer |
|---|---|---|---|
MESA_RESERVADA | 400 | Essa 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ódigo | HTTP | Quando | O que fazer |
|---|---|---|---|
LIMITE_DE_PEDIDOS | 429 | Excedeu o limite de pedidos por segundo. Retry-After diz quanto esperar. | Esperar e repetir; reduzir o ritmo. |
ERRO_INTERNO | 500 | Erro 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).