Entrar Criar conta
Guias

Servidor MCP

Conecte Claude, ChatGPT, Gemini, Cursor, VS Code e outros assistentes de IA à sua conta da Postal: 34 ferramentas, OAuth ou chave de API, permissões por escopo e as mesmas regras da API.

#O que é

O servidor MCP (Model Context Protocol) da Postal deixa assistentes de IA, como Claude, ChatGPT, Gemini, Cursor, VS Code e o Claude Code, usarem a sua conta: enviar e-mails, conferir uma entrega, ajustar contatos, escrever e enviar campanhas, ler a caixa de entrada. O assistente chama ferramentas (34 hoje), e cada uma roda o mesmo endpoint da API REST.

Endereço: https://api.postal.so/mcp. É o transporte Streamable HTTP do MCP, sem sessão: cada chamada é um POST com uma mensagem JSON-RPC e traz a própria credencial. Cada chamada age em um time: o que você escolheu ao conectar o app ou o time da chave de API.

  • As mesmas regras da API. Validação, limites do plano, cotas de envio, pausa de envio, lista de supressão e limite de requisições do time valem como em qualquer chamada. Os erros chegam como nome: mensagem (por exemplo validation_error: … ou sending_paused: …), os mesmos nomes de erro da API.
  • Dois jeitos de entrar. OAuth, em que a pessoa entra na Postal, escolhe o time e pode revogar depois; ou uma chave de API no cabeçalho Authorization.
  • Permissões por escopo. Um app conectado pode só ler, só enviar ou fazer tudo, e o assistente só enxerga as ferramentas que pode usar.
  • Ações irreversíveis pedem confirmação. Enviar uma campanha exige confirm: true, e as instruções do servidor mandam o assistente confirmar destinatários e conteúdo com você antes de enviar e-mails.

Depende do plano

O servidor MCP é um recurso do plano (mcp_access). Sem ele, o endpoint responde 403 plan_limit_reached. Veja Limites.

#Conectar um assistente

No painel, abra Configurações › Conexões de IA: a tela mostra a URL do servidor e o passo a passo de cada assistente. Os apps de chat usam OAuth; as ferramentas de desenvolvimento aceitam OAuth ou uma chave de API.

AssistenteEntradaComo conectar
ClaudeOAuthNas configurações de conectores (web ou desktop), adicione um conector personalizado, cole a URL e conecte: você entra na Postal, escolhe o time e autoriza.
ChatGPTOAuthNas configurações de apps e conectores (alguns planos pedem o modo desenvolvedor), crie um conector com a URL e a autenticação OAuth.
GeminiOAuth ou chaveNo Gemini CLI, adicione o servidor em ~/.gemini/settings.json (ou use a URL nos apps do Gemini que aceitam servidores MCP). Sem o cabeçalho, o Gemini abre a entrada na Postal no primeiro uso.
CursorOAuth ou chaveNas configurações de MCP (ou em ~/.cursor/mcp.json), adicione um servidor. Só com a URL, o Cursor abre a entrada na Postal para você escolher o time.
VS CodeOAuth ou chaveRode MCP: Add Server, escolha HTTP e cole a URL; na primeira chamada o VS Code pede para entrar. Com chave, use o .vscode/mcp.json abaixo.
Claude CodeOAuth ou chaveAdicione o servidor com o comando abaixo. Sem o cabeçalho, rode /mcp dentro do Claude Code para entrar na Postal.

Para entrar com uma chave de API, use os exemplos abaixo e troque ps_... pela sua chave. Os nomes das telas de cada assistente mudam com frequência; na dúvida, siga a documentação dele.

Configuração com chave de API

Claude Code

claude mcp add --transport http postal https://api.postal.so/mcp \
  --header "Authorization: Bearer ps_..."

Cursor

// ~/.cursor/mcp.json
{
  "mcpServers": {
    "postal": {
      "url": "https://api.postal.so/mcp",
      "headers": { "Authorization": "Bearer ps_..." }
    }
  }
}

VS Code

// .vscode/mcp.json
{
  "servers": {
    "postal": {
      "type": "http",
      "url": "https://api.postal.so/mcp",
      "headers": { "Authorization": "Bearer ${input:postal-api-key}" }
    }
  },
  "inputs": [
    { "id": "postal-api-key", "type": "promptString", "description": "Postal API key", "password": true }
  ]
}

Gemini CLI

// ~/.gemini/settings.json
{
  "mcpServers": {
    "postal": {
      "httpUrl": "https://api.postal.so/mcp",
      "headers": { "Authorization": "Bearer ps_..." }
    }
  }
}

Outros clientes MCP que leem um arquivo com mcpServers e aceitam cabeçalhos usam o mesmo bloco do Cursor.

#Autenticação

O servidor aceita duas credenciais, sempre como Authorization: Bearer. Nenhuma outra é aceita, e a Postal não repassa o token a terceiros.

OAuthChave de API
Para quemOs conectores do Claude, ChatGPT e Gemini e qualquer app que consiga abrir uma janela de entrada.Ferramentas de desenvolvimento que aceitam cabeçalhos (Claude Code, Cursor, VS Code) e scripts.
Como entraA pessoa entra na Postal, escolhe um time e autoriza. O app recebe um token de acesso (1 hora) e um de renovação (30 dias).Uma chave que começa com ps_, criada em Chaves de API.
PermissõesOs escopos que o app pediu e a pessoa aceitou.Acesso total (full_access) vira mcp:full; só envio (sending_access) vira mcp:send.
RevogarEm Conexões de IA, a qualquer momento.Excluindo a chave, em Chaves de API.

Uma chave de API restrita a um domínio só envia desse domínio, como na API. Um token OAuth deixa de valer na hora quando a conexão é revogada, quando a pessoa sai do time ou quando a conta é suspensa.

Prefira o OAuth quando o app oferecer: você escolhe o time, vê o que o app fez e revoga sem trocar nenhuma chave. Se usar chave, prefira uma só de envio quando o assistente só precisa enviar e-mails e eventos.

Sem credencial, o /mcp responde 401 com o cabeçalho WWW-Authenticate apontando para os metadados do recurso (/.well-known/oauth-protected-resource/mcp): é por ele que um cliente MCP descobre como iniciar o OAuth.

#Escopos e permissões

Cada ferramenta pede um escopo. mcp:full vale por todos; mcp:send não inclui a leitura (um app que só envia não vê e-mails, contatos nem o uso). O assistente só enxerga, em tools/list, as ferramentas que o token ou a chave pode chamar.

EscopoNome na tela de autorizaçãoO que permiteFerramentas
mcp:readLer seus dadosVer e-mails, domínios, contatos, segmentos, tópicos, templates, campanhas, automações, formulários, inboxes, métricas e uso.22 ferramentas de leitura
mcp:sendEnviar e-mails e eventosEnviar e agendar e-mails dos seus domínios verificados e registrar eventos para as suas automações.send_email, track_event
mcp:fullGerenciar tudoCriar, alterar e excluir contatos, templates e campanhas, enviar campanhas, cancelar e-mails agendados e trabalhar nas conversas das inboxes (organizar, responder, salvar rascunhos). Inclui os outros dois escopos.As 34 ferramentas

Se o app não pedir escopos, a tela oferece os três. A pessoa autoriza ou recusa o pedido como um todo.

Quando falta permissão

  • Conexão OAuth: chamar uma ferramenta além dos escopos dá 403 com WWW-Authenticate: Bearer error="insufficient_scope", scope="…" e o corpo JSON-RPC insufficient_scope: …. O cliente pode pedir ao usuário para conectar de novo com o escopo que falta.
  • Chave de API: uma chave só de envio que chama uma ferramenta de leitura ou de gestão recebe um erro de ferramenta restricted_api_key. Atenção: no MCP, get_email e get_usage pedem mcp:read, mesmo que a API REST deixe uma chave só de envio ler esses dois endpoints.

#Ferramentas

Os argumentos de cada ferramenta seguem os campos do endpoint correspondente, e o cliente os lê do inputSchema de tools/list. As listas aceitam limit (1 a 100, padrão 20) e os cursores after e before: passe o último id em after enquanto has_more for true. As ferramentas também declaram se só leem, se são destrutivas e se são idempotentes (annotations), para o assistente decidir quando pedir confirmação.

E-mails

FerramentaEscopoO que fazEndpoint da API
send_emailmcp:sendEnvia ou agenda um e-mail transacional (até 50 destinatários; HTML, texto ou template publicado).POST /emails
list_emailsmcp:readLista os e-mails, com filtros de texto, status, domínio e datas.GET /emails
get_emailmcp:readUm e-mail com o conteúdo, o status e a linha do tempo de eventos.GET /emails/{id}
cancel_scheduled_emailmcp:fullCancela um e-mail ainda agendado.POST /emails/{id}/cancel

Domínios

FerramentaEscopoO que fazEndpoint da API
list_domainsmcp:readDomínios de envio, com o status da verificação e a região.GET /domains
get_domainmcp:readUm domínio com os registros DNS e o que está errado em cada um.GET /domains/{id}

Contatos e audiência

FerramentaEscopoO que fazEndpoint da API
list_contactsmcp:readContatos, com filtros por texto, segmento, tópico e descadastro.GET /contacts
get_contactmcp:readUm contato por ID ou e-mail.GET /contacts/{id}
create_contactmcp:fullCria um contato, com segmentos e tópicos. Conta no limite de contatos do plano.POST /contacts
update_contactmcp:fullAltera só os campos enviados de um contato.PATCH /contacts/{id}
delete_contactmcp:fullExclui um contato e as suas inscrições, sem volta.DELETE /contacts/{id}
list_segmentsmcp:readSegmentos estáticos e dinâmicos, com o total de contatos.GET /segments
list_topicsmcp:readTópicos de inscrição.GET /topics
list_contact_propertiesmcp:readPropriedades personalizadas dos contatos (chave, tipo e valor padrão).GET /contact-properties

Templates

FerramentaEscopoO que fazEndpoint da API
list_templatesmcp:readTemplates, publicados ou rascunhos.GET /templates
get_templatemcp:readUm template por ID ou alias.GET /templates/{id}
create_templatemcp:fullCria um template; com publish: true já o publica.POST /templates

Campanhas

FerramentaEscopoO que fazEndpoint da API
list_broadcastsmcp:readCampanhas, com status e estatísticas.GET /broadcasts
get_broadcastmcp:readUma campanha com a audiência, o conteúdo e as estatísticas de entrega.GET /broadcasts/{id}
create_broadcastmcp:fullCria uma campanha como rascunho. Nada é enviado.POST /broadcasts
send_broadcastmcp:fullEnvia ou agenda uma campanha. Exige confirm: true.POST /broadcasts/{id}/send

Automações e eventos

FerramentaEscopoO que fazEndpoint da API
list_automationsmcp:readAutomações, com status e total de execuções.GET /automations
get_automationmcp:readUma automação com o gatilho e os passos.GET /automations/{id}
track_eventmcp:sendRegistra um evento de um contato, que pode iniciar automações (e enviar e-mails).POST /events

Métricas e uso

FerramentaEscopoO que fazEndpoint da API
get_metricsmcp:readEnvios, entregas, aberturas, cliques, bounces e reclamações de um período.GET /emails/metrics
get_usagemcp:readPlano, uso e limites, limite de requisições e se o envio está pausado.GET /usage

Formulários

FerramentaEscopoO que fazEndpoint da API
list_formsmcp:readFormulários de inscrição, com o total de envios.GET /forms
list_form_submissionsmcp:readEnvios de um formulário: respostas, contato e double opt-in.GET /forms/{id}/submissions

Inboxes

FerramentaEscopoO que fazEndpoint da API
list_inboxesmcp:readInboxes do time e as configurações do agente de IA.GET /inboxes
list_inbox_threadsmcp:readConversas de uma inbox, com filtros de pasta, etiqueta, não lidas, responsável e texto.GET /inboxes/{id}/threads
get_inbox_threadmcp:readUma conversa com todas as mensagens e rascunhos (inclusive o otp_token das mensagens recebidas).GET /inboxes/{id}/threads/{thread_id}
update_inbox_threadmcp:fullMuda a pasta, marca como lida, atribui e etiqueta uma conversa.PATCH /inboxes/{id}/threads/{thread_id}
reply_to_inbox_threadmcp:fullResponde a uma conversa pelo endereço da inbox. Envia um e-mail de verdade.POST /inboxes/{id}/threads/{thread_id}/reply
create_inbox_draftmcp:fullSalva um rascunho para uma pessoa revisar e enviar no painel. Não envia.POST /inboxes/{id}/drafts

O que o assistente não deve fazer sozinho

send_email, send_broadcast e reply_to_inbox_thread chegam a pessoas de verdade e não podem ser desfeitos. As instruções do servidor mandam o assistente confirmar destinatários, remetente, assunto e conteúdo com você antes. send_broadcast recusa a chamada com confirmation_required se confirm não for true. Para uma resposta que uma pessoa deve revisar, use create_inbox_draft, que só salva o rascunho. Para testar sem enviar de verdade, mande para os endereços de teste ([email protected]…): nada é entregue e a cota não é usada, mas os eventos e webhooks são reais.

#Recursos e prompts

Além das ferramentas, o servidor oferece contexto pronto e receitas:

TipoNomeO que é
Recursopostal://domainsOs domínios de envio do time e a verificação de cada um, em JSON (os 100 primeiros). Pede mcp:read.
Recursopostal://docs/quickstartUm guia curto, em Markdown, de como domínios, envio, templates, contatos, campanhas, automações, inboxes e erros se encaixam.
Promptwrite_and_send_campaignGuia o assistente do objetivo até uma campanha enviada ou agendada, com a sua aprovação a cada passo. Argumentos: goal (obrigatório), segment e send_at.
Promptdiagnose_deliveryInvestiga por que um e-mail não chegou ou foi para o spam: eventos, registros DNS, métricas e limites, sem enviar nada. Argumentos: email_id, recipient e domain.

Os prompts aparecem com os títulos “Escrever e enviar campanha” e “Diagnosticar entrega”.

#Exemplos

Um assistente faz essas chamadas por você, mas elas são só JSON-RPC sobre HTTP, o que ajuda a testar a chave e entender as respostas. Os cabeçalhos Accept e MCP-Protocol-Version acompanham toda chamada; a resposta é JSON. Não é preciso abrir sessão (initialize) antes: cada POST se basta.

Listar as ferramentas

POST /mcp (tools/list)

cURL

curl -X POST https://api.postal.so/mcp \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list"
  }'

Node.js

const response = await fetch('https://api.postal.so/mcp', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
    'Content-Type': 'application/json',
    Accept: 'application/json, text/event-stream',
    'MCP-Protocol-Version': '2025-11-25',
  },
  body: JSON.stringify({
    jsonrpc: '2.0',
    id: 1,
    method: 'tools/list',
  }),
});

const data = await response.json();

Python

import os
import requests

response = requests.post(
    "https://api.postal.so/mcp",
    headers={
        "Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}",
        "Accept": "application/json, text/event-stream",
        "MCP-Protocol-Version": "2025-11-25",
    },
    json={
        "jsonrpc": "2.0",
        "id": 1,
        "method": "tools/list",
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->post('mcp', [
    'headers' => [
        'Authorization' => 'Bearer '.getenv('POSTAL_API_KEY'),
        'Accept' => 'application/json, text/event-stream',
        'MCP-Protocol-Version' => '2025-11-25',
    ],
    'json' => [
        'jsonrpc' => '2.0',
        'id' => 1,
        'method' => 'tools/list',
    ],
]);

$data = json_decode($response->getBody(), true);
200Resposta (resumida)
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "send_email",
        "title": "Send email",
        "description": "Sends (or schedules) a transactional email …",
        "inputSchema": { "type": "object", "properties": { … }, "required": ["from", "to"] },
        "annotations": { "title": "Send email", "readOnlyHint": false, "destructiveHint": false, "idempotentHint": false, "openWorldHint": true }
      },
      …
    ]
  }
}

Com uma chave só de envio, a lista traz só send_email e track_event.

Enviar um e-mail

A ferramenta send_email aceita os campos de POST /emails, e idempotency_key faz o papel do cabeçalho Idempotency-Key. O exemplo manda para um endereço de teste, então nada é entregue.

POST /mcp (tools/call)

cURL

curl -X POST https://api.postal.so/mcp \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "send_email",
      "arguments": {
        "from": "Loja Exemplo <[email protected]>",
        "to": ["[email protected]"],
        "subject": "Pedido enviado",
        "html": "<p>Seu pedido saiu para entrega.</p>",
        "idempotency_key": "pedido-1042-enviado"
      }
    }
  }'

Node.js

const response = await fetch('https://api.postal.so/mcp', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
    'Content-Type': 'application/json',
    Accept: 'application/json, text/event-stream',
    'MCP-Protocol-Version': '2025-11-25',
  },
  body: JSON.stringify({
    jsonrpc: '2.0',
    id: 2,
    method: 'tools/call',
    params: {
      name: 'send_email',
      arguments: {
        from: 'Loja Exemplo <[email protected]>',
        to: ['[email protected]'],
        subject: 'Pedido enviado',
        html: '<p>Seu pedido saiu para entrega.</p>',
        idempotency_key: 'pedido-1042-enviado',
      },
    },
  }),
});

const data = await response.json();

Python

import os
import requests

response = requests.post(
    "https://api.postal.so/mcp",
    headers={
        "Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}",
        "Accept": "application/json, text/event-stream",
        "MCP-Protocol-Version": "2025-11-25",
    },
    json={
        "jsonrpc": "2.0",
        "id": 2,
        "method": "tools/call",
        "params": {
            "name": "send_email",
            "arguments": {
                "from": "Loja Exemplo <[email protected]>",
                "to": ["[email protected]"],
                "subject": "Pedido enviado",
                "html": "<p>Seu pedido saiu para entrega.</p>",
                "idempotency_key": "pedido-1042-enviado",
            },
        },
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->post('mcp', [
    'headers' => [
        'Authorization' => 'Bearer '.getenv('POSTAL_API_KEY'),
        'Accept' => 'application/json, text/event-stream',
        'MCP-Protocol-Version' => '2025-11-25',
    ],
    'json' => [
        'jsonrpc' => '2.0',
        'id' => 2,
        'method' => 'tools/call',
        'params' => [
            'name' => 'send_email',
            'arguments' => [
                'from' => 'Loja Exemplo <[email protected]>',
                'to' => ['[email protected]'],
                'subject' => 'Pedido enviado',
                'html' => '<p>Seu pedido saiu para entrega.</p>',
                'idempotency_key' => 'pedido-1042-enviado',
            ],
        ],
    ],
]);

$data = json_decode($response->getBody(), true);
200Resposta
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"id\":\"019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b\"}"
      }
    ],
    "structuredContent": {
      "id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b"
    },
    "isError": false
  }
}

O resultado vem em structuredContent (e também como texto em content). Siga o e-mail com get_email.

Quando a API recusa

Um erro da API vira um resultado com isError: true e o texto nome: mensagem, com o status HTTP 200 do JSON-RPC. Aqui, o remetente não está num domínio verificado:

200Resposta
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "validation_error: The outro.com domain is not verified. Please, add and verify your domain."
      }
    ],
    "isError": true
  }
}

Falhas de credencial e de permissão, ao contrário, são respostas HTTP (veja Erros).

#OAuth para quem desenvolve um cliente MCP

Quem usa um assistente pronto não precisa desta parte. Ela é para quem constrói um cliente MCP e quer conectar a Postal. O servidor de autorização é a própria API (https://api.postal.so) e segue o OAuth 2.1: fluxo de código de autorização com PKCE, sem outros fluxos.

1. Descoberta

  • GET /.well-known/oauth-protected-resource/mcp (RFC 9728) diz qual é o servidor de autorização e os escopos do recurso. Também existe na raiz.
  • GET /.well-known/oauth-authorization-server (RFC 8414) traz os endpoints (authorization_endpoint, token_endpoint, registration_endpoint, revocation_endpoint), os escopos (mcp:read, mcp:send, mcp:full) e code_challenge_methods_supported: ["S256"]. As duas respostas ficam em cache por 1 hora.

2. Registro do cliente

Há dois jeitos, e ambos dispensam cadastro manual:

  • Registro dinâmico (RFC 7591): POST /oauth/register. Os clientes são limitados por IP (30 por minuto e 300 por hora), e os que nunca concluem uma autorização são apagados depois de 24 horas.
  • Client ID Metadata Document: use como client_id uma URL HTTPS que devolve o JSON do cliente. A Postal busca o documento sem seguir redirecionamentos, com prazo de 5 segundos e até 5 KB, nunca em rede privada, e o guarda por 1 hora.

As redirect_uris (até 10) precisam ser https, de loopback (http://localhost, 127.0.0.1, [::1]) ou de um esquema próprio de app de desktop conhecido (cursor, vscode, vscode-insiders, windsurf, zed), sem credenciais nem fragmento. O pedido de autorização precisa usar uma delas, exatamente igual. O método de autenticação padrão é client_secret_basic (a resposta traz um client_secret, uma única vez); com none o cliente é público e se apoia só no PKCE.

POST /oauth/register

cURL

curl -X POST https://api.postal.so/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "Meu assistente",
    "redirect_uris": ["https://assistente.exemplo.com/oauth/callback"],
    "token_endpoint_auth_method": "none",
    "scope": "mcp:read mcp:send"
  }'

Node.js

const response = await fetch('https://api.postal.so/oauth/register', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    client_name: 'Meu assistente',
    redirect_uris: ['https://assistente.exemplo.com/oauth/callback'],
    token_endpoint_auth_method: 'none',
    scope: 'mcp:read mcp:send',
  }),
});

const data = await response.json();

Python

import requests

response = requests.post(
    "https://api.postal.so/oauth/register",
    json={
        "client_name": "Meu assistente",
        "redirect_uris": ["https://assistente.exemplo.com/oauth/callback"],
        "token_endpoint_auth_method": "none",
        "scope": "mcp:read mcp:send",
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->post('oauth/register', [
    'json' => [
        'client_name' => 'Meu assistente',
        'redirect_uris' => ['https://assistente.exemplo.com/oauth/callback'],
        'token_endpoint_auth_method' => 'none',
        'scope' => 'mcp:read mcp:send',
    ],
]);

$data = json_decode($response->getBody(), true);
201Resposta
{
  "client_id": "0198f3c2-7a41-7d0e-9b3a-5e6f7a8b9c0d",
  "client_id_issued_at": 1793000000,
  "client_name": "Meu assistente",
  "redirect_uris": ["https://assistente.exemplo.com/oauth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

3. Autorização

Abra o navegador da pessoa em GET /oauth/authorize. São obrigatórios response_type=code, client_id, redirect_uri e o PKCE com code_challenge_method=S256. Mande também resource (o endereço do MCP, https://api.postal.so/mcp, ou a origem https://api.postal.so), scope e state. Quem não está logado vai para o login do painel e volta; a tela de consentimento mostra o app, o endereço de retorno e as permissões, e a pessoa escolhe o time. O app não escolhe o time: um escopo team:… no pedido é recusado com invalid_scope. Com prompt=none o servidor responde login_required ou consent_required em vez de mostrar telas.

URL de autorização (exemplo)
https://api.postal.so/oauth/authorize?response_type=code&client_id=0198f3c2-7a41-7d0e-9b3a-5e6f7a8b9c0d&redirect_uri=https%3A%2F%2Fassistente.exemplo.com%2Foauth%2Fcallback&scope=mcp%3Aread%20mcp%3Asend&state=<state>&code_challenge=<code_challenge>&code_challenge_method=S256&resource=https%3A%2F%2Fapi.postal.so%2Fmcp

Depois que a pessoa autoriza, ela volta para a redirect_uri com code, state e iss (o emissor, RFC 9207: confira-o). Se recusa, volta com error=access_denied.

4. Tokens

Troque o code em POST /oauth/token (corpo application/x-www-form-urlencoded), com o code_verifier do PKCE e o mesmo resource. Só os grants authorization_code e refresh_token existem.

Trocar o código
curl -X POST https://api.postal.so/oauth/token \
  -d grant_type=authorization_code \
  -d client_id=0198f3c2-7a41-7d0e-9b3a-5e6f7a8b9c0d \
  -d "redirect_uri=https://assistente.exemplo.com/oauth/callback" \
  -d code=<code> \
  -d code_verifier=<code_verifier> \
  -d "resource=https://api.postal.so/mcp"
200Resposta
{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "<JWT>",
  "refresh_token": "<refresh_token>"
}
  • O token de acesso é um JWT de 1 hora, com a audiência do MCP (https://api.postal.so/mcp) e o escopo team:<id> do time escolhido. Um token que não nomeia este servidor como audiência é recusado (401 invalid_token).
  • O token de renovação vale 30 dias e é trocado a cada uso: guarde sempre o novo, porque o antigo deixa de servir.
  • Erros do OAuth vêm no formato da RFC 6749 ({"error": "invalid_grant", "error_description": "…"}), não no formato de erro da API.
Renovar
curl -X POST https://api.postal.so/oauth/token \
  -d grant_type=refresh_token \
  -d client_id=0198f3c2-7a41-7d0e-9b3a-5e6f7a8b9c0d \
  -d refresh_token=<refresh_token>

Para desconectar, o app chama POST /oauth/revoke (RFC 7009) com o token (de acesso ou de renovação) e as credenciais do cliente. Isso encerra a conexão inteira do app com aquele time, como se a pessoa a revogasse no painel, e responde 200 mesmo para um token desconhecido.

Os endpoints de registro, token, revogação, metadados e o /mcp aceitam chamadas de navegadores (CORS aberto, sem cookies). Limites: o token, 60 por minuto por cliente e IP; a autorização, 30 por minuto.

#Gerenciar as conexões

Em Configurações › Conexões de IA ficam os apps conectados ao time, com quem conectou, quando e o último uso, e o histórico recente de conexões, recusas e revogações do time. Revogar corta o acesso na hora; para usar o app de novo, ele precisa ser conectado outra vez.

  • Cada pessoa revoga as próprias conexões. Donos e administradores do time revogam as de qualquer pessoa.
  • Cada conexão é de uma pessoa em um time: se a pessoa sai do time ou a conta é suspensa, os tokens deixam de valer.
  • As chaves de API não aparecem ali; elas se gerenciam em Chaves de API.

#Limites

O quêLimite
PlanoRecurso mcp_access; sem ele, 403 plan_limit_reached
RequisiçõesO mesmo limite por segundo do time (Limites), contado à parte das chamadas REST; cada ferramenta ainda conta como uma requisição da API. Acima dele, 429 rate_limit_exceeded
TransporteSó POST em /mcp; GET e DELETE respondem 405 (Allow: POST)
Listaslimit de 1 a 100 (padrão 20), paginação por cursor
Destinatários e agendamentoOs da API: até 50 destinatários por e-mail e agendamento até 30 dias à frente
Token de acesso OAuth1 hora
Token de renovação OAuth30 dias, trocado a cada uso
Registro de clientes OAuth30 por minuto e 300 por hora, por IP

#Erros

Problemas de credencial, de plano e de escopo são respostas HTTP; problemas de uma chamada de ferramenta (validação, cotas, pausa de envio…) vêm como resultado com isError: true.

OndeNomeQuando acontece
401unauthorizedFaltou o token. A resposta traz WWW-Authenticate com os metadados do recurso.
401invalid_tokenO token OAuth é inválido, expirou, foi revogado ou não é para este servidor (audiência), a pessoa saiu do time ou a conta foi suspensa. Conecte o app de novo.
401invalid_api_keyA chave ps_… não existe ou foi excluída.
403plan_limit_reachedO plano do time não inclui o servidor MCP.
403insufficient_scopeA conexão OAuth não tem o escopo da ferramenta chamada (JSON-RPC error.code -32001, com WWW-Authenticate e o escopo que falta).
429rate_limit_exceededO time passou do limite de requisições por segundo. Espere o Retry-After.
toolrestricted_api_keyUma chave só de envio chamou uma ferramenta que não é de envio.
toolvalidation_errorDados inválidos, como um remetente fora de um domínio verificado.
toolsending_pausedO envio do time está pausado (bounces ou reclamações demais).
tooldaily_quota_exceededA cota diária de envio acabou.
toolconfirmation_requiredsend_broadcast sem confirm: true.
toolnot_foundO ID não existe neste time.

A linha “tool” é um erro dentro de um resultado de tools/call (HTTP 200). Os demais nomes são os da API; leia a mensagem e corrija a causa em vez de repetir a chamada às cegas.

#Veja também