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.
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
| Escopo | Abrange |
|---|---|
| site.read | Perfil e configurações da comunidade |
| content.read | Leitura de publicações, feeds, busca e árvores de respostas |
| content.write | Criar, editar e excluir publicações |
| media.read | URLs assinadas das mídias armazenadas |
| media.write | Tokens de envio de mídia — também lê URLs assinadas, por compatibilidade |
| spaces.read | Diretório e detalhes de espaços |
| members.read | Leitura 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.
X-API-Key: mfk_xxx
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
{
"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.
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.
Perfil, configurações e metadados da comunidade
Uma publicação por id
Obrigatório: id
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
Pesquisar publicações — o campo é query, não keyword
Obrigatório: query
Publicações em destaque, opcionalmente limitadas a um espaço
Todas as respostas sob uma publicação
Obrigatório: post_id
Apenas as respostas de primeiro nível
Obrigatório: post_id
A subárvore abaixo de uma resposta
Obrigatório: post_id
A cadeia de ancestrais de uma resposta
Obrigatório: post_id
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
Feed da comunidade por pontuação (cursor de ponto flutuante)
Feed de destaques (cursor de texto)
O feed de um espaço, com ordenação, busca e filtros de perguntas e respostas opcionais (cursor de texto)
Obrigatório: space_id
O feed de um espaço por pontuação (cursor de ponto flutuante) — a entrada antiga, mantida por compatibilidade
Obrigatório: space_id
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
Editar uma publicação; passe version para travamento otimista
Obrigatório: actor_user_id, idempotency_key, post_id
Excluir uma publicação
Obrigatório: actor_user_id, idempotency_key, post_id
Os espaços visíveis para o observador — omita viewer_user_id para a visão anônima, apenas do que é publicamente legível
Um espaço por id
Obrigatório: id
Um membro por id
Obrigatório: id
Resolver um endereço de e-mail exato para um membro
Obrigatório: email
Token de curta duração para o serviço de envio de mídia
Obrigatório: author_user_id
URLs assinadas para mídia privada (expiram — assine de novo, não armazene)
Obrigatório: author_user_id, items, access_level
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_idO membro em nome de quem a nova publicação sai. Precisa já pertencer a esta comunidade; caso contrário, 20303.
actor_user_idQuem executa uma edição ou exclusão. A permissão é avaliada sobre esse membro — quem não é autor nem administrador recebe 10004.
viewer_user_idOpcional 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:
- Troque sua chave de API por um token de envio de curta duração em /media/get_token.
- 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.
- 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.
# 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ódigo | HTTP | Significado |
|---|---|---|
| 0 | 200 | Sucesso |
| 10005 | 400 | Parâmetros inválidos — msg indica o campo problemático |
| 10007 | 401 | Chave inválida, revogada ou expirada, ou o IP de origem não está na lista permitida |
| 21304 | 403 | A chave não tem o escopo que este endpoint exige; os escopos ficam fixos na criação |
| 22203 | 403 | O plano não inclui acesso à API — veja data.required_plan |
| 22204 | 403 | Cota diária esgotada — veja data.current e data.limit |
| 20303 | 404 | author_user_id / viewer_user_id não é membro desta comunidade |
| 10003 | 404 | O registro de destino não existe |
| 10004 | 403 | Quem age não tem permissão sobre este recurso |
| 10202 | 429 | Limite de taxa — reduza o ritmo e respeite o cabeçalho Retry-After |
| 10001 | 500 | Erro 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».
// 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.
| Camada | O que conta | Ao ultrapassar |
|---|---|---|
| Cota diária do plano | Um dia inteiro, todos os endpoints, por comunidade | 22204 / HTTP 403 |
| Orçamento de rajada por chave | Janelas de 10 e 60 segundos, por chave, escalonado por endpoint | 10202 / HTTP 429 |
| Limite geral por IP | Por IP de origem, 60 a cada 10 s e 300 por minuto | 10202 / 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.
| Faixa | Abrange | Por 10 s | Por 60 s |
|---|---|---|---|
| Leituras | Feeds, publicações, respostas, espaços, introspecção | 120 | 600 |
| Mídia | get_token e get_signed_urls | 30 | 120 |
| Escritas | Criar, atualizar e excluir publicações | 20 | 120 |
| Busca | post/search | 20 | 60 |
| Busca de membro | member/lookup | 5 | 10 |
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.
| Campo | Identifica | Entre retentativas |
|---|---|---|
| event_id | O evento de negócio | Estável |
| occurred_at | Quando o evento aconteceu | Estável |
| delivery_id | Esta tentativa de entrega | Muda |
| timestamp | Quando esta tentativa foi enviada | Muda |
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.
| Sintoma | Geralmente |
|---|---|
| 10007 invalid API key, mas a chave acabou de ser copiada do console | Espaç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_available | O 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 0 | A 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 usar | A 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 429 | Um 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_unsupported | Você 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 funcionar | Desde setembro de 2026 um token sem escopo serve só para envio. Passe um escopo read ou delete |
| /member/lookup devolve 200 com found false | Isso é um «não encontrado» normal, não um erro. Acertos e falhas são ambos 200 — ramifique por found |
| 10005 invalid params mencionando query | O campo de busca é query. keyword não é um campo válido |
| 20303 user not found | O id não é membro desta comunidade, ou você enviou um id numérico em vez de um hashid |
| A paginação para de avançar | Os 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 tempo | URLs 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