SpotHub · Integração

Referência de API

Integração SpotHub

O SpotHub conecta agências e veículos em torno do pedido de inserção. A agência lança a PI, o veículo recebe a programação, confirma a veiculação e devolve o checking. Esta referência cobre as três APIs públicas, endpoint por endpoint.

Versão do spec 20251114170022 Base https://spothub.com.br Formato JSON · UTF-8 Auth OAuth 2.0 · chave de API

Início rápido

Do zero à primeira PI lida, em duas chamadas.

1 · Obtenha um token

curl -X POST https://spothub.com.br/oauth/gam/v2.0/access_token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "client_id=SEU_CLIENT_ID" \
  -d "client_secret=SEU_CLIENT_SECRET" \
  -d "username=SEU_USUARIO" \
  -d "password=SUA_SENHA" \
  -d "scope=fullcontrol"

2 · Leia as PIs do seu veículo

curl https://spothub.com.br/veiculo/PIs?Veiculoidexterno=95 \
  -H "Authorization: OAuth SEU_TOKEN"
As três APIs públicas
Você éUseBaseEndpoints
Veículo — rádio ou TV API de Veículo /veiculo 9
Agência ou sistema de mídia API de Agência /api/agencia v2
/agencia legada
3
9
Anunciante API de Cliente /client 1

Homologação antes de produção

Todo integrador recebe credencial de homologação primeiro. O contrato é o mesmo nos dois ambientes; muda o host e o prefixo da chave. Rode o fluxo completo — inclusive os desfechos que não são sucesso — antes de pedir a credencial de produção.

Autenticação

OAuth 2.0 para as APIs de veículo, agência legada e cliente; chave de API para a v2 de agência.

OAuth 2.0 — password grant

POST /oauth/gam/v2.0/access_token, corpo application/x-www-form-urlencoded, com grant_type=password, client_id, client_secret, username, password e scope=fullcontrol.

A resposta, e o que cada campo realmente entrega:

{"access_token":"4a154f7f-...!sygPyX2WIc3C0Lhx...",
 "token_type":"Bearer",
 "expires_in":0,
 "refresh_token":"",
 "scope":""}
CampoO que esperar
access_token115 caracteres. Trate como opaco — o formato é interno e pode mudar sem aviso; o que não muda é você devolvê-lo no header.
expires_in0. O token não expira hoje. Não programe renovação por tempo; se um dia passar a expirar, este campo trará o valor real.
refresh_tokenVem vazio. Não há fluxo de renovação disponível — para obter outro token, autentique de novo com usuário e senha.
scopeVem vazio, mesmo que você envie scope=fullcontrol. O valor enviado não é ecoado.

Credencial errada devolve 401 com uma forma própria de erro — não é o bloco Messages do resto da API:

{"Code":11,"Message":"O usuário ou senha está incorreto."}

Os dois formatos de header são aceitos nas chamadas seguintes:

Authorization: OAuth <token>
Authorization: Bearer <token>

Em breve: a autenticação muda de host

A autenticação vai passar de spothub.com.br para api.spothub.com.br. Muda só o host — mesmo caminho, mesmo corpo de requisição, mesma resposta, e o token continua com o mesmo formato. Uma linha de configuração do seu lado.

Ainda não está ativo, e esta página é atualizada quando estiver. Nada a fazer agora; a data vem por contato direto, com o endereço definitivo.

Duas garantias para o dia da troca: o token que você já tem continua valendo depois dela, e o endereço antigo responde durante a transição. Ou seja, você pode trocar a URL num dia e não precisa coordenar nada com o ciclo de vida do seu token.

Reaproveite o token

O endpoint de autenticação é limitado por IP. Guarde o token e reutilize entre chamadas em vez de autenticar a cada requisição — autenticar em laço é a causa mais comum de bloqueio durante a integração.

Chave de API — apenas na v2 de agência

Header X-SpotHub-Key, com prefixo de ambiente (spk_homo_ ou spk_prod_) seguido de 64 caracteres hexadecimais. A chave é entregue uma única vez e não pode ser recuperada — guarde em cofre de segredo no momento em que receber.

A agência vem da chave, nunca do corpo. Você não envia o seu identificador em lugar nenhum. Uma agência pode ter mais de uma chave ativa, o que permite rotação sem janela de indisponibilidade.

API de Veículo

Base /veiculo · OAuth · 9 endpoints · leitura e retorno de veiculação.

É a API da emissora. Além de ler a programação a veicular, ela permite devolver o ciclo: mudar o status da PI, enviar checking parcial durante a campanha e checking final ao término, atualizar a própria grade de programação e responder ao e-mail que originou a PI.

GET/veiculo/PIs pedidos de inserção pendentes

Devolve os pedidos com dados cadastrais, grade de programação, inserções por dia e anexos.

ParâmetroTipoDescrição
NumerostringfuncionaO único que seleciona. Sem ele, a resposta é 204.
VeiculoidexternostringescopoNão seleciona: restringe. Com o código de um veículo seu, a PI vem; com o de outro licenciado, 204.
StatusstringignoradoSozinho dá 204. Com Numero, é ignorado — Status=P devolve PI que está em R.
PIGUIDuuidignoradoSozinho dá 204, mesmo com GUID válido.
Agenciaidint64ignoradoSozinho dá 204.
Dataentradainiciodate-timeignoradoSozinho dá 204.
Dataentradafimdate-timeignoradoSozinho dá 204.
Retornaarquivosbooleannão desligatrue e false devolvem o mesmo corpo.

Resposta · PIsOutput

[{// array cru quando há resultado — ver Convenções
  "agencia": { "codigo_externo": "117", "nome": "...", "cnpj": "00000000000100",
               "cidade": "Brasília", "uf": "DF", "cep": "70316000" },
  "cliente": { "nome": "...", "cnpj": "...", "ie": "" },
  "pi": {
    "piguid": "eeb7d9b3-27dd-4375-8e74-636da73f04ab",
    "veiculoExternalID": "95", "numero": "107821", "versao": "2", "status": "P",
    "periodo_veiculacao": "MARÇO\/2024",
    "Inicio": "2024-03-26", "Termino": "2024-03-29", "vencimento": "0000-00-00",
    "total_insercoes": "21", "total_bruto": "11128.32", "total_liquido": "8902.66",
    "programacao": [{
      "codigo": "1", "formato": "SPOT", "posicao": "MANHÃ",
      "hora_inicio": "06:00", "hora_fim": "10:00", "peca": "PEÇA 1",
      "total_insercoes": "21", "custo_unitario": "828.00", "duracao": 30,
      "codigoPrograma": "P1",
      "insercao": [{ "dia": 26, "quantidade": "6" }, { "dia": 27, "quantidade": "5" }]
    }],
    "anexo": []
  }}]

Sete dos oito parâmetros não filtram

Medido em homologação: chame sempre com Numero. É o único que seleciona; sem ele a resposta é 204 com corpo vazio, e os demais parâmetros não mudam isso. Veiculoidexterno é a exceção parcial — ele restringe o escopo, e é o que impede um licenciado de ler a PI de outro.

O spec declara os oito como filtros. Não são. Programe para o comportamento.

Retornaarquivos não desliga

true e false devolvem exatamente o mesmo corpo. Os anexos vêm sempre, em base64 no campo anexo[].arquivo — não por URL. Respostas grandes são esperadas; dimensione buffer e timeout com folga.

GET/veiculo/DadosVeiculo cadastro do veículo
ParâmetroTipoDescrição
Veiculoidexternostringopcionalid ou código externo do veículo

Devolve VeiculoInfos — cadastro, contatos e grade. Use para conferir se o seu código externo está corretamente vinculado.

POST/veiculo/AlterarStatusPI aceite, recusa e confirmação

É por aqui que o veículo aceita, recusa ou confirma a veiculação de uma PI.

CampoTipoDescrição
PIGUIDuuidobrig.a PI a alterar
Statusstringobrig.R recebido · A aprovado · E recusado · V veiculado
Observacaostringopcionaltexto livre
MotivoRecusastringopcionalpreencha quando Status = E
curl -X POST https://spothub.com.br/veiculo/AlterarStatusPI \
  -H "Authorization: OAuth $TOKEN" -H "Content-Type: application/json" \
  -d '{"PIGUID":"eeb7d9b3-27dd-4375-8e74-636da73f04ab",
       "Status":"A","Observacao":"Grade confirmada"}'
POST/veiculo/CheckingParcial uma inserção por vez

Registra uma veiculação individual, à medida que acontece. Use durante a campanha. É por aqui que se registra veiculação — o CheckingFinal faz o mesmo, não o lote que o nome sugere.

Reenviar é seguro. A mesma posição, peça e data e hora não geram um segundo registro — se a sua chamada falhou por rede e você não sabe se chegou, repita. Mas a chave é por linha de programação: a mesma data e hora em outra posição são duas veiculações, não uma.

CampoTipoDescrição
PIGUIDuuidobrig.a PI
ProgPosicaostringobrig.a posição, como veio em programacao[].posicao
ProgPecastringobrig.a peça veiculada
DataHoradate-timeobrig.quando foi ao ar
Obsstringopcionalobservação
POST/veiculo/CheckingFinal encerra a PI — não é lote

O nome engana: ele não envia a coleção

Apesar de aceitar um array, só o primeiro item é gravado. Do segundo em diante nada entra, e a resposta ainda é 200 — o serviço não avisa que descartou. Medido em 24/09/2026: dois itens válidos e distintos entraram, um foi registrado.

Se você manda a campanha inteira numa chamada, está perdendo tudo menos a primeira linha. Para registrar N veiculações, faça N chamadas — de preferência a /veiculo/CheckingParcial, que faz exatamente a mesma coisa e cujo nome não promete o contrário.

O que ele tem de próprio é marcar a PI como Veiculada — e só quando CheckingCollection chega vazia. Mandando com item, ele grava a veiculação e o status da PI não muda.

Ou seja, o fim de campanha são duas etapas: registre as veiculações uma a uma, e depois feche a PI com a coleção vazia.

CampoTipoDescrição
PIGUIDuuidobrig.a PI
CheckingCollectionarrayobrig.lista de CheckingVeiculoSDT
# ERRADO — a segunda veiculação é descartada em silêncio, e a resposta é 200
curl -X POST https://spothub.com.br/veiculo/CheckingFinal \
  -H "Authorization: OAuth $TOKEN" -H "Content-Type: application/json" \
  -d '{"PIGUID":"eeb7d9b3-27dd-4375-8e74-636da73f04ab",
       "CheckingCollection":[
         {"ProgPosicao":"MANHÃ","ProgPeca":"PEÇA 1",
          "DataHora":"2024-03-26 07:12:00","Obs":""},
         {"ProgPosicao":"MANHÃ","ProgPeca":"PEÇA 1",
          "DataHora":"2024-03-26 09:41:00","Obs":""}]}'

# CERTO — uma chamada por veiculação...
curl -X POST https://spothub.com.br/veiculo/CheckingParcial \
  -H "Authorization: OAuth $TOKEN" -H "Content-Type: application/json" \
  -d '{"PIGUID":"eeb7d9b3-27dd-4375-8e74-636da73f04ab",
       "ProgPosicao":"MANHÃ","ProgPeca":"PEÇA 1",
       "DataHora":"2024-03-26 07:12:00","Obs":""}'

# ...e a coleção VAZIA para encerrar a PI
curl -X POST https://spothub.com.br/veiculo/CheckingFinal \
  -H "Authorization: OAuth $TOKEN" -H "Content-Type: application/json" \
  -d '{"PIGUID":"eeb7d9b3-27dd-4375-8e74-636da73f04ab",
       "CheckingCollection":[]}'
POST/veiculo/Programacao atualiza a sua grade

Mantém a grade do veículo no SpotHub. É a chamada que mais reduz trabalho manual na sua ponta: é contra essa grade que o SpotHub casa a posição escrita na PI com um programa seu.

Não substitui a grade — atualiza programa a programa, pela SIGLA. O que você mandar é criado ou atualizado; o que você não mandar fica como estava. Pode enviar só o que mudou, e pode reenviar a grade inteira sem medo de duplicar.

Excluir só funciona em programa que já existe

Mandar "Excluir": true numa SIGLA que o SpotHub ainda não conhece cria o programa ativo, em vez de não criar nada. O campo é lido apenas na atualização. Para desativar, a sigla precisa já estar na grade.

CampoTipoDescrição
VeiculoIDExternostringobrig.o seu código de veículo
programacaoarrayobrig.lista de ProgramacaoSDT
{"VeiculoIDExterno":"95",
 "programacao":[
   {"SIGLA":"P1","Descricao":"Manhã Total","Inicio":"06:00","Termino":"10:00",
    "Duracao":240,"Excluir":false},
   {"SIGLA":"N18S","Descricao":"Novela I (Sáb)","Inicio":"18:00","Termino":"19:00",
    "Duracao":60,"Excluir":false}]}
GET/veiculo/Triagem o que não virou PI

E-mails retidos para conferência humana — ainda não geraram PI. Útil para saber que algo chegou mas não entrou.

ParâmetroTipoDescrição
Datahoradate-timeopcionalsó o que entrou a partir daí

Devolve lista de ListaTriagem.

POST/veiculo/EnvioPIPDF PI em PDF
CampoTipoDescrição
Base64stringobrig.o PDF codificado
ExtCodestringobrig.código de acompanhamento; use-o para consultar o desfecho

Os dois campos vazios devolvem 422 — os únicos 422 da API de veículo. Base64 que não decodifica devolve 500 com BASE64RENDER, e nada é armazenado: o serviço recusa em vez de aceitar um PDF corrompido.

O 200 significa que o PDF foi recebido e enfileirado, não que virou PI. A leitura do documento acontece depois; acompanhe pelo ExtCode.

POST/veiculo/ResponderEmail responde a origem da PI
CampoTipoDescrição
PIGUIDuuidobrig.a PI
textostringobrig.corpo da resposta
assinaturastringopcionalassinatura ao pé

Responde ao e-mail que originalmente trouxe a PI, na mesma conversa — para a sua equipe não precisar sair do sistema e abrir o cliente de e-mail.

Só funciona em PI que nasceu de e-mail. PI que entrou por outra via não tem a que responder e a chamada devolve 404 — o mesmo 404 de PI inexistente, sem distinguir os casos. É o único endpoint da API que usa 404.

Sucesso e falha de envio respondem igual

Não conseguindo enfileirar a mensagem, a resposta continua sendo 200 com corpo vazio: o serviço monta a mensagem de erro internamente e não a devolve. Não trate o 200 como confirmação de que o e-mail saiu. Se isso importa na sua operação, confirme pela caixa de saída.

API de Agência

Duas gerações convivem. Comece pela v2.

Qual usar

A v2 (/api/agencia) é a recomendada para integração nova: tem idempotência explícita, códigos de erro nomeados e usa o status HTTP com o significado usual. A legada (/agencia) continua no ar, tem mais endpoints — inclusive leitura de checking e status — e segue as convenções descritas em Convenções.

v2 recomendada

Chave X-SpotHub-Key. A ordem das chamadas não é sugestão: a PI referencia a campanha, não a cria.

  1. GET /api/agencia/veiculos — descubra os veículos e os códigos externos. Aceita busca.
  2. POST /api/agencia/campanha — crie a campanha, identificada pelo external_id do seu sistema.
  3. POST /api/agencia/pi — lance a PI, uma por veículo.
POST /api/agencia/pi — desfechos
HTTPResultadoSignifica
201gravadaPI nova
201versao_id maiorjá existia, conteúdo mudou — versão nova criada
200sem_alteracaoconteúdo idêntico. Nada criado — e isso é sucesso

A identidade de uma PI é o trio agência + veículo + número. Não existe PUT: corrigir é reenviar o mesmo POST com o conteúdo novo. Reenviar conteúdo idêntico é seguro e não duplica, o que torna a retentativa trivial.

A chamada é síncrona por decisão — o download do anexo e a marca d'água acontecem dentro dela. Típico de 1 a 5 segundos; configure timeout de 120 segundos, não os 30 que a maioria dos clientes usa por padrão.

Campos que confundem na primeira integração

CampoO que éExemplo
insercoes[].diaDia do mês, 1 a 31 — não é data. Mês e ano vêm do período da campanha.26
insercoes[].quantidadeTexto, porque aceita marcação de peça além de contagem."2" · "A" · "X"
anexos[]Exatamente um entre url e conteudo_base64. Teto de 20 MB.{"url":"…"}

Como as suas referências são resolvidas

EntidadeResolvida porSe não achar
Agênciaa chave de API—
Veículocodigo_externo; na primeira PI, nome + cidade + ufVEICULO_NAO_ENCONTRADO
ClienteCNPJ; depois nome exatocadastra automaticamente
Campanhaexternal_id do seu sistemaCAMPANHAIDEXT

O casamento por nome é exato

Quando o veículo é identificado por nome, cidade e UF, a comparação é caractere a caractere, incluindo acento e pontuação. Rádio Massa e Radio Massa são veículos diferentes. Casando uma vez, o vínculo passa a existir e as PIs seguintes podem usar só o codigo_externo.

Legada em operação

Base /agencia, OAuth. Oferece leitura que a v2 ainda não tem — em especial Checking e StatusPI, com que a agência acompanha o que o veículo devolveu.

MétodoRotaParâmetrosO que faz
GET/agencia/PIsPinumero, Status, Inicio, Terminorecupera PIs
GET/agencia/Dados—dados da agência
POST/agencia/NovaPIcorpocria pedido de inserção
POST/agencia/Novacampanhacorpocria campanha
POST/agencia/AnexaMaterialcorpoenvia material
GET/agencia/CheckingGUIDchecking devolvido pelo veículo
GET/agencia/StatusPICampanhaextid, Piguidstatus das PIs da campanha
GET/agencia/VeiculoProgramacaoVeiculoextid, Codigoprogramagrade do veículo
GET/agencia/VeiculoDadosVeiculoidexternocadastro do veículo

API de Cliente

Base /client · OAuth · para o anunciante acompanhar as próprias campanhas.

GET/client/PIs

Recupera os pedidos de inserção do anunciante. Superfície mínima — se você precisa de mais do que a lista, fale com a equipe antes de desenhar a integração.

Objetos

Os schemas reutilizados entre endpoints.

pi

CampoTipoDescrição
piguiduuididentificador estável da PI — use-o, não o número
numerostringnúmero da PI no sistema da agência
versaoint64incrementa quando o conteúdo muda
statusstringP pendente · R recebido · A aprovado · E recusado · V veiculado
vendadiretabooleancliente direto, sem agência
Inicio · Terminodateperíodo da campanha
total_insercoesint64*soma das inserções
total_bruto · comissao · total_liquidodouble*valores da PI
fatura_* · nf_*stringdados impressos na PI para faturamento e nota
programacao[]arrayas linhas da grade
material[]arraypeca, Titulo, duracao, formato
comentario[]arraytexto livre
anexo[]arraynome, extensao, arquivo em base64

* O spec declara tipo numérico, mas o serviço devolve string. Ver Convenções.

programacao[]

CampoTipoDescrição
posicaostringa posição como escrita na PI — MANHÃ, NOVELA I
codigoProgramastringo código na sua grade. Pode vir vazio — ver aviso
formato · titulo · pecastringo que veicular
hora_inicio · hora_fimstringfaixa horária, HH:mm
duracaoint32segundos da peça
total_insercoesint64*inserções desta linha
custo_unitario · custo_total · descontodouble*valores da linha
insercao[]arraydia (dia do mês) e quantidade (texto)

codigoPrograma pode vir vazio — e isso é intencional

Quando o SpotHub não casa a posição escrita na PI com um programa da sua grade com confiança suficiente, ele deixa o campo vazio em vez de chutar: um palpite errado colocaria a inserção no programa errado, e ninguém perceberia.

Trate vazio como “requer conferência humana”, nunca como erro de transmissão. Manter a grade atualizada por POST /veiculo/Programacao é o que reduz esses casos.

CheckingVeiculoSDT

CampoTipoDescrição
ProgPosicaostringa posição, como veio em programacao[].posicao
ProgPecastringa peça veiculada
DataHoradate-timemomento da veiculação
Obsstringobservação

ProgramacaoSDT

CampoTipoDescrição
SIGLAstringo código do programa — é ele que volta em codigoPrograma
Descricaostringnome do programa
Inicio · Terminostringfaixa horária
Duracaodoubleduração em minutos
Excluirbooleantrue desativa o programa — apenas se a SIGLA já existir. Em sigla nova, cria ativo

VeiculoInfos

codigo, nome, tipo, contato, telefone, email, endereco, cidade, uf, cep, cnpj, ie, status, frequencia, pais, observacao, razao_social, nome_fantasia, logotipo, mais as coleções contacts[] e programacao[].

ListaTriagem

CampoTipoDescrição
Idint64identificador da triagem
Datadate-timequando entrou
Obsstringpor que foi retida
De · Email · Assuntostringo e-mail de origem

Convenções de resposta

As APIs OAuth têm clientes em produção há anos. O comportamento abaixo é preservado e não vai mudar.

O spec declara tipo; o serviço devolve string

total_bruto é declarado number/double e chega como "11128.32". total_insercoes é declarado integer e chega como "21". Programe para o comportamento, não para o spec: converta explicitamente e não confie em coerção automática do seu desserializador.

ConvençãoComo tratar
Erro de negócio vem com HTTP 200 — menos onde não vemNas consultas, sim: não use o status para detectar recusa, verifique a presença do bloco Messages. Nos endpoints de escrita, o status varia por rota — ver a tabela de códigos por endpoint. A regra que funciona em todos: havendo Messages, houve recusa, seja qual for o status.
Resposta vazia é 204/veiculo/PIs sem resultado devolve 204 com corpo vazio. Não tente desserializar.
A forma muda com o conteúdoCampo vazio é omitido. Em 17 PIs reais, cliente veio em 15 e representante em nenhuma. Trate todo campo como opcional.
Envelope condicionalHavendo só um objeto de saída, ele vem cru, sem envelope; havendo mais de um, vem nomeado. Aceite as duas formas.
Não aplique TrimCampos de largura fixa preservam espaço à esquerda: "codigo":" 7159". Compare aparando dos dois lados, mas não reenvie aparado.
Nunca nullAusência é string vazia. Data vazia é "0000-00-00" — inválida em quase toda biblioteca. Trate como “sem valor” antes de converter.
Data e hora com espaçoyyyy-MM-dd HH:mm:ss, espaço e não T. Não é ISO 8601.
Barra escapada/ aparece como \/. É JSON válido; só não compare string bruta.
UTF-8 diretoSem sequências \uXXXX. Leia como UTF-8, nunca Latin-1.
A resposta vem comprimidaO serviço responde content-encoding: br (Brotli). Quase todo cliente descomprime sozinho; se o seu não descomprime, mande Accept-Encoding: identity e o serviço honra.
204 traz headers de conteúdoA resposta vazia vem com content-type e content-encoding mesmo sem corpo. Não deduza que há conteúdo a partir deles.
Inteiro vem cru; monetário vem stringNo mesmo objeto: "duracao":0 é número e "custo_unitario":"914.00" é string. Não presuma um tipo só para a linha toda.
Formatação varia por rotacnpj sem pontuação em /veiculo/PIs, com pontuação em /agencia/PIs. Normalize antes de comparar.

Barra final devolve 404

O caminho não diferencia maiúscula de minúscula — /VEICULO/PIS funciona — mas /veiculo/PIs/, com barra no fim, devolve 404. Se o seu cliente HTTP ou proxy normaliza URL acrescentando barra, desligue isso para estas rotas.

No mesmo espírito: parâmetro de query repetido faz vencer o último, e parâmetro desconhecido é ignorado em silêncio — um nome digitado errado não gera erro, só deixa de filtrar.

Erros

Dois formatos, um por geração de API.

APIs OAuth — bloco Messages

Chega com 200. Cada item traz Id, Type e Description.

{"VeiculoInfos": {"codigo":"","nome":"","tipo":"", /* todos os campos, vazios */
                  "status":false, "logotipo":""},
 "Messages": [{"Id":"VEICULO_NAO_EXISTE","Type":0,
               "Description":"Não foi possível encontrar o codigo externo do veiculo."}]}

Qual status cada endpoint usa

Não é uniforme, e programar supondo uniformidade quebra. Os valores abaixo foram medidos chamada a chamada contra o serviço, não lidos do spec.

EndpointHTTPQuando
GET /veiculo/*200recusa vem no corpo, em Messages; sem resultado é 204
AlterarStatusPI200inclusive nas recusas
CheckingParcial · CheckingFinal400toda recusa de negócio
Programacao422código de veículo desconhecido
EnvioPIPDF422campo obrigatório vazio
EnvioPIPDF500base64 que não decodifica (BASE64RENDER)
ResponderEmail404PI inexistente, ou PI que não veio de e-mail — sem corpo de mensagem
todos com PIGUID500ver abaixo

PIGUID malformado devolve 500, não 400

O valor é desserializado antes de qualquer regra de negócio, e o que não é um UUID derruba essa camada. Quatro entradas ruins, duas respostas diferentes:

Você enviaHTTPCorpo
campo ausente do JSON400CODIGO_NAO_INFORMADO
"00000000-0000-0000-0000-000000000000"400CODIGO_NAO_INFORMADO
""500{"error":{"code":"500",…}}
qualquer texto que não seja UUID500{"error":{"code":"500",…}}

A linha que pega gente de surpresa é a terceira: string vazia não é o mesmo que campo ausente. Se o seu código monta o corpo com um valor padrão "" quando não tem a PI, você recebe 500 e vai procurar um problema no servidor. Omita o campo, ou não faça a chamada.

DataHora se comporta ao contrário: texto ilegível — ou vazio — vira recusa de negócio DATAVAZIA com 400, e não 500.

v2 de agência — código nomeado e status HTTP

CódigoHTTPSignificaRetentar?
CHAVE_INVALIDA401chave ausente, errada ou revogadanão
CHAVE_SEM_AGENCIA403a chave não está vinculada a uma agêncianão
EXTERNAL_ID_EM_USO409o id de campanha pertence a outra agêncianão
CAMPANHAIDEXT422a campanha referenciada não existedepois de criá-la
VEICULO_NAO_ENCONTRADO422nome, cidade ou UF não casaramdepois de corrigir
FILEDOWN · STORAGE · WATER503falha ao baixar ou guardar o anexosim — 1, 5 e 15 min
—500falha inesperadasim — com backoff

Limites conhecidos

AssuntoSituação
Paginaçãonão há E não há como recortar por data, status ou agência — esses filtros não funcionam. O acesso prático é uma PI por vez, por Numero.
Webhook ou pushnão há O fluxo é pull. Programe consulta periódica; o intervalo depende do seu volume.
Anexos na respostaVêm sempre em base64 e não podem ser desligados. Dimensione buffer e timeout com folga.
URLs de arquivoPré-assinadas e de vida curta. Baixe ao receber; não guarde a URL nem a use como identificador.
Limite de requisiçõesO endpoint de autenticação é limitado por IP. Reaproveite o token.

Antes de ir para produção

Rode em homologação os desfechos que não são sucesso: reenvie a mesma PI e confirme que 200 sem_alteracao é tratado como sucesso; referencie uma campanha inexistente e confirme que você não retenta em laço; consulte um veículo sem PI e confirme que o seu cliente aceita 204 sem corpo; e receba uma PI com codigoPrograma vazio e confirme que ela vai para conferência, não para o ar.

Se você registra veiculação, confira mais três, porque são erros que não dão sintoma: mande duas veiculações num só CheckingFinal e confirme no relatório que as duas ficaram registradas — se só uma ficou, quebre em chamadas separadas; mande CheckingCollection: [] e confirme que a PI foi para Veiculada; e mande a mesma veiculação duas vezes e confirme que não duplicou.

Se você envia a grade, mande uma SIGLA que você sabe que o SpotHub não conhece, com "Excluir": true, e confirme se o programa apareceu — ele aparece, ativo.