API de Suporte para Parceiros
Abra e acompanhe chamados de suporte da Pluggy de forma programática. Mesma fila, mesmo SLA e mesmo portal de um chamado aberto por uma pessoa.
URL basehttps://docs.pluggy.ai
Nesta página
Visão geral#
Abra e acompanhe chamados de suporte da Pluggy a partir dos seus próprios sistemas. Um chamado criado por esta API é idêntico a um aberto manualmente no portal de suporte: mesmo tipo de solicitação, mesmo formulário, mesmo SLA e mesma fila. Seu time continua vendo tudo no portal, como sempre.
Contrato legível por máquina: /api/partners/docs (OpenAPI 3.0 — aponte seu gerador de código para ele).
Início rápido#
Obtenha sua API key
Seu contato na Pluggy a emite. Envie-a em todas as requisições — os detalhes estão em Autenticação, logo abaixo.
Abrir um chamado
curl -X POST https://docs.pluggy.ai/api/partners/tickets \
-H "Authorization: Bearer $PLUGGY_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{
"summary": "Transação do cartão de crédito não sendo retornada",
"description": "Item conectado e atualizado, fatura fechada em 05/08. As 3 últimas transações não aparecem na API. Afeta ~40 usuários desde 04/08.",
"itemId": "8f2a1b3c-0000-4d4e-9a9a-111122223333",
"product": "Credit Card",
"problemType": "Transação do cartão de credito não sendo retornada",
"accountType": "Pessoa Física",
"externalId": "YOUR-1947",
"priority": "P1 — Alto"
}'Resposta:
{
"ticket": "SUP2-13826",
"portalUrl": "https://pluggy.atlassian.net/servicedesk/customer/portal/1/SUP2-13826",
"status": "Aberto",
"created": true,
"deduplicatedFrom": null,
"attachmentsUploaded": 0,
"derivedFromItem": { "connectorName": "Nubank", "isOpenFinance": true },
"unmappedValues": []
}Consultar status
curl https://docs.pluggy.ai/api/partners/tickets/SUP2-13826 \
-H "Authorization: Bearer $PLUGGY_PARTNER_KEY"Retorna o status atual e os relógios de SLA. Você só consegue ler chamados que pertencem à sua conta.
Autenticação#
Envie a API key que seu contato na Pluggy gerou para você:
Authorization: Bearer pk_live_…
# or
X-Api-Key: pk_live_…A key identifica a sua conta — você nunca envia id de time ou de cliente, e uma key só consegue abrir chamados da própria conta. Trate-a como segredo; se vazar, peça a revogação e emitimos uma nova na hora.
Chamados#
Abrir um chamado#
POST /api/partners/tickets abre um chamado. O início rápido acima traz uma requisição completa e a resposta; todos os campos que ela aceita estão na tabela abaixo.
Campos#
| Campo | Obrigatório | Observações |
|---|---|---|
summary | sim | Título curto. Nosso fluxo o reescreve como [SuaOrg/Instituição] problema (externalId), então seja descritivo mas não dependa dele. |
description | sim | O que está errado, desde quando, quantos usuários são afetados e como reproduzir. É o que a pessoa de engenharia lê primeiro. |
itemId | sim | Um item da Pluggy onde o problema se reproduz. Precisa pertencer à sua conta, e é o conector dele que define a instituição — você não envia nenhuma. Um chamado citando o item de outra conta é recusado antes de ser criado. |
product | sim | Transactions, Credit Card, Investments, Consent… |
problemType | sim | A opção mais próxima do enum. Escolher bem é o que faz o chamado ser roteado corretamente. |
externalId | altamente recomendado | O id do seu próprio chamado. Torna a chamada idempotente — uma nova tentativa devolve o chamado existente em vez de duplicar — e aparece no título, para os dois lados se referenciarem. |
accountType | não | Pessoa Física, Pessoa Jurídica, Corretora ou Outra. Diz em que tipo de conta devemos reproduzir. |
accountId | não | A conta da Pluggy em que o problema acontece, quando o item tem mais de uma. Evita uma ida e volta perguntando qual. |
investmentId | não | O mesmo para problemas de investimento: o investimento específico que está errado ou faltando. |
priority | não | De P0 — Crítico a P3 — Baixo. A sua leitura do impacto — é um sinal para a triagem, não um compromisso de SLA. |
Evidências e anexos#
Prints, arquivos HAR e logs são a diferença entre um chamado resolvido em um dia e outro que fica indo e voltando por uma semana. Envie até 5 arquivos por chamado, de 10 MB cada, como URL HTTPS que buscamos uma única vez (preferido — URLs assinadas com TTL curto funcionam bem) ou em base64 inline, para arquivos pequenos. Chamados já abertos também recebem evidências: POST /api/partners/tickets/{key}/comments aceita os mesmos attachments em chamados SUP2, com ou sem mensagem.
"attachments": [
{ "filename": "evidencia.har", "contentType": "application/json",
"url": "https://files.example.com/signed/evidencia.har" },
{ "filename": "log.txt", "contentType": "text/plain",
"base64": "TG9nIGNvbnRlbnQ=" }
]Acompanhar um chamado#
GET /api/partners/tickets lista os seus chamados com a data limite do SLA e se ele foi estourado, paginado por cursor (nextCursor na resposta, devolva-o como cursor), e POST /api/partners/tickets/{key}/comments adiciona um comentário público — o mesmo que o seu time escreveria no portal, então o suporte o vê na conversa e não em uma nota interna. GET /api/partners/tickets/{key}/comments devolve as mensagens públicas do chamado, e GET /api/partners/tickets/{key}/events o histórico — mudanças de status, prioridade e prazo, mensagens e a resolução, cada uma com a data. Leituras não consomem a cota.
| Método | Caminho | O que faz |
|---|---|---|
| GET | /api/partners/tickets | Seus chamados, do mais recente ao mais antigo, com a data limite do SLA e se ele foi estourado. Paginado por cursor. |
| GET | /api/partners/tickets/{key} | Um chamado: o status atual e os relógios de SLA. |
| GET | /api/partners/tickets/{key}/comments | As mensagens públicas do chamado. |
| GET | /api/partners/tickets/{key}/events | O histórico do chamado: mudanças de status, prioridade e prazo, mensagens e a resolução. |
| POST | /api/partners/tickets/{key}/comments | Responde no chamado com um comentário público. Em chamados SUP2 também aceita attachments. |
Eventos de chamado (webhooks)#
Em vez de consultar a API atrás de mudanças, cadastre um endpoint HTTPS e enviaremos um POST com o evento sempre que um chamado de vocês andar. A API continua disponível para todo o resto — abrir chamados, responder e reconciliar quando quiserem ter certeza — então o webhook é o caminho rápido, não o único.
Referência de webhooks: todos os campos, headers e regras de reenvio
Configuração#
- Cadastrem a URL vocês mesmos com
POST /api/partners/webhookse a API key de vocês (exemplo abaixo). Podem ter até 10 — uma por ambiente ou por sistema que consome — e todo evento vai para todas as que estiverem ativas.GET /api/partners/webhookslista as URLs eDELETE /api/partners/webhooks/{id}remove uma. Se preferirem, o contato de vocês na Pluggy cadastra por vocês. Se a resposta trouxer"routed": false, as URLs ficam salvas, mas a conta de vocês ainda não está configurada para receber eventos de chamado — avisem o contato de vocês na Pluggy. - A resposta traz o segredo de assinatura daquela URL (
whsec_…), uma única vez — ele não pode ser lido depois. Guardem junto das outras credenciais de vocês. Para renovar, enviemPATCH /api/partners/webhooks/{id}com a mesmaurl(ou"rotateSecret": true): o segredo anterior para de valer na hora. - Testem a validação com
POST /api/partners/webhooks/{id}/test: enviamos na hora um eventowebhook.testassinado e respondemos com o código HTTP que o endpoint de vocês devolveu. - Validem a assinatura em toda requisição e respondam qualquer
2xxassim que tiverem aceitado o evento de forma durável. Enfileirem e processem de forma assíncrona: esperamos 8 segundos pela resposta, e qualquer coisa mais lenta conta como entrega falha.
curl -X POST https://docs.pluggy.ai/api/partners/webhooks \
-H "Authorization: Bearer $PLUGGY_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://hooks.example.com/pluggy/tickets", "description": "production" }'
# 201 → { "endpoint": { "id": "3f6c…", "url": "…", "enabled": true, … },
# "secret": "whsec_…" } ← shown onceEventos#
São cinco eventos, todos derivados da mesma comparação com o sistema de chamados que mantém a nossa cópia do chamado de vocês correta — por isso um evento nunca afirma uma mudança que não aconteceu:
| Evento | Quando é enviado |
|---|---|
ticket.status_changed | o chamado mudou de status. previous traz o valor anterior. |
ticket.resolved | o chamado foi resolvido. Enviado no lugar da mudança de status, não junto com ela, então uma mudança real é um evento. |
ticket.message_added | respondemos no chamado. Carrega o texto da resposta. |
ticket.priority_changed | a prioridade ou o SLA mudou. A primeira prioridade que observamos em um chamado não é uma mudança e não envia nada. |
ticket.updated | o título mudou. |
Exemplo de payload:
{
"eventId": "0f2b6c4e-7a1d-4a2e-9c3f-5b8d1e0a7c44",
"sequence": 1042,
"event": "ticket.message_added",
"occurredAt": "2026-09-18T13:42:07.912Z",
"ticket": {
"key": "SUP2-13826",
"number": 418,
"title": "Transação do cartão de crédito não sendo retornada",
"state": "on_customer",
"status": "On customer",
"updatedAt": "2026-09-18T13:41:58.000Z",
"portalUrl": "https://pluggy.atlassian.net/servicedesk/customer/portal/1/SUP2-13826"
},
"message": {
"author": "Pluggy Support",
"text": "Confirmamos com a instituição: as transações entram no próximo ciclo.",
"sentAt": "2026-09-18T13:41:58.000Z"
}
}Verifique a assinatura#
Toda requisição leva X-Pluggy-Signature: t=<unix segundos>,v1=<hmac hex>. O que é assinado é a string "{t}.{corpo bruto}" — o corpo exatamente como chegou, antes de qualquer parse de JSON, porque serializar de novo o objeto já interpretado muda os bytes e a assinatura não confere. O t também é o mecanismo anti-replay: rejeitem o que tiver mais de cinco minutos.
import { createHmac, timingSafeEqual } from "node:crypto";
// `raw` is the body exactly as it arrived — parse it only after this passes.
export function verify(raw: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(
header.split(",").map((kv) => kv.split("=", 2) as [string, string]),
);
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (!Number.isFinite(age) || age > 300) return false; // anti-replay
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${raw}`)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 ?? "");
return a.length === b.length && timingSafeEqual(a, b);
}Entrega#
- Notas internas nunca saem. Nossos chamados têm notas internas ao lado das respostas que vocês veem. O
ticket.message_addedsó carrega mensagem visível para vocês no portal — nunca uma nota interna. - Deduplique pelo
eventId. Ele é estável entre reenvios e também vai no cabeçalhoX-Pluggy-Event-Id. O mesmo id chegando duas vezes significa a mesma mudança, não duas. - A ordem não é garantida. O
sequencediz qual evento foi criado primeiro, para vocês descartarem um que já foi superado — mas não é a ordem de entrega. Tratem o evento como um aviso para ler o chamado, não como o estado em si. - Reenviamos. Seis tentativas com backoff — 30s, 2m, 10m, 1h e 6h — e depois disso a entrega é marcada como falha e podemos reenviar manualmente com o mesmo
eventId. Esses intervalos são quando a tentativa fica devida, não uma promessa de quando ela chega.
Regras do jogo#
- Um chamado por problema, não por usuário final. Se o mesmo bug de conector afeta 500 dos seus usuários, isso é um chamado com o volume descrito — não 500 chamados. É o fator que mais pesa na velocidade da resposta.
- Você não envia mais a instituição. Nós a lemos a partir do itemId, junto com o fato de o conector ser ou não Open Finance. Se o conector não estiver entre as opções do nosso formulário, o chamado é registrado como “Outra” com o nome real na descrição — nada se perde.
- Antes de abrir o chamado, validamos o item Quatro situações impedem a criação do chamado — conexão excluída, sem atualização há mais de duas semanas, só execuções com credenciais inválidas, ou consentimento Open Finance aguardando os demais administradores. Em todas, a correção está na conexão e o suporte só poderia responder o mesmo. A resposta traz um
codee uma mensagem escrita para o seu usuário final (ver a tabela de erros abaixo). - Sempre envie externalId. Retentativas e automações que disparam duas vezes são normais; idempotência é o que impede que virem duplicatas.
- Cota. Cada key tem uma cota mensal de chamados, e só um chamado que a gente realmente cria consome. Leituras não consomem, responder em um chamado não consome, um
dryRunnão consome, uma chamada recusada não consome, e um retry que devolve o chamado que você já abriu não consome — então continue mandandoexternalId. Ao estourar a cota, a API responde429comRetry-After. Faça backoff e tente de novo; não insista em looping. - Use dryRun durante a integração. Com
"dryRun": truevalidamos o payload e devolvemos o que seria registrado, sem criar nada e sem consumir cota. - O que não entra aqui: incidentes em andamento (acompanhe a status page), dúvidas de integração (assistente da documentação) e pedidos de funcionalidade (fale com seu contato na Pluggy).
Erros#
| Status | Significado |
|---|---|
400 | Campos ausentes ou inválidos — a mensagem diz quais. |
401 | Key ausente, desconhecida ou revogada. |
403 | O item não pertence à sua conta. |
404 | Item inexistente ou conexão excluída (ITEM_NOT_FOUND). Também para um item criado nos últimos minutos — tente de novo em instantes. |
409 | O item não pode ser investigado como está: ITEM_TOO_OLD (atualize a conexão), ITEM_ONLY_HAS_INVALID_CREDENTIALS_EXECUTIONS (o usuário final precisa reconectar) ou OF_ITEM_RESOURCES_WITHOUT_MASTER_PERMISSIONS (faltam os demais administradores autorizarem). |
413 | Algum anexo passa de 10 MB. |
429 | Cota mensal atingida. Respeite o Retry-After. |
5xx | Problema do nosso lado. Tente de novo com backoff exponencial; a chamada é idempotente quando você envia externalId. |
