Sniffington

Servidor MCP protegido por OAuth para acessar menções públicas à marca e ações suportadas de um workspace do Sniffington.

Servidor MCP hospedado

npx add-mcp 'https://sniffington.com/mcp'

Instala no Claude Code, Codex, Cursor e outros

Documentação

API do Sniffington

Leia e gerencie menções à marca via HTTP ou MCP.

Visão geral

O Sniffington encontra postagens públicas que mencionam sua marca ou palavras-chave no Reddit, X, Hacker News, YouTube, TikTok e outras plataformas. Um modelo de IA remove o ruído e classifica cada postagem com um sentimento e uma categoria. Esta API lê e altera os mesmos dados que você vê no aplicativo.

URL base: https://sniffington.com/api/v1. Requisições e respostas são em JSON. A especificação OpenAPI 3.1 está em /openapi.json, e agentes de IA podem começar por /llms.txt.

Autenticação

Crie uma chave de API no aplicativo em Integrações → Chaves de API e envie-a como um token bearer em cada requisição:

Authorization: Bearer ss_sk_…
  • Chaves ss_sk_… têm acesso total e podem ler e escrever.
  • Chaves ss_ro_… são somente leitura. Uma escrita com elas retorna 403.
  • Workspaces armazenados na UE têm chaves que começam com ss_sk_eu_… e ss_ro_eu_…. Elas funcionam da mesma forma.

Uma chave pertence a um workspace, então as requisições nunca nomeiam um workspace. O aplicativo mostra uma chave apenas uma vez. Se uma vazar, revogue-a lá e crie uma nova.

Clientes MCP como ChatGPT e Claude podem entrar com OAuth em vez de usar uma chave (veja MCP). Algumas operações, como criar chaves de API, exigem que um proprietário do workspace esteja conectado ao aplicativo. A referência marca essas como Proprietário.

Cadastro de agente

Um agente de IA pode criar sua própria conta. Uma pessoa ainda é a dona: ela aprova uma vez, por e-mail, e o agente coleta uma chave de API. Não há senha nem etapa de navegador para o agente.

1. O agente solicita uma conta para o endereço de e-mail de uma pessoa:

curl -X POST https://sniffington.com/api/agent/signup -H "content-type: application/json" \
  -d '{"email":"you@example.com","agent_name":"Claude","scope":"full"}'

scope é read ou full (padrão full). region é us ou eu; deixe-o de fora e ele seguirá a origem da requisição. A resposta é 202 com um claim_id, um poll_secret e um poll_url.

2. A pessoa recebe um e-mail com um link. Ela entra com esse endereço e escolhe o workspace, se o agente pode alterar coisas e um orçamento diário. O link funciona por uma hora e apenas para o proprietário daquele endereço.

3. O agente faz polling até a pessoa responder:

curl https://sniffington.com/api/agent/signup/<claim_id> -H "Authorization: Bearer <poll_secret>"
  • 202 pending: continue esperando, com alguns segundos de intervalo.
  • 200 approved: o corpo tem api_key, mostrado uma vez, além do workspace, scope e daily_budget. Armazene a chave agora.
  • 403 denied: a pessoa disse não. 410: o link expirou ou a chave já foi coletada. Comece de novo.

Chaves criadas dessa forma carregam um orçamento diário de 10, 25 ou 100 ações custosas: create_project, create_keyword, scan_now, scan_project e sniff_brand. Leituras são ilimitadas. Ao passar do orçamento, uma chamada retorna 429 com o código key_budget, e a ferramenta MCP whoami mostra quantas restam. Qualquer pessoa pode definir o mesmo limite em uma chave que criar passando daily_budget para create_api_key. Os limites do próprio plano também se aplicam.

Para subir de plano, um agente com chave de leitura e escrita chama request_upgrade (POST /v1/billing/upgrade-link). Ele retorna um link de pagamento para o proprietário da conta abrir. Nada é cobrado até que ele pague, e o plano muda quando a Polar confirma.

Os cadastros são limitados a 3 por dia por endereço de e-mail e 10 por dia por endereço de IP.

Início rápido

Mantenha a chave em uma variável de ambiente:

export SCOUT_API_KEY=ss_sk_…

Liste seus projetos. Cada um tem um id que outras chamadas usam:

curl https://sniffington.com/api/v1/projects \
  -H "Authorization: Bearer $SCOUT_API_KEY"

Adicione uma palavra-chave a um projeto. Aliases contam como a mesma palavra-chave, e postagens que contêm um termo excluído são ignoradas:

curl -X POST https://sniffington.com/api/v1/projects/acme/keywords \
  -H "Authorization: Bearer $SCOUT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"term": "acme", "aliases": ["acme.com", "@acmehq"], "excluded": ["acme corp"]}'

Liste menções negativas do Reddit e do X que ninguém tratou ainda, 50 por vez:

curl "https://sniffington.com/api/v1/mentions?project=acme&sources=reddit,x&sentiment=negative&status=open&limit=50" \
  -H "Authorization: Bearer $SCOUT_API_KEY"

Marque uma como concluída, usando o id dessa lista:

curl -X PATCH "https://sniffington.com/api/v1/mentions/MENTION_ID" \
  -H "Authorization: Bearer $SCOUT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "done"}'

A referência da API lista todas as operações com seus parâmetros.

Paginação

Operações de listagem retornam uma página de resultados e um cursor para a próxima:

{ "data": [ … ], "next_cursor": "WzE3NTg…", "total": 1234 }

Passe next_cursor de volta como cursor para obter a próxima página. Ele é null na última página. limit define o tamanho da página: 25 por padrão, no máximo 100. Um cursor marca uma posição na lista, então postagens que chegam enquanto você navega pelas páginas não deslocam nem repetem resultados.

Erros

Todo erro tem o mesmo formato. code é estável, então use-o para ramificar. message é escrito para pessoas e pode mudar.

{ "error": { "code": "not_found", "message": "no project acme" } }
StatusCódigoQuando
400bad_request, validation_errorUm parâmetro ou campo está ausente ou tem o tipo errado. A mensagem o nomeia.
401unauthorizedSem credenciais, ou a chave está errada, expirada ou revogada.
402plan_limitO limite do plano em projetos, palavras-chave ou assentos foi atingido.
403forbiddenA credencial não tem permissão para fazer isso, por exemplo, uma chave somente leitura tentando escrever.
404not_foundNada com esse id neste workspace.
409conflictA requisição conflita com o estado atual, por exemplo, um nome que já está em uso.
415unsupported_media_typeUma escrita de um navegador conectado que não é JSON.
429rate_limitedMuitas requisições. Aguarde o número de segundos em Retry-After.

Um status 5xx significa que o servidor falhou. Leituras são seguras para repetir.

Limites de taxa

Cada credencial pode fazer 240 leituras (GET) e 60 escritas por minuto. Chamadas sem chave, como os endpoints públicos do painel, são contadas por endereço de IP.

Ao passar do limite, você recebe um 429 com o código rate_limited. Aguarde um minuto e tente novamente.

Webhooks

Um webhook envia eventos, como uma nova menção, para sua URL como JSON em um POST. Adicione endpoints no aplicativo ou com as operações de webhook.

Cada entrega é assinada no padrão Standard Webhooks, com estes cabeçalhos:

  • webhook-id: o id da mensagem. Repetições o reutilizam, então use-o para ignorar duplicatas.
  • webhook-timestamp: quando foi enviada, em segundos Unix.
  • webhook-signature: um ou mais valores v1,<signature> separados por espaço.

A assinatura é o HMAC-SHA256 em base64 de {webhook-id}.{webhook-timestamp}.{body}. A chave é o segredo do seu endpoint: remova o prefixo whsec_ e decodifique o restante em base64. Verifique a assinatura contra o corpo bruto antes de analisar o JSON e rejeite timestamps com mais de cinco minutos.

import { createHmac, timingSafeEqual } from "node:crypto";

// secret: the endpoint's "whsec_…" secret. headers: the request headers. body: the raw body as a string.
export function verifyWebhook(secret, headers, body) {
  const id = headers["webhook-id"], ts = headers["webhook-timestamp"], sigs = headers["webhook-signature"];
  if (!id || !ts || !sigs) return false;
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const expected = Buffer.from(createHmac("sha256", key).update(\`${id}.${ts}.${body}\`).digest("base64"));
  return sigs.split(" ").some((part) => {
    const [version, sig] = part.split(",");
    const given = Buffer.from(sig || "");
    return version === "v1" && given.length === expected.length && timingSafeEqual(given, expected);
  });
}

Responda com um status 2xx rapidamente e faça o trabalho lento depois. Qualquer outro status, ou nenhuma resposta, conta como falha, e a entrega é repetida com intervalos crescentes por cerca de 90 horas.

MCP

O Sniffington também é um servidor MCP, em https://sniffington.com/mcp via Streamable HTTP.

  • ChatGPT, Claude e outros clientes que suportam OAuth 2.1 com registro dinâmico de clientes só precisam dessa URL. Eles se registram e pedem que você entre, então não há chave para copiar.
  • Clientes sem OAuth podem enviar uma chave de API como Authorization: Bearer ss_…. Com uma chave somente leitura, apenas as ferramentas de leitura funcionam.

Cada operação marcada como ferramenta MCP na referência é uma ferramenta com o mesmo nome e recebe os parâmetros e campos do corpo dessa operação como argumentos.

Claude Code:

claude mcp add --transport http sniffington https://sniffington.com/mcp

Um cliente configurado com uma chave:

{
  "mcpServers": {
    "sniffington": {
      "url": "https://sniffington.com/mcp",
      "headers": {
        "Authorization": "Bearer ss_ro_…"
      }
    }
  }
}