API do Bluesky: Como Publicar a Partir de Código (2026)

API do Bluesky: Como Publicar a Partir de Código (2026)

Crie uma sessão, envie um blob, escreva um registro app.bsky.feed.post. Sem revisão de aplicativo, sem tela de OAuth, mas você calcula os facets de link você mesmo.

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.

RecursoSuporte
Imagens por publicação4 no máximo
Tamanho da imagem1.000.000 bytes cada, conforme a documentação de publicações
Tamanho total de blob por publicação2.000.000 bytes no máximo
Texto alternativoobrigatório por imagem, com proporção de aspecto
Publicações de citaçãoapp.bsky.embed.record
Cartões de linkapp.bsky.embed.external, com sua própria miniatura
Respostasreply com referências fortes root e parent
Marcações de idiomalangs, 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.external pede 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

  1. POST /xrpc/com.atproto.server.createSession com identifier (identificador ou DID) e password (a senha de aplicativo). Retorna accessJwt, refreshJwt e did.
  2. Se você tiver imagens: POST /xrpc/com.atproto.repo.uploadBlob para cada imagem, com os bytes brutos e o Content-Type correto. Cada chamada retorna uma referência de blob.
  3. Calcule os facets para links ou menções, como deslocamentos de bytes na codificação UTF-8 de text.
  4. POST /xrpc/com.atproto.repo.createRecord com repo definido como o seu DID, collection definido como app.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.

LimiteValor
Pontos de gravação5.000/hora, 35.000/dia
Criações (derivado)~1.666/hora, ~11.666/dia
Requisições totais ao PDS3.000 a cada 5 minutos, por IP
createSession30 a cada 5 minutos, 300 por dia, por conta
Teto de upload de blob52.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, uploadBlob para imagens, createRecord com um registro app.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.

Relacionados