Ir para o conteúdo
Programadores

API aberta de comunidades

Uma API no escopo de uma comunidade: uma chave fica ligada a uma comunidade e age em nome dos membros que você indicar. Não é a API do painel do criador nem a API de membros — aqui não existe sessão de login.

Baixar openapi.jsonContrato publicado em setembro de 2026

Antes de começar

Duas coisas precisam ser verdade antes que qualquer chave funcione, e nenhuma delas se resolve a partir do seu código.

1. O plano da comunidade precisa incluir acesso à API

Quem controla isso é a pessoa dona da comunidade, não quem integra. O plano precisa do recurso de acesso à API — caso contrário toda chamada devolve 22203 — e de uma cota diária diferente de zero; senão toda chamada devolve 22204.

Comunidades criadas há mais tempo têm o acesso à API desligado por padrão, mesmo quando o plano já inclui outros recursos avançados. Se uma chave recém-emitida devolve 22203 em todas as chamadas, peça a quem é dono que verifique no console em vez de reler seu código.

Você não precisa descobrir isso acumulando erros: /capabilities/get responde diretamente e continua funcionando nesses dois estados.

2. Crie uma chave no console de administração da comunidade

Administração da comunidade → Integrações → API Keys → Criar. Uma chave é mfk_ seguido de 64 caracteres hexadecimais, e o valor completo aparece exatamente uma vez — depois só o prefixo fica visível.

Os escopos marcados na criação decidem quais endpoints a chave pode chamar. Não é possível editá-los depois; para mudar, crie uma nova chave e revogue a antiga.

Opcionalmente restrinja uma chave a uma lista de IPs permitidos (endereços isolados ou faixas CIDR). Requisições de qualquer outro lugar falham com 10007 — o mesmo código de uma chave inválida, o que vale lembrar quando uma chave que funcionava ontem para de funcionar a partir de outro host.

Escopos

EscopoAbrange
site.readPerfil e configurações da comunidade
content.readLeitura de publicações, feeds, busca e árvores de respostas
content.writeCriar, editar e excluir publicações
media.readURLs assinadas das mídias armazenadas
media.writeTokens de envio de mídia — também lê URLs assinadas, por compatibilidade
spaces.readDiretório e detalhes de espaços
members.readLeitura de membros e busca por e-mail

O significado de * mudou em setembro de 2026

Antes queria dizer «tudo, inclusive os escopos adicionados depois», o que ampliava silenciosamente cada chave já emitida sempre que a plataforma lançava um recurso novo. Agora uma chave nova guarda a lista concreta vigente no momento da criação, e um * herdado cobre apenas os quatro escopos que existiam quando foi assinado: site.read, content.read, content.write e media.write.

Por isso uma chave antiga não consegue chamar os endpoints mais novos — espaços, membros e leitura de URLs assinadas. Emita uma chave nova com esses escopos marcados. É deliberado: ninguém deveria entregar seu diretório de membros só porque a plataforma lançou um recurso.

Autenticação

Envie sua chave em um dos dois cabeçalhos. São equivalentes; se mandar os dois, vence o X-API-Key. A comunidade é deduzida da chave, então você nunca passa um id de site.

Opção 1: cabeçalho X-API-Key (recomendado)
X-API-Key: mfk_xxx
Opção 2: cabeçalho Authorization Bearer
Authorization: Bearer mfk_xxx

A chave por si só é a credencial — guarde-a no servidor. Ela não pertence nem ao código do navegador nem a um pacote móvel, onde qualquer pessoa pode lê-la e publicar como se fosse seus membros.

Convenções

Valem para todos os endpoints abaixo.

  • Todos os endpoints são POST. Não há GET, PUT nem DELETE — as leituras também são POST.
  • Envie Content-Type: application/json. Os endpoints que não recebem parâmetros ainda assim esperam um corpo JSON vazio.
  • Todo id é uma string hashid como "kZ3mQ9x" — comunidade, usuário, publicação, espaço e mídia igualmente. Ids numéricos são rejeitados.
  • Os carimbos de tempo voltam em RFC3339, por exemplo 2026-08-18T10:00:00Z.
  • Os valores monetários são inteiros na menor unidade da moeda (centavos).
  • Um cabeçalho Accept-Language opcional (zh, en, ja, ko, es, fr, de, pt) traduz as mensagens de erro.
  • A comunidade é deduzida da sua chave de API. Não envie um id de site nem no caminho nem no corpo.

Uma entrada quebra essa regra: scheduled_at em /post/create e /post/update é um carimbo Unix i64 em segundos, não uma string. Veja publicações agendadas mais abaixo.

Sucesso e falha compartilham um mesmo envelope

JSON
{
  "code": 0,
  "msg": "success",
  "data": { }
}

Endpoints

Todos os endpoints ficam sob /site_open_api/v1, usam POST com corpo JSON e exigem na sua chave o escopo indicado.

Introspecção
POST/capabilities/getsem escopo

Os escopos da sua chave, se o acesso à API está ativo, e a cota usada, a restante e o horário de reinício. É o único endpoint que ainda responde quando o plano ou a cota diária bloqueiam todo o resto — e ele não consome cota. Um quota_limit de -1 significa ilimitado; os três campos de cota aparecem e desaparecem juntos, então confirme que existem antes de subtrair.

Comunidade
POST/site/getsite.read

Perfil, configurações e metadados da comunidade

Leitura de publicações
POST/post/getcontent.read

Uma publicação por id

Obrigatório: id

POST/post/batch_getcontent.read

Até 50 publicações de uma vez; os ids não encontrados voltam em missing_ids em vez de derrubar o lote

Obrigatório: ids

POST/post/searchcontent.read

Pesquisar publicações — o campo é query, não keyword

Obrigatório: query

POST/post/featured_postscontent.read

Publicações em destaque, opcionalmente limitadas a um espaço

POST/post/replies/listcontent.read

Todas as respostas sob uma publicação

Obrigatório: post_id

POST/post/direct_replies/listcontent.read

Apenas as respostas de primeiro nível

Obrigatório: post_id

POST/post/reply_descendants/listcontent.read

A subárvore abaixo de uma resposta

Obrigatório: post_id

POST/post/thread_chaincontent.read

A cadeia de ancestrais de uma resposta

Obrigatório: post_id

Feeds
POST/feed/listcontent.read

Feed da comunidade, mais recentes primeiro (cursor de texto). since_id traz apenas publicações mais novas — ele não vê edições nem exclusões, então serve para completar uma linha do tempo, não para sincronizá-la

POST/feed/topcontent.read

Feed da comunidade por pontuação (cursor de ponto flutuante)

POST/feed/featuredcontent.read

Feed de destaques (cursor de texto)

POST/space/feed/listcontent.read

O feed de um espaço, com ordenação, busca e filtros de perguntas e respostas opcionais (cursor de texto)

Obrigatório: space_id

POST/space/feed/topcontent.read

O feed de um espaço por pontuação (cursor de ponto flutuante) — a entrada antiga, mantida por compatibilidade

Obrigatório: space_id

Escrita de publicações
POST/post/createcontent.write

Publicar como um membro. Corpo, espaço, título, alvos de resposta e citação, mídia, enquetes, áudio e agendamento são todos opcionais

Obrigatório: author_user_id, idempotency_key

POST/post/updatecontent.write

Editar uma publicação; passe version para travamento otimista

Obrigatório: actor_user_id, idempotency_key, post_id

POST/post/deletecontent.write

Excluir uma publicação

Obrigatório: actor_user_id, idempotency_key, post_id

Espaços
POST/space/listspaces.read

Os espaços visíveis para o observador — omita viewer_user_id para a visão anônima, apenas do que é publicamente legível

POST/space/getspaces.read

Um espaço por id

Obrigatório: id

Membros
POST/member/getmembers.read

Um membro por id

Obrigatório: id

POST/member/lookupmembers.read

Resolver um endereço de e-mail exato para um membro

Obrigatório: email

Mídia
POST/media/get_tokenmedia.write

Token de curta duração para o serviço de envio de mídia

Obrigatório: author_user_id

POST/media/get_signed_urlsmedia.read | media.write

URLs assinadas para mídia privada (expiram — assine de novo, não armazene)

Obrigatório: author_user_id, items, access_level

cURL
curl -X POST https://api.mateflow.com/site_open_api/v1/capabilities/get \
  -H "X-API-Key: mfk_xxx" -H "Content-Type: application/json" -d '{}'

# → data: {
#     "api_version": "v1",
#     "site_id": "kZ3mQ9x",
#     "scopes": ["site.read", "content.read"],
#     "api_access": true,
#     "quota_limit": 5000,     // -1 means unlimited
#     "quota_used": 128,
#     "quota_reset_at": "2026-09-15T00:00:00Z"
#   }
  • As leituras que recebem uma lista de ids param em 50, e o limit de todos os endpoints paginados também para em 50.
  • Em /space/feed/list a codificação do cursor acompanha o sort, então trocar a ordenação significa reiniciar a paginação. Espaços de perguntas e respostas ignoram sort e ordenam por qa_sort. Integrações novas devem usar /space/feed/list com sort=top em vez de /space/feed/top.
  • As respostas de membro são uma projeção estreita: id, username, display_name, avatar_url, status, role e created_at. O e-mail nunca é devolvido, mesmo quando foi por ele que você pesquisou — você já tem esse endereço, e devolver o de cada membro transformaria /member/get em uma exportação de contatos. A busca é apenas por correspondência exata, e não achar nada é um sucesso normal, não um erro, de modo que acertos e falhas não se distinguem nem pelo código de status nem pelo tempo de resposta. É também o limite de taxa mais rígido da API.

Agir em nome de um membro

A API não tem sessão de login, então quem age — e para quem os resultados são apresentados — vai sempre explícito no corpo da requisição. Os três recebem um hashid de membro.

  • author_user_id

    O membro em nome de quem a nova publicação sai. Precisa já pertencer a esta comunidade; caso contrário, 20303.

  • actor_user_id

    Quem executa uma edição ou exclusão. A permissão é avaliada sobre esse membro — quem não é autor nem administrador recebe 10004.

  • viewer_user_id

    Opcional nas leituras. Devolve os resultados como esse membro os vê: espaços privados, estado de curtidas e favoritos. Omita para a visão anônima, apenas pública.

Paginação

  • Feeds e listas devolvem seus itens junto com next_cursor. Envie next_cursor de volta sem alterações para buscar a próxima página; um next_cursor vazio significa que você chegou ao fim.
  • limit para em 50 em todo lugar.
  • Em /space/feed/list o cursor codifica a ordenação atual. Mudar sort no meio da listagem o invalida — volte para a primeira página.

Atenção: os cursores de /feed/top e /space/feed/top são pontuações em ponto flutuante, não strings. Guardar os dois tipos na mesma variável de texto quebra a paginação em silêncio.

Idempotência

Toda escrita recebe um idempotency_key no corpo da requisição. Aqui há exatamente uma camada de idempotência — veja a nota abaixo se você já conhece a da plataforma, baseada em cabeçalho.

  • idempotency_key é obrigatório em toda escrita e limitado a 190 caracteres. Reutilizar um nunca cria uma segunda publicação.
  • Seu escopo é a comunidade, o tipo de requisição e a chave em conjunto, independentemente de qual chave de API você usou: duas chaves diferentes da mesma comunidade enviando o mesmo idempotency_key ao mesmo endpoint fazem a segunda chamada repetir o resultado da primeira.
  • A mesma chave com um corpo diferente devolve um erro de conflito estável, em vez de passar silenciosamente como uma retentativa bem-sucedida.
  • Quando uma requisição expira, tente de novo com a mesma chave — nunca gere uma nova.

O cabeçalho geral Idempotency-Key da plataforma — aquele com cache de resposta de 24 horas — não se aplica a /site_open_api/**. Essa camada fica antes da autenticação por chave, então um acerto de cache pularia por completo a verificação da chave. Não envie o cabeçalho esperando que ele faça algo aqui.

Publicações agendadas

O agendamento é o único ponto em que tanto o formato de entrada quanto o comportamento diferem do resto.

  • scheduled_at é um carimbo Unix i64 em segundos — não a string RFC3339 que todos os outros horários usam.
  • Em /post/update, omitir scheduled_at deixa o agendamento existente intacto. Não é uma limpeza.

Passar um valor 0 ou menor cancela o agendamento e devolve a publicação ao rascunho — mas só publicações de blog têm estado de rascunho. Em uma publicação comum, a mesma chamada devolve 10005 com schedule_cancel_unsupported, porque o caminho por baixo ali é «publicar agora»: aceitá-la tornaria o conteúdo público antes da hora, dispararia o feed e as notificações, e não deixaria nada para desfazer.

Enviar mídia

Os arquivos vão para o serviço de mídia, não para o host da API. Três passos:

  1. Troque sua chave de API por um token de envio de curta duração em /media/get_token.
  2. Envie o arquivo por POST como multipart ao serviço de mídia, com o token em um cabeçalho literalmente chamado "token" e file_cate definido como Media, Avatar, Header, Audio ou File.
  3. Passe o id de mídia devolvido em media_ids ao criar a publicação.

Mudança incompatível, setembro de 2026

Um token emitido sem escopo explícito agora carrega apenas a permissão de envio; antes trazia envio, leitura e exclusão juntos. Se você usava esse token para ler ou excluir, passe explicitamente um escopo read ou delete. À parte disso, uma comunidade em período de carência — teste expirado, sem cartão cadastrado — tem o token de envio negado pelo plano; os de leitura e exclusão não são afetados.

cURL
# 1. Exchange the API key for a short-lived upload token.
#    Since 2026-09 a token with no explicit scope is upload-only.
curl -X POST https://api.mateflow.com/site_open_api/v1/media/get_token \
  -H "X-API-Key: mfk_xxx" -H "Content-Type: application/json" \
  -d '{"author_user_id": "kZ3mQ9x", "scope": "upload"}'

# 2. Upload the file to the media service (token goes in a "token" header)
curl -X POST https://media.mateflow.com/api/v1/media/upload \
  -H "token: <token from step 1>" \
  -F "file_cate=Media" -F "file=@photo.jpg"

# 3. Attach the media id when creating the post
curl -X POST https://api.mateflow.com/site_open_api/v1/post/create \
  -H "X-API-Key: mfk_xxx" -H "Content-Type: application/json" \
  -d '{"author_user_id":"kZ3mQ9x","idempotency_key":"6f1c...","body":"Hi","media_ids":["m8Yq2Lp"]}'

A mídia privada é lida por /media/get_signed_urls, que recebe os items e um access_level de 1 (público), 2 (semiprivado) ou 3 (privado) e devolve URLs com validade. Peça-as quando precisar em vez de armazená-las, ou elas passarão a devolver 403.

Anexar mídia a uma publicação

  • Envie ou medias — uma lista de pares media_id e alt — ou media_ids, uma lista simples de ids. Enviar os dois devolve 10005.
  • alt é opcional. Uma string vazia limpa o texto alternativo existente; omitir o campo o mantém como estava.

Códigos de erro

Sucesso e falha compartilham o envelope, e o status HTTP acompanha o código de negócio. Ramifique por code, não pelo status.

CódigoHTTPSignificado
0200Sucesso
10005400Parâmetros inválidos — msg indica o campo problemático
10007401Chave inválida, revogada ou expirada, ou o IP de origem não está na lista permitida
21304403A chave não tem o escopo que este endpoint exige; os escopos ficam fixos na criação
22203403O plano não inclui acesso à API — veja data.required_plan
22204403Cota diária esgotada — veja data.current e data.limit
20303404author_user_id / viewer_user_id não é membro desta comunidade
10003404O registro de destino não existe
10004403Quem age não tem permissão sobre este recurso
10202429Limite de taxa — reduza o ritmo e respeite o cabeçalho Retry-After
10001500Erro do servidor — é seguro tentar de novo

Atenção à armadilha: 22203 e 22204 são 403, não 429. Só 10202 é limitação de taxa. Detecte pelo code, nunca pelo status HTTP — é a leitura errada mais comum desta API.

As respostas de erro trazem um request_id. Cite-o ao relatar um problema — é assim que encontramos exatamente a sua chamada nos registros.

Os erros de plano trazem metadados estruturados

Os dois erros de plano voltam com detalhe suficiente para dizer a quem é dono da comunidade o que fazer, para você mostrar uma mensagem de verdade em vez de «algo deu errado».

JSON
// The plan does not include API access
{
  "code": 22203,
  "msg": "...",
  "request_id": "00de5640-8ae5-4a71-aef8-2f2cab0ce8a7",
  "data": {
    "error_code": "feature_not_available",
    "feature": "api_access",
    "required_plan": "Growth"
  }
}

// The community is out of daily quota
{
  "code": 22204,
  "data": {
    "error_code": "quota_exceeded",
    "resource": "api_requests_per_day",
    "current": "5001",
    "limit": "5000",
    "required_plan": "Business"
  }
}

Cota e limites de taxa

Três mecanismos distintos. Confundi-los é a razão de se relatar que uma integração «está limitada por taxa» quando na verdade acabou a cota do plano.

CamadaO que contaAo ultrapassar
Cota diária do planoUm dia inteiro, todos os endpoints, por comunidade22204 / HTTP 403
Orçamento de rajada por chaveJanelas de 10 e 60 segundos, por chave, escalonado por endpoint10202 / HTTP 429
Limite geral por IPPor IP de origem, 60 a cada 10 s e 300 por minuto10202 / HTTP 429

A cota diária conta as requisições que passam pela autenticação, reinicia às 00:00 UTC e informa em data.limit o teto realmente em vigor — incluindo qualquer folga concedida especificamente àquela comunidade. Quando o backend do contador falha, a requisição passa em vez de ser recusada, então uma contagem ocasionalmente baixa não significa que a cota parou de funcionar.

Orçamento de rajada por chave (novo em setembro de 2026)

Antes o único balde era por IP, então vários clientes atrás do mesmo endereço de saída disputavam espaço entre si, enquanto quem espalhava as chamadas por muitos endereços mal era restringido. Agora o orçamento é escalonado por chave — aquilo que quem é dono emite, pode rotacionar e por que responde.

FaixaAbrangePor 10 sPor 60 s
LeiturasFeeds, publicações, respostas, espaços, introspecção120600
Mídiaget_token e get_signed_urls30120
EscritasCriar, atualizar e excluir publicações20120
Buscapost/search2060
Busca de membromember/lookup510

member/lookup é bem mais rígido por segurança, não por capacidade. As outras restrições limitam quanto uma única resposta revela; esta limita quantas vezes você pode perguntar — e é justamente esse o passo que transforma a busca em coleta de diretório. Usado como foi pensado, resolvendo no ritmo de uma pessoa endereços que você já tem, dez por minuto sobram.

  • Ultrapassar devolve HTTP 429 com code 10202, mais Retry-After e os cabeçalhos X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset.
  • Trate esses números como ponto de partida, não como medição — eles são ajustados conforme o tráfego observado. Não os fixe na sua lógica de retentativa; respeite o 429 e o Retry-After. Toda a camada de limite de taxa vem desligada por padrão e roda um tempo em modo de observação antes de ser aplicada.
  • Implemente recuo em 429 de qualquer forma, e mantenha o tráfego de leitura em cerca de dez requisições por segundo ou menos.

Receber webhooks

Se quem é dono da comunidade configurou um endpoint de webhook ou conectou Slack, Discord ou Zapier, as entregas trazem dois conjuntos de ids que significam coisas diferentes.

CampoIdentificaEntre retentativas
event_idO evento de negócioEstável
occurred_atQuando o evento aconteceuEstável
delivery_idEsta tentativa de entregaMuda
timestampQuando esta tentativa foi enviadaMuda

Deduplique pelo event_id. Uma entrega que você processou bem mas respondeu devagar demais chega de novo com um delivery_id novo e o mesmo event_id. Quando um evento vai ao mesmo tempo para um webhook próprio e para um Zap, os dois lados veem o mesmo event_id, então dá para cruzá-los.

A entrega é pelo menos uma vez e pode chegar fora de ordem. Tanto duplicatas quanto a chegada tardia de eventos antigos são normais; quem recebe precisa ser idempotente.

event_id pode não existir. Tarefas enfileiradas antes de o campo existir não o trazem, e a entrega omite o campo por completo em vez de mandar 0 — um 0 faria todas as tarefas antigas parecerem o mesmo evento. Aceite a ausência e recorra a uma deduplicação de melhor esforço pelo delivery_id.

Resolução de problemas

Os sintomas que realmente aparecem, e no que costumam dar.

SintomaGeralmente
10007 invalid API key, mas a chave acabou de ser copiada do consoleEspaço ou quebra de linha em volta da chave; ou a chave foi revogada; ou existe uma lista de IPs permitidos e o seu endereço de saída não está nela
22203 feature_not_availableO plano está com o acesso à API desligado. Depois que quem é dono mudar, aguarde até 10 minutos para o cache do plano expirar
22204 quota_exceeded com limit igual a 0A cota diária de requisições do plano é 0, o que significa indisponível — e não «não configurado, logo ilimitado»
21304 em um endpoint que você esperava usarA chave não tem esse escopo. Os escopos ficam fixos na criação, então a solução é uma chave nova
21304 em uma chave com «todas as permissões» (*)Um * herdado não cobre os escopos adicionados depois — media.read, spaces.read, members.read. Emita uma chave nova com eles marcados
10202 com HTTP 429Um limite de taxa. Reduza o ritmo conforme o Retry-After; se foi em member/lookup, lembre que esse balde é de dez por minuto
10005 com schedule_cancel_unsupportedVocê passou um scheduled_at de 0 ou menor em uma publicação comum. Só as de blog têm um estado de rascunho para onde voltar
Um token de mídia que servia para ler ou excluir parou de funcionarDesde setembro de 2026 um token sem escopo serve só para envio. Passe um escopo read ou delete
/member/lookup devolve 200 com found falseIsso é um «não encontrado» normal, não um erro. Acertos e falhas são ambos 200 — ramifique por found
10005 invalid params mencionando queryO campo de busca é query. keyword não é um campo válido
20303 user not foundO id não é membro desta comunidade, ou você enviou um id numérico em vez de um hashid
A paginação para de avançarOs cursores de /feed/top e /space/feed/top são de ponto flutuante. Serializados como texto, deixam de corresponder
As URLs de mídia passam a devolver 403 depois de um tempoURLs assinadas têm um TTL. Assine de novo quando precisar em vez de armazená-las

Notas

  • A comunidade é deduzida da sua chave de API. Não envie um id de site nem no caminho nem no corpo.
  • Os escopos ficam fixos quando a chave é criada. Para mudá-los, crie uma chave nova e revogue a antiga.
  • Guarde suas chaves de API no servidor. Uma chave em código de cliente permite que qualquer pessoa que a leia publique como se fosse seus membros.
  • O contrato OpenAPI cobre cada caminho e cada esquema. Uma ressalva: o gerador de onde ele vem não emite required nos corpos de requisição, então tire os campos obrigatórios desta página e não do contrato.

Comece a construir

Crie uma chave com escopos no console da sua comunidade, chame /capabilities/get para confirmá-la e siga em frente.

Teste gratuito de 14 dias · Sem cartão de crédito

Iniciar teste gratuito