API Bluesky : comment publier depuis du code (2026)

API Bluesky : comment publier depuis du code (2026)

Créez une session, envoyez un blob, écrivez un enregistrement app.bsky.feed.post. Pas de validation d’application, pas d’écran OAuth, mais vous calculez vous-même les facettes de lien.

Publier sur Bluesky nécessite trois appels : com.atproto.server.createSession pour obtenir des jetons, com.atproto.repo.uploadBlob si vous avez des images, et com.atproto.repo.createRecord pour écrire un enregistrement app.bsky.feed.post. Il n’y a ni validation d’application ni écran de consentement OAuth à faire approuver. Le prix de cette simplicité : les liens et les mentions ne sont pas détectés pour vous, vous calculez vous-même les décalages d’octets sur l’UTF-8.

Que peut publier l’API Bluesky, et que ne peut-elle pas ?

Un enregistrement de publication est un simple document JSON. Les champs requis sont $type (app.bsky.feed.post), text et createdAt sous forme d’horodatage ISO 8601. Tout le reste est une structure optionnelle.

FonctionnalitéPrise en charge
Images par publication4 maximum
Taille d’image1 000 000 octets chacune, selon la documentation des publications
Taille totale des blobs par publication2 000 000 octets maximum
Texte alternatifObligatoire par image, avec un rapport largeur/hauteur
Publications citéesapp.bsky.embed.record
Cartes de lienapp.bsky.embed.external, avec votre propre miniature
Réponsesreply avec des références fortes root et parent
Balises de languelangs, un tableau comme ["en-US"]

Les manques à signaler d’emblée :

  • Rien n’est détecté automatiquement. Les liens, mentions et hashtags sont du texte inerte tant que vous n’attachez pas de facettes. Une URL collée sans facette n’est pas cliquable.
  • Les cartes de lien sont à votre charge. app.bsky.embed.external attend le titre, la description et la miniature. Rien ne scrute la page à votre place.
  • Les mentions nécessitent un DID résolu. Vous ne pouvez pas mettre un identifiant dans une facette ; vous devez d’abord résoudre l’identifiant en DID.
  • Les métadonnées EXIF doivent être retirées avant l’envoi des images, selon la documentation.
  • La documentation des publications indique l’absence de limite de caractères ou de graphèmes. Nous n’avons pas pu vérifier un chiffre sur cette page, nous n’en citons donc pas. Consultez l’article sur la limite de caractères Bluesky pour ce que le client applique réellement.

Remarque : les chiffres ici ont été vérifiés dans la documentation développeur de Bluesky (docs.bsky.app, qui redirige désormais vers bsky.network/docs) en septembre 2026. Les plateformes les modifient sans préavis.

Quel est le modèle d’authentification ?

C’est la section d’authentification la plus courte que vous lirez pour n’importe quelle plateforme sociale.

Il n’y a aucune inscription d’application, aucune validation d’application et aucun écran de consentement OAuth pour le chemin par mot de passe d’application. Un utilisateur crée un mot de passe d’application dans ses paramètres Bluesky et le communique à votre logiciel. Vous échangez l’identifiant plus ce mot de passe d’application contre un JWT d’accès et un JWT de rafraîchissement via com.atproto.server.createSession. Les jetons d’accès sont de courte durée ; vous les rafraîchissez avec le JWT de rafraîchissement.

Deux conséquences. Un mot de passe d’application est une donnée d’identification que votre utilisateur vous remet directement : l’absence d’écran de consentement signifie l’absence d’octroi de portée médié par la plateforme, donc l’obligation de stockage repose entièrement sur vous. Chiffrez-les, et donnez aux utilisateurs un moyen visible de se déconnecter.

Et le réseau n’appartient pas à une seule entreprise. Les comptes AT Protocol vivent sur un serveur de données personnel (PDS), et un PDS peut être auto-hébergé. Votre client s’adresse à l’hôte PDS de l’utilisateur, donc coder en dur bsky.social fonctionne aujourd’hui mais ne correspond pas au modèle du protocole. Lisez l’hôte depuis le document DID de l’utilisateur.

Comment publier concrètement ? La séquence d’appels

  1. POST /xrpc/com.atproto.server.createSession avec identifier (identifiant ou DID) et password (le mot de passe d’application). Retourne accessJwt, refreshJwt et did.
  2. Si vous avez des images : POST /xrpc/com.atproto.repo.uploadBlob par image, avec les octets bruts et le bon Content-Type. Chacune retourne une référence de blob.
  3. Calculez les facettes pour les liens ou mentions, sous forme de décalages d’octets dans l’encodage UTF-8 de text.
  4. POST /xrpc/com.atproto.repo.createRecord avec repo réglé sur votre DID, collection réglé sur app.bsky.feed.post, et l’enregistrement lui-même.
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 }],
        },
      ],
    },
  }),
});

L’arithmétique des facettes, en détail

byteStart et byteEnd sont des décalages dans les octets UTF-8 du texte, pas dans les indices de chaîne JavaScript et pas dans les caractères.

"café".length vaut 4 en JavaScript, mais l’encodage UTF-8 fait 5 octets. Tout emoji placé avant votre lien décale le décalage de 4 octets tout en ne déplaçant l’indice de chaîne que de 2. Une erreur ici et le lien s’affiche comme du texte cassé ou met en surbrillance la mauvaise portion, sans aucune erreur du serveur : l’enregistrement est valide, il pointe simplement vers les mauvais octets.

Encodez la chaîne une seule fois, trouvez les décalages dans le tableau d’octets, et ne mélangez jamais les deux systèmes de coordonnées. La fonctionnalité de facette de lien utilise uri, pas url.

Quelles sont les limites de débit de Bluesky ?

Les écritures sont mesurées avec un système de points par compte : CREATE coûte 3 points, UPDATE 2, DELETE 1, contre un budget de 5 000 points par heure et 35 000 par jour. Cela représente environ 1 666 créations par heure et 11 666 par jour.

LimiteValeur
Points d’écriture5 000/heure, 35 000/jour
Créations (calculé)~1 666/heure, ~11 666/jour
Requêtes PDS globales3 000 par tranche de 5 minutes, par IP
createSession30 par tranche de 5 minutes, 300 par jour, par compte
Plafond d’envoi de blob52 428 800 octets (50 Mo)

La limite createSession est celle qui piège les planificateurs. 30 par tranche de 5 minutes et par compte signifie que vous mettez la session en cache et la rafraîchissez, plutôt que de vous connecter à chaque publication. Un service qui crée une session à chaque tâche se limitera lui-même bien avant d’atteindre une quelconque limite de publication.

Notez les deux chiffres de blob : le PDS accepte des blobs jusqu’à 50 Mo, tandis que la documentation des publications indique une limite de 1 000 000 octets par image et de 2 000 000 octets au total pour les images d’une publication. Dimensionnez selon le plus petit des deux.

Remarque : les chiffres ici ont été vérifiés dans la documentation des limites de débit de Bluesky en septembre 2026. Les plateformes les modifient sans préavis.

Qu’est-ce qui vous coûtera vraiment trois semaines ?

Bluesky est réellement la plateforme majeure la moins coûteuse à intégrer, mais « peu coûteux » ne veut pas dire « gratuit ».

Le calcul des facettes sur du texte réel. Détecter les URL, la ponctuation en fin de mot, les identifiants et les hashtags, convertir chaque correspondance en décalages d’octets UTF-8, et garder cela correct quand un utilisateur modifie le texte. C’est là que vivent les bugs.

La gestion du budget de blobs. 2 Mo au total sur jusqu’à 4 images signifie redimensionner et réencoder côté serveur, retirer les métadonnées EXIF, et décider quoi faire quand les photos de l’utilisateur ne tiennent pas.

Les cartes de lien. Pour que les publications ressemblent à de vraies publications, vous récupérez la page cible, en extrayez le titre, la description et l’image, envoyez cette image comme blob, et construisez l’intégration external. C’est un petit robot d’exploration avec des délais d’expiration et une gestion des échecs.

La mise en cache de session, à cause du plafond de 30 par tranche de 5 minutes, et la résolution de l’hôte PDS, car présumer bsky.social casse les comptes auto-hébergés d’une façon que vous ne pouvez pas corriger de votre côté.

Si vous mettez en miroir du contenu existant, republier de X vers Bluesky et republier de Threads vers Bluesky couvrent tous deux les différences de longueur de texte et de média à concilier avant même que l’enregistrement soit valide.

En bref

  • Trois appels : createSession, uploadBlob pour les images, createRecord avec un enregistrement app.bsky.feed.post.
  • Pas de validation d’application, pas d’écran OAuth. Des mots de passe d’application, remis par l’utilisateur.
  • Les liens et mentions nécessitent des facettes avec des décalages d’octets en UTF-8. Rien n’est détecté automatiquement.
  • 4 images par publication, 1 000 000 octets chacune, 2 000 000 octets au total, texte alternatif obligatoire.
  • Les écritures coûtent 3 points par création, contre 5 000/heure et 35 000/jour. createSession est limité à 30 par tranche de 5 minutes.
  • Les comptes peuvent vivre sur un PDS auto-hébergé. Ne codez pas l’hôte en dur.

Publier sur Bluesky aux côtés de 14 autres réseaux

Bluesky est le cas facile. Le même produit a généralement aussi besoin de X (OAuth 2.0 PKCE, envoi de média fragmenté, facturation par requête), de Threads (validation d’application Meta, conteneur puis publication, rafraîchissement de jeton sur 60 jours) et de TikTok (un audit de l’API de publication de contenu avant de pouvoir publier quoi que ce soit). Chacun a son propre modèle d’authentification, son pipeline média et son modèle d’échec asynchrone.

BulkPublish est une seule API REST couvrant 15 plateformes, Bluesky inclus, avec le calcul des facettes, le redimensionnement des blobs et le rafraîchissement de session gérés côté serveur. La documentation développeur et la référence de l’API REST contiennent les points de terminaison, et programmer des publications Bluesky couvre cela sans code.

À lire aussi