Publicar no Bluesky exige três chamadas: com.atproto.server.createSession para obter tokens, com.atproto.repo.uploadBlob se você tiver imagens, e com.atproto.repo.createRecord para gravar um registro app.bsky.feed.post. Não há revisão de aplicativo nem tela de consentimento OAuth para aprovar. O custo dessa simplicidade é que links e menções não são detectados automaticamente: você mesmo calcula os deslocamentos de bytes sobre o UTF-8.
O que a API do Bluesky consegue publicar, e o que ela não consegue?
Um registro de publicação é um documento JSON simples. Os campos obrigatórios são $type (app.bsky.feed.post), text e createdAt como um timestamp ISO 8601. Todo o resto é estrutura opcional.
| Recurso | Suporte |
|---|---|
| Imagens por publicação | 4 no máximo |
| Tamanho da imagem | 1.000.000 bytes cada, conforme a documentação de publicações |
| Tamanho total de blob por publicação | 2.000.000 bytes no máximo |
| Texto alternativo | obrigatório por imagem, com proporção de aspecto |
| Publicações de citação | app.bsky.embed.record |
| Cartões de link | app.bsky.embed.external, com sua própria miniatura |
| Respostas | reply com referências fortes root e parent |
| Marcações de idioma | langs, um array como ["en-US"] |
As lacunas a citar de antemão:
- Nada é detectado automaticamente. Links, menções e hashtags são texto inerte a menos que você anexe facets. Uma URL colada sem um facet não fica clicável.
- Cartões de link são responsabilidade sua. O
app.bsky.embed.externalpede título, descrição e miniatura. Nada extrai isso da página para você. - Menções precisam de um DID resolvido. Você não pode colocar um identificador em um facet; primeiro resolve o identificador para um DID.
- As imagens precisam ter os dados EXIF removidos antes do upload, conforme a documentação.
- A documentação de publicações não indica um limite de caracteres ou grafemas. Não conseguimos confirmar um valor nessa página, então não estamos citando um número. Veja o artigo sobre o limite de caracteres do Bluesky para o que o cliente impõe.
Observação: os números aqui foram verificados na documentação para desenvolvedores do Bluesky (docs.bsky.app, que agora redireciona para bsky.network/docs) em setembro de 2026. As plataformas mudam esses valores sem aviso prévio.
Como funciona o modelo de autenticação?
Esta é a seção de autenticação mais curta que você vai ler sobre qualquer rede social.
Não há registro de aplicativo, revisão de aplicativo nem tela de consentimento OAuth para o caminho de senha de aplicativo. O usuário cria uma senha de aplicativo nas configurações do Bluesky e a entrega ao seu software. Você troca o identificador mais essa senha de aplicativo por um JWT de acesso e um JWT de atualização em com.atproto.server.createSession. Os tokens de acesso têm vida curta; você os renova com o JWT de atualização.
Duas consequências. Uma senha de aplicativo é uma credencial que o seu usuário entrega diretamente a você: sem tela de consentimento, não há concessão de escopo mediada pela plataforma, então a obrigação de armazenamento fica inteiramente com você. Criptografe-as e dê aos usuários uma forma visível de desconectar.
E a rede não pertence a uma única empresa. As contas do AT Protocol vivem em um Servidor de Dados Pessoais (PDS), e um PDS pode ser auto-hospedado. Seu cliente conversa com o host do PDS do usuário, então fixar bsky.social funciona hoje, mas não é o modelo do protocolo. Leia o host a partir do documento DID do usuário.
Como você publica de fato? A sequência de chamadas
POST /xrpc/com.atproto.server.createSessioncomidentifier(identificador ou DID) epassword(a senha de aplicativo). RetornaaccessJwt,refreshJwtedid.- Se você tiver imagens:
POST /xrpc/com.atproto.repo.uploadBlobpara cada imagem, com os bytes brutos e oContent-Typecorreto. Cada chamada retorna uma referência de blob. - Calcule os facets para links ou menções, como deslocamentos de bytes na codificação UTF-8 de
text. POST /xrpc/com.atproto.repo.createRecordcomrepodefinido como o seu DID,collectiondefinido comoapp.bsky.feed.post, e o registro em si.
const base = 'https://bsky.social/xrpc';
const auth = await fetch(`${base}/com.atproto.server.createSession`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ identifier: handle, password: appPassword }),
}).then((r) => r.json());
const text = 'Notes on the release: https://example.com/changelog';
const url = 'https://example.com/changelog';
const enc = new TextEncoder();
const byteStart = enc.encode(text.slice(0, text.indexOf(url))).length;
await fetch(`${base}/com.atproto.repo.createRecord`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${auth.accessJwt}`,
},
body: JSON.stringify({
repo: auth.did,
collection: 'app.bsky.feed.post',
record: {
$type: 'app.bsky.feed.post',
text,
createdAt: new Date().toISOString(),
facets: [
{
index: { byteStart, byteEnd: byteStart + enc.encode(url).length },
features: [{ $type: 'app.bsky.richtext.facet#link', uri: url }],
},
],
},
}),
});
A aritmética dos facets, explicada
byteStart e byteEnd são deslocamentos nos bytes UTF-8 do texto, não em índices de string do JavaScript e não em caracteres.
"café".length é 4 em JavaScript, mas a codificação UTF-8 tem 5 bytes. Qualquer emoji antes do seu link desloca o offset em 4 bytes enquanto move o índice da string em 2. Errar isso faz o link renderizar como texto quebrado ou destacar o trecho errado, sem nenhum erro do servidor: o registro é válido, ele só aponta para os bytes errados.
Codifique a string uma vez, encontre os offsets no array de bytes e nunca misture os dois sistemas de coordenadas. O recurso de facet de link usa uri, não url.
Quais são os limites de taxa do Bluesky?
As gravações são medidas por um sistema de pontos por conta: CREATE custa 3 pontos, UPDATE 2, DELETE 1, contra um orçamento de 5.000 pontos por hora e 35.000 por dia. Isso equivale a aproximadamente 1.666 criações por hora e 11.666 por dia.
| Limite | Valor |
|---|---|
| Pontos de gravação | 5.000/hora, 35.000/dia |
| Criações (derivado) | ~1.666/hora, ~11.666/dia |
| Requisições totais ao PDS | 3.000 a cada 5 minutos, por IP |
createSession | 30 a cada 5 minutos, 300 por dia, por conta |
| Teto de upload de blob | 52.428.800 bytes (50 MB) |
O limite do createSession é o que costuma pegar agendadores de desprevenido. 30 a cada 5 minutos por conta significa que você deve armazenar em cache a sessão e renová-la, em vez de fazer login a cada publicação. Um worker que cria uma sessão a cada tarefa vai se autolimitar bem antes de atingir qualquer limite de publicação.
Note os dois valores de blob: o PDS aceita blobs de até 50 MB, enquanto a documentação de publicações estabelece um limite de 1.000.000 bytes por imagem e 2.000.000 bytes no total para imagens de publicação. Dimensione pelo menor dos dois.
Observação: os números aqui foram verificados na documentação de limites de taxa para desenvolvedores do Bluesky em setembro de 2026. As plataformas mudam esses valores sem aviso prévio.
O que realmente vai custar três semanas?
O Bluesky é genuinamente o mais barato das grandes plataformas para integrar, mas “barato” não é “grátis”.
Cálculo de facets para texto real. Detectar URLs, pontuação no final, identificadores e hashtags, converter cada correspondência em deslocamentos de bytes UTF-8 e manter isso correto quando o usuário edita o texto. É aí que vivem os bugs.
Orçamento de blob. 2 MB no total em até 4 imagens significa redimensionar e recodificar no servidor, remover EXIF e decidir o que fazer quando as fotos do usuário não couberem.
Cartões de link. Para fazer publicações parecerem publicações, você busca a página de destino, extrai título, descrição e imagem, faz upload dessa imagem como um blob e monta o embed external. Isso é um pequeno rastreador com timeouts e tratamento de falhas.
Cache de sessão, por causa do teto de 30 a cada 5 minutos, e resolução do host do PDS, porque assumir bsky.social quebra contas auto-hospedadas de um jeito que você não consegue consertar do seu lado.
Se você está espelhando conteúdo, fazer cross-posting do X para o Bluesky e fazer cross-posting do Threads para o Bluesky cobrem as diferenças de tamanho de texto e mídia que você precisa resolver antes mesmo de o registro ser válido.
Resumo rápido
- Três chamadas:
createSession,uploadBlobpara imagens,createRecordcom um registroapp.bsky.feed.post. - Sem revisão de aplicativo, sem tela de OAuth. Senhas de aplicativo, entregues pelo usuário.
- Links e menções precisam de facets com deslocamentos de bytes sobre UTF-8. Nada é detectado automaticamente.
- 4 imagens por publicação, 1.000.000 bytes cada, 2.000.000 bytes no total, texto alternativo obrigatório.
- As gravações custam 3 pontos por criação, contra 5.000/hora e 35.000/dia. O
createSessioné 30 a cada 5 minutos. - As contas podem viver em um PDS auto-hospedado. Não fixe o host.
Publicando no Bluesky junto com outras 14 redes
O Bluesky é o fácil. O mesmo produto geralmente também precisa do X (OAuth 2.0 PKCE, upload de mídia em partes, cobrança por requisição), do Threads (Revisão de Aplicativo da Meta, criar contêiner e depois publicar, renovação de token a cada 60 dias) e do TikTok (uma auditoria da Content Posting API antes mesmo de conseguir publicar). Cada um tem seu próprio modelo de autenticação, pipeline de mídia e modelo de falha assíncrona.
O BulkPublish é uma única API REST para 15 plataformas, incluindo o Bluesky, com cálculo de facets, redimensionamento de blob e renovação de sessão tratados no servidor. A documentação para desenvolvedores e a referência da API REST trazem os endpoints, e agendar publicações no Bluesky cobre isso sem código.