Programando bots para o Mastodon: listando usuários via API
A API do Mastodon é rica, e desenvolver clientes ou robôs para interagir com ela está ao seu alcance em várias linguagens, como veremos em mais um exemplo.
Dando sequência aos exemplos de interação direta com a API do Mastodon que venho publicando, hoje veremos como obter a lista de usuários de uma instância, juntamente com dados sobre a sua atividade e identidade.
O ensejo para escrever este post foi o interesse em gerar esta lista das contas mais falantes da instância brasileira organica.social, que a partir do início da semana passada recebeu uma migração de 700+ brasileiros, graças especialmente a uma ação bem-sucedida da Biana na rede Bluesky.
Para rodar os exemplos abaixo, você precisa ter uma shell Bash ou compatível, com o awk e o gron (para facilitar o consumo das respostas em formato JSON), instalados em um sistema compatível com o Posix (como a maior parte das distribuições Linux, o Mac e Unix em geral).

Iremos direto ao ponto, e para uma explicação mais detalhada sobre alguns conceitos, sugiro a leitura também do post Programando bots para o Mastodon: obtendo dados via API.
Encaminhando consultas para a API do Mastodon
Já vimos que a interface básica com uma instância do Mastodon é por meio de uma interface (a API) acessada por meio de URLs, e que responde em formato JSON. Há uma série de bibliotecas que intermediam esse acesso em várias linguagens, mas faremos o acesso diretamente, para demonstrar o funcionamento.
Nosso exemplo de hoje é baseado em um endpoint específico da API: o /api/v1/directory, que na minha instância fica visível pela URL https://arram.senta-la.cloud/api/v1/directory - se você acessá-la em seu navegador, verá uma longa linha contendo a lista dos usuários conhecidos pela instância.
Com o acesso básico (da URL acima), a resposta colocará os usuários em ordem de quem postou mais recentemente, e mostrará apenas os primeiros 40 usuários que a instância conhece – ou seja, os usuários que postam em alguma timeline, lista ou tag seguida pelos usuários da própria instância.
A URL também pode incluir parâmetros, como:
local=true- ao invés de listar todos os usuários que a instância conhece, lista apenas os usuários da própria instância.limit=60- ao invés de retornar uma lista com até 40 contas, retornará até 60 (o máximo é 80)offset=120- ao invés de começar a listar a partir do 1º usuário, começa do 120º usuário.
Embora só possamos receber 80 contas por vez, note que com uma sequência de chamadas usando limit=80 e variando o parâmetro offset, podemos listar quantos quisermos: a primeira será com offset=0 (que é o default), a segunda com offset=80, depois 160, e assim por diante.
Vamos a 2 exemplos de URLs com parâmetros:
https://arram.senta-la.cloud/api/v1/directory?local=true- Retorna uma longa linha contendo apenas os usuários locais da minha instância (que é pequena, então será uma lista de meia-dúzia de contas).https://arram.senta-la.cloud/api/v1/directory?limit=80&offset=160- retorna uma longa linha contendo o equivalente à terceira página (considerando que cada página são 80 contas) da lista de usuários conhecidos pela instância.
Entendendo as respostas da API
Essas longas linhas da resposta são difíceis de compreender visualmente, porque usam o formato JSON, compatível com uma série de sistemas, mas de baixa legibilidade humana direta.
Há várias formas de ampliar a legibilidade do JSON mas, considerando os requisitos deste post, usaremos exemplos com o gron, já mencionado e linkado acima. Se você usar, numa shell compatível, o comando gron 'https://arram.senta-la.cloud/api/v1/directory?local=true' | head, verá uma resposta como a da tela abaixo:

Note que agora, ao invés de uma linha gigante, temos uma sequência de linhas, cada uma mostrando um campo da resposta da API. Na tela acima, limitada a 10 linhas, todas as linhas se referem a um mesmo usuário e começam com json[0], como nos exemplos:
json[0].acct = "autobrain";
json[0].created_at = "2025-04-23T00:00:00.000Z";
json[0].discoverable = true;
json[0].display_name = "VISITE O DECORADO";
A listagem quanto ao primeiro usuário continua ainda por muitas outras linhas não visíveis na resposta (porque nós a cortamos com o filtro head) e, quando acabar, o próximo usuário começa em seguida, aí com o prefixo json[1], e assim por diante. Veja um exemplo das linhas da transição entre o 3º e o 4º usuários:
json[3].url = "https://arram.senta-la.cloud/@medidor";
json[3].username = "medidor";
json[4] = {};
json[4].acct = "TrendsBR";
json[4].bot = true;
Na versão atual da API, geralmente cada usuário é descrito por mais de 20 campos – ou seja, mais de 20 linhas geradas pelo gron –, e a último delas é referente ao campo username.
Um script básico para listar usuários
Este post de hoje nasceu de um script criado para apoiar a elaboração de uma lista dos usuários mais ativos da instância brasileira organica.social, que em dezembro de 2025 recebeu um grande influxo de usuários alcançados por uma bem-sucedida ação da Biana na rede Bluesky.
Tudo que precisamos para fazer uma lista dessas pode ser obtido pelo endpoint /api/v1/directory, que conhecemos acima. Como a organica.social recebeu centenas de novos usuários, será necessário buscar nela várias páginas de resposta (cada página listando 80 contas, como vimos acima).
Entre os diversos atributos da conta, que a resposta da API descreve, os que nos interessam especialmente são o login, o nome, a data de criação da conta (pois para a nossa listagem queremos só os recém-chegados) e a contagem de posts.
Adicionaremos também o campo referente à quantidade de contas que a pessoa segue, para assim ser mais fácil identificar uma parte das pessoas que criou conta apenas para "reservar o login". Os campos são os seguintes, retirados diretamente da resposta do gron sobre a minha conta pessoal:
json[0].display_name = "VISITE O DECORADO";
json[0].following_count = 1194;
json[0].indexable = true;
json[0].statuses_count = 9579;
json[0].username = "autobrain";
Há uma infinidade de maneiras de extrair esses dados usando linguagens e utilitários, e o programa a seguir, em shell e awk (com apoio do gron) é uma delas.
Ele baixa as 6 primeiras páginas de resultados, o que corresponde a até 480 contas - ou, no caso, as 480 contas locais da instância, que tiverem postado mais recentemente. Em seguida, ele faz um processamento básico de formatação das linhas (extraindo a identificação do campo e o ";" ao final da linha) e produz a saída em formato CSV básico, ou seja, com os campos separados por vírgulas, as linhas separadas por Return, e as strings entre aspas.
Segue a listagem completa:
#!/usr/bin/env bash
#
# masto_userdir.sh - exemplo de uso da API 'directory' do Mastodon
#
# Copyright (c) 2025, Augusto Campos (https://augustocampos.net/).
# Licensed under the Apache License, Version 2.0.
#
echo "DISPLAY_NAME,FOLLOWING_COUNT,INDEXABLE,STATUSES_COUNT,USERNAME"
delta=0
inst="organica.social"
url="https://$inst/api/v1/directory?local=true&limit=80&offset=$delta"
# loop de 6 leituras
for i in {1..6}; do
gron "$url" | awk '
# filtra só as linhas desses campos mencionados:
/\.(display_name|following_count|indexable|statuses_count|username) = / {
# separador será uma vírgula, exceto no último campo ("username"),
# quando será um Return:
SEP=","
if ($1 ~ /\.username/) SEP="
"
# remove a identificação do campo ("json[x].xxx"), o "=" e o ";"
$1=$2=""
sub(/;$/,"")
# gera a saída com o conteúdo do campo e o separador
printf("%s"SEP, substr($0,3))
}'
# antes de repetir o loop, aumenta o valor que será usado no "offset"
delta=$((delta + 80))
done
Explicações sobre a linguagem estão fora do escopo deste post, mas incluí comentários nos trechos do código, para orientar quem preferir implementar com estrutura similar, em sua tecnologia favorita.
A saída produzida será algo similar a essa, que é o resultado de rodar o mesmo programa, mas direcionado à minha instância:
DISPLAY_NAME,FOLLOWING_COUNT,INDEXABLE,STATUSES_COUNT,USERNAME
"Locutora de feeds",1,true,1168,"locutora"
"VISITE O DECORADO",1194,true,9581,"autobrain"
"Gugu",0,true,176,"gugu"
"Medidor de ranço",11,true,63,"medidor"
"Trends Brasil 🚀",1,true,827,"TrendsBR"
"Madame Sandra Rosa Madalena",22,true,31,"MadameSandra"
"Tags Temáticas BR",2,true,366,"TagsBR"
"Bebê Taz Ininteligível",24,true,433,"aridiculaideia"
"Sextou, galera!",1,true,6,"sextou"
Note que esse formato de saída com linhas, aspas e separação por vírgulas, conhecido como CSV (ou RFC 4180), serve para ser importado em planilhas, bancos de dados e outros sistemas, e aí usá-los para reordenar, classificar, aplicar condições, gerar gráficos e o que mais você quiser fazer com essas informações – no meu caso, serviu para gerar uma thread no Mastodon.
O programa acima é bem cru, e tem muitas oportunidades de melhoria para você se exercitar, inclusive pela adição de tratamento de erro e de ser capaz de perceber que ele está tentando ler mais usuários do que a instância tem - na versão acima, o loop de 6 leituras é feito até mesmo para instâncias com menos de 80 usuários, e aí repete várias vezes a resposta da primeira leitura.
Um detalhe importante é que, embora o protocolo do Mastodon preveja que esse endpoint da API seja acessado sem precisar de autenticação, algumas instâncias configuram (com intenções variadas e eficácia baixa) bloqueios a esse acesso não autenticado, então se você experimentar em uma instância e não receber resposta, essa é uma das razões possíveis.
Referências e documentação
Venho preferindo exemplos com operações simples, envolvendo apenas um endpoint de API por vez, e com acesso a dados públicos, porque desenvolver interfaces ou clientes para o Mastodon tem uma escala de complexidade a ser vencida, como em tudo na vida, e não há razão para começar já tentando operações difíceis, autenticadas e envolvendo múltiplas instâncias e entidades.
Lembre-se que a API do Mastodon é bem documentada, e desde já recomendo os capítulos "Getting Started with the API", "Playing with public data" e "Directory API methods" para entendimento aprofundado do que apresentei acima com bem menos detalhes.
Leia também o post anterior: t Programando bots para o Mastodon: obtendo dados via API.





