Contents
開発者アカウント取得と認証基礎
Bluesky の API(AT Protocol)を本格的に利用するには、まず公式の開発者コンソールで クライアント ID と シークレット を取得し、OAuth2 + DID フローでアクセストークンを入手する必要があります。ここでは、アカウント作成から安全なキー管理、認証フロー全体の流れを順を追って解説します。
Bluesky 開発者アカウントの作成手順
開発者コンソールは Bluesky の公式サイト(https://bsky.social/about)からアクセスできます。以下の手順でプロジェクトと認証情報を取得してください。
- サインアップ – 既存の Bluesky アカウントでログインするか、メールアドレスで新規作成します。
- Developer Console へ遷移 – ユーザーメニューの「Developer Console」を選択し、利用規約に同意します。
- プロジェクト登録 – 「Create Project」ボタンを押し、アプリ名・概要・リダイレクト URI を入力してプロジェクトを作成します。
作成後の画面で Client ID(API Key) と Client Secret が表示されます。これらは 機密情報 ですので、次節で解説する安全な保管方法を必ず実践してください。
API キー・クライアントシークレットの安全な管理
認証情報が漏洩すると不正利用やサービス停止リスクが高まります。以下は推奨される管理手段です。
- 環境変数 に格納し、コードベースに直接書かない
bash
export BSKY_CLIENT_ID="your-client-id"
export BSKY_CLIENT_SECRET="your-client-secret" - シークレットマネージャー(AWS Secrets Manager、Google Secret Manager、HashiCorp Vault 等)を利用し、実行時に取得する
.gitignoreに*.envやキーが含まれるファイルを必ず追加し、リポジトリへコミットしない
OAuth2 + DID 認証フローの概要
Bluesky は OAuth 2.0 Authorization Code Grant と Decentralized Identifier(DID) を組み合わせた認証を提供します。以下は実装上必要な主要ステップです。
- 認可リクエスト – ユーザーを
https://bsky.social/oauth/authorizeにリダイレクトし、client_id,redirect_uri,scope=atprotoなどのパラメータを付与します。 - 認可コード受領 – 許可後に
redirect_uri?code=xxxxが返ります。サーバー側でこのコードを取得してください。 - トークン交換(バックエンド)
bash
POST https://bsky.social/xrpc/com.atproto.server.createSession
Content-Type: application/json
{
"client_id": "$BSKY_CLIENT_ID",
"client_secret": "$BSKY_CLIENT_SECRET",
"code": "<認可コード>"
}
成功すると accessJwt(アクセストークン)と refreshJwt が返ります。公式ドキュメントは AT Protocol – Session API を参照してください。
- DID の取得 – アクセストークンで
com.atproto.identity.resolveHandleエンドポイントを呼び出すと、ユーザーの DID(例:did:plc:abcd1234...)が取得できます。
ポイント:アクセストークンは数分から数時間で期限切れになるため、
refreshJwtを用いた自動更新ロジックを必ず実装しましょう。
API エンドポイントと公式 SDK のセットアップ
Bluesky のすべての機能は https://bsky.social/xrpc/ 配下にある 名前空間ベース のエンドポイントで提供されます。ここではエンドポイント構造・バージョニング方針、主要言語向け公式 SDK のインストール方法と基本サンプルを紹介します。
エンドポイント構造とバージョニングポリシー
AT Protocol では 名前空間 がバージョン管理の中心です。エンドポイントは次のように分類されます(※2026‑05‑21 時点の公式情報):
| 名前空間 | 主な機能 | エンドポイント例 |
|---|---|---|
com.atproto.server |
認証・サーバー情報 | /xrpc/com.atproto.server.createSession |
com.atproto.repo |
レコード操作(投稿・プロフィール) | /xrpc/com.atproto.repo.createRecord |
app.bsky.feed |
タイムライン・フィード取得 | /xrpc/app.bsky.feed.getTimeline |
com.atproto.sync |
Firehose (SSE) などリアルタイム購読 | /xrpc/com.atproto.sync.subscribeRepos |
- バージョニングは名前空間にサフィックスを付与する形で行われます(例:
com.atproto.repo.v2.createRecord)。 - 最新の名前空間は
GET /xrpc/com.atproto.server.describeServerで取得できる Discovery API を利用すると安全です。
各言語 SDK のインストールと基本使用例
JavaScript(Node.js)SDK
|
1 2 |
npm install @atproto/api |
概要:公式の
@atproto/apiパッケージは TypeScript 定義付きで、認証・レコード操作をシンプルに呼び出せます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { AtpAgent } from '@atproto/api' const agent = new AtpAgent({ service: 'https://bsky.social' }) await agent.login({ identifier: process.env.BSKY_HANDLE, // 例: alice.bsky.social password: process.env.BSKY_PASSWORD, }) // テキスト投稿(1 行だけ) await agent.com.atproto.repo.createRecord({ repo: agent.session.did, collection: 'app.bsky.feed.post', record: { $type: 'app.bsky.feed.post', text: 'Hello Bluesky from JavaScript!', createdAt: new Date().toISOString(), }, }) |
Python SDK
|
1 2 |
pip install atproto |
概要:
atprotoパッケージはClientクラスに認証・トークン管理を委譲し、シンプルな API 呼び出しが可能です。
|
1 2 3 4 5 6 7 8 9 10 |
from atproto import Client client = Client(base_url='https://bsky.social') client.login(handle='alice.bsky.social', password='YOUR_PASSWORD') # タイムライン取得(最新10件) timeline = client.app.bsky.feed.get_timeline(limit=10) for post in timeline['feed']: print(post['post']['record']['text']) |
Swift SDK(iOS / macOS)
|
1 2 3 |
// Package.swift に追加 .package(url: "https://github.com/bluesky-social/atproto-swift", from: "1.0.0") |
概要:
ATProtoClientは非同期 API と SSE のサポートを提供し、iOS アプリでも同等の操作感で利用できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import ATProto let client = ATProtoClient(baseURL: URL(string: "https://bsky.social")!) client.login(handle: "alice.bsky.social", password: "YOUR_PASSWORD") { result in switch result { case .success(let session): // 画像を Blob としてアップロード let imageData = try! Data(contentsOf: Bundle.main.url(forResource: "photo", withExtension: "jpg")!) client.uploadBlob(data: imageData, mimeType: "image/jpeg") { blobResult in switch blobResult { case .success(let blob): // 画像付き投稿作成 let post = AppBskyFeedPost( text: "Hello from Swift!", createdAt: Date(), embed: .images([blob]) ) client.createRecord(repo: session.did, collection: "app.bsky.feed.post", record: post) { _ in } case .failure(let error): print("Blob upload failed:", error) } } case .failure(let error): print("Login failed:", error) } } |
投稿機能実装ガイド
Bluesky への投稿は レコード作成 API と メディア(Blob)アップロード API の組み合わせで行います。ここではテキストだけのシンプル投稿から、画像・動画添付、アクセシビリティ対応まで段階的に実装する方法を示します。
テキストのみの投稿例
app.bsky.feed.post コレクションは AT Protocol が定義した標準スキーマです。必須フィールドは text, createdAt, $type のみです。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
POST https://bsky.social/xrpc/com.atproto.repo.createRecord Authorization: Bearer <accessJwt> Content-Type: application/json { "repo": "did:plc:xxxxxxxxxxxx", "collection": "app.bsky.feed.post", "record": { "$type": "app.bsky.feed.post", "text": "AT Protocol 初投稿です! #Bluesky", "createdAt": "2026-05-21T10:30:00Z" } } |
- 文字数上限は 300 文字(マルチバイト含む)で、超過時は
400 Bad Requestが返ります。 createdAtは必ず ISO‑8601 UTC 形式で送信してください。サーバー側は自動補正しません。
メディアアップロード手順とサイズ上限の根拠
1. Blob アップロード(画像・動画)
公式ドキュメント(Blob API)によれば、画像は最大 5 MB、動画は最大 50 MB が上限です。アップロード時に Content-Type ヘッダーで MIME を指定し、バイナリデータを POST します。
|
1 2 3 4 5 6 |
POST https://bsky.social/xrpc/com.atproto.repo.uploadBlob Authorization: Bearer <accessJwt> Content-Type: image/jpeg <binary data> |
- 成功レスポンス例
json
{
"blob": {
"ref": { "$link": "bafkreihdwd..." },
"mimeType": "image/jpeg",
"size": 123456
}
}
2. Blob をレコードに埋め込む
画像は app.bsky.embed.images、動画は app.bsky.embed.video に格納します。以下は画像付き投稿のサンプルです。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ "$type": "app.bsky.feed.post", "text": "風景写真を添付しました。", "createdAt": "...", "embed": { "$type": "app.bsky.embed.images", "images": [ { "image": { "ref": { "$link": "bafkreihdwd..." }, "mimeType": "image/jpeg", "size": 123456 }, "alt": "山と湖の風景" } ] } } |
3. ベストプラクティス
| 項目 | 推奨内容 |
|---|---|
| 事前圧縮 | JPEG は 80 % 前後、動画は H.264/AVC + CRF 23 程度でエンコードし、サイズを削減 |
| 画像枚数上限 | app.bsky.embed.images の配列は最大 4 枚まで |
| アクセシビリティ | 必ず alt テキストを設定。モデレーションの自動判定精度が向上します |
| サイズチェック | クライアント側で 5 MB / 50 MB を超える場合はアップロード前にエラー返却し、ユーザーへ通知 |
カスタムエンベッド例(音声添付)
標準スキーマ外のメディアを扱う場合は Lexicon で独自コレクションを定義し、app.bsky.embed.recordWithMedia の embed 部分に組み込むことができます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "$type": "app.custom.audioPost", "text": "新しいポッドキャストエピソードです。", "createdAt": "...", "embed": { "$type": "app.bsky.embed.recordWithMedia", "record": { /* 任意の別レコードへの参照 */ }, "media": { "$type": "app.custom.audio", "audio": { "ref": "<blob.ref>", "mimeType": "audio/mpeg", "size": 987654 } } } } |
タイムライン取得とリアルタイムストリーミング活用
ユーザー体験の中心は「最新投稿」をどう取得し、どのように表示させるかです。Bluesky は GET feed 系エンドポイントと Firehose(SSE) の二つの手段を提供しています。
GET Timeline API の使い方
app.bsky.feed.getTimeline はユーザーのホームタイムラインやカスタムアルゴリズムに基づくフィード取得に利用します。公式ドキュメントは Feed API を参照してください。
|
1 2 3 |
GET https://bsky.social/xrpc/app.bsky.feed.getTimeline?limit=20&algorithm=reverse-chronological Authorization: Bearer <accessJwt> |
| パラメータ | 説明 |
|---|---|
limit |
取得件数(最大 100) |
cursor |
ページング用トークン。前回のレスポンスに含まれる cursor を指定 |
algorithm |
ソート方式(デフォルトは reverse-chronological) |
- ページング例:最初のリクエストで取得した
cursorを次回GET /getTimeline?cursor=xxxxに付与すれば、無限スクロールが実装できます。
メンション検索クエリ例
ハンドル名で絞り込む場合は app.bsky.feed.searchPosts の term パラメータを利用します。
|
1 2 3 |
GET https://bsky.social/xrpc/app.bsky.feed.searchPosts?term=%40alice.bsky.social&limit=10 Authorization: Bearer <accessJwt> |
%40は URL エンコードされた「@」です。- 取得結果は
feed配列内にpost.record.text等の標準構造で返りますので、既存パーサーを流用可能です。
Firehose(SSE)によるリアルタイムストリーミング
接続方法と基本ハンドリング
Firehose は Server‑Sent Events で全公開レコードの変更をリアルタイムに配信します。開発・テスト用途として便利です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const es = new EventSource( 'https://bsky.social/xrpc/com.atproto.sync.subscribeRepos?cursor=0' ) es.onmessage = event => { const data = JSON.parse(event.data) if (!data.ops) return data.ops.forEach(op => { // 新規投稿だけを抽出 if (op.action === 'create' && op.collection === 'app.bsky.feed.post') { console.log('New post:', op.record.text) } }) } |
- フィルタリングは
filterクエリパラメータで DID やコレクションを絞り込めます(例:?filter=did:plc:abcd1234)。詳細は Sync API を参照。
Rate Limit の実際の数値と Retry‑After ヘッダー
- GET 系エンドポイント:公式に記載されているレートリミットは 1 分間に 60 リクエスト(IP・アクセストークン共通)です。超過すると
429 Too Many Requestsが返り、レスポンスヘッダーにRetry-After: <秒数>が付与されます。 - SSE 接続自体はレートリミット対象外ですが、Firehose から取得した URI を再度 API 呼び出しする場合は上記の制限が適用される点に注意してください。
エラーハンドリング・運用、カスタムスキーマとプライバシー考慮
実運用では エラー対応 と レートリミット回避策 が不可欠です。さらに AT Protocol の柔軟な Lexicon により独自スキーマを追加できるため、その設計・運用方法も合わせて解説します。
エラーコードと推奨対策
| HTTP ステータス | 主な原因 | 推奨対応 |
|---|---|---|
| 400 Bad Request | パラメータ欠落、フォーマットエラー | 入力バリデーションを事前に実施。公式スキーマと突き合わせる |
| 401 Unauthorized | アクセストークン期限切れ・無効 | refreshJwt で再取得、もしくはユーザーへ再ログインを促す |
| 403 Forbidden | DID がブロック対象、モデレーション違反 | エラーメッセージを UI に表示し、コンテンツ修正フローを用意 |
| 404 Not Found | リソース未存在(例:レコード削除済み) | キャッシュのクリアや再取得ロジックでフォールバック |
| 429 Too Many Requests | レートリミット超過 | 指数バックオフ(1 s → 2 s → 4 s …)と Retry-After の遵守 |
| 500 / 502 | サーバ障害・一時的なネットワークエラー | 5 秒以上の遅延でリトライし、連続失敗時はアラートを上げる |
バックオフ実装例(JavaScript)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
async function fetchWithBackoff(url, opts = {}, attempts = 0) { try { const res = await fetch(url, opts) if (!res.ok && res.status === 429) throw new Error('rate-limit') return await res.json() } catch (e) { if (attempts >= 5) throw e const delay = Math.pow(2, attempts) * 1000 // 1,2,4,8,16 秒 await new Promise(r => setTimeout(r, delay)) return fetchWithBackoff(url, opts, attempts + 1) } } |
サンドボックス環境での安全なテスト方法
Bluesky は本番と同一エンドポイントを使用しますが、テスト用 DID を発行できるサンドボックスモードがあります。
- 開発者コンソールの「Test DIDs」タブから
Create Test DIDボタンをクリック - 発行された
did:plc:test-xxxxと テストトークン(accessJwt)を環境変数に設定 - 本番コードと同一ロジックでリクエストすれば、実際のユーザーアカウントやデータに影響を与えずに動作確認が可能です
サンドボックスは レートリミットが緩和(1 分間に 120 リクエスト)されているため、大量テストにも適しています。
カスタム Lexicon スキーマの作成と登録例
AT Protocol の拡張は Lexicon JSON で定義します。以下は「投票」機能用カスタムコレクション app.custom.vote の最小実装例です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
{ "$schema": "https://atproto.com/lexicons/v1", "defs": { "app.custom.vote": { "type": "record", "$id": "app.custom.vote", "description": "シンプルな投票レコード", "properties": { "question": { "type": "string" }, "options": { "type": "array", "items": { "type": "string" } }, "votes": { "type": "map", "values": { "type": "integer" } } }, "required": ["question", "options"] } } } |
登録手順
- 上記 JSON を
vote.lexicon.jsonとして保存 com.atproto.repo.createRecordのcollectionに"app.custom.vote"、recordに Lexicon 内容を指定して POST- 作成後は通常の
getRecord,updateRecordAPI で取得・更新が可能
カスタムスキーマは 公開されるため、個人情報や機密データは絶対に含めないようにしてください。
プライバシー・コンテンツモデレーションへの対応
Bluesky の公式ガイドライン(2026‑05版)では以下の点が特に重要とされています。
- ユーザー同意:個人情報や位置情報を含む投稿は、事前に取得した同意を
app.bsky.label.defs#selfLabelに記録し、クライアント側で表示制御できるようにする。 - コンテンツ警告ラベル:暴力・成人向け表現は
selfLabelフィールドに該当ラベルを付与し、取得時のfilter=excludeLabelsパラメータで除外可能にする。 - 削除リクエスト:ユーザーが投稿削除を要求したら
com.atproto.repo.deleteRecordを呼び出し、サーバー側の保持ポリシーに従い即時消去する。
実装例(Python) – 画像投稿前に自動ラベリング
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
from atproto import Client import google.cloud.vision as vision # 例: Google Vision API client = Client(base_url='https://bsky.social') client.login(handle='alice.bsky.social', password='PWD') def label_image(data): img = vision.Image(content=data) resp = vision_client.safe_search_detection(image=img) safe = resp.safe_search_annotation # NSFW 判定例 if safe.adult == vision.Likelihood.VERY_LIKELY: return ["porn"] return [] # 画像読み込み → ラベリング → Blob アップロード → 投稿作成 image_path = "nsfw.jpg" img_data = open(image_path, "rb").read() labels = label_image(img_data) blob = client.upload_blob(data=img_data, mime_type="image/jpeg") post = { "$type": "app.bsky.feed.post", "text": "テスト投稿(自動ラベル付与)", "createdAt": "2026-05-21T12:00:00Z", "embed": { "$type": "app.bsky.embed.images", "images": [{ "image": {"ref": blob["blob"]["ref"], "mimeType": "image/jpeg", "size": len(img_data)}, "alt": "自動ラベル付き画像" }] }, "labels": {"selfLabels": labels} } client.com.atproto.repo.create_record( repo=client.session.did, collection="app.bsky.feed.post", record=post ) |
まとめ
- 開発者コンソールで取得した Client ID と Secret は環境変数やシークレットマネージャーで安全に保管。
- 認証は OAuth2 + DID フローで行い、
refreshJwtによる自動更新を必ず実装すること。 - エンドポイントは
https://bsky.social/xrpc/配下の名前空間ベース。最新バージョンは Discovery API で取得可能。 - 公式 SDK(JavaScript, Python, Swift) を利用すれば、数行コードで認証・投稿・タイムライン取得が実現できる。
- メディアアップロードは Blob API を経由し、画像 5 MB / 動画 50 MB の上限(公式ドキュメント参照)を遵守。
altテキストやサイズ圧縮でアクセシビリティと帯域効率を向上させる。 - Firehose (SSE) によるリアルタイム購読は、イベント駆動型アプリに最適であり、レートリミットは SSE 接続自体にはかからない点に留意。
- エラーはステータスコード別にハンドリングし、429 は指数バックオフと
Retry-Afterヘッダーの遵守が必須。 - サンドボックス DID を活用すれば本番データを汚染せずに大量テストが可能。
- カスタムスキーマは Lexicon で自由に定義できるが、公開情報になるため機密データは除外すること。
- プライバシー・モデレーションはラベル付与とユーザー同意取得を徹底し、公式ポリシーに準拠した運用体制を整える。
これらの手順とベストプラクティスに沿って実装すれば、Bluesky 上で安全かつスケーラブルなマイクロブログ・SNS 連携アプリを迅速に構築できます。まずは サンドボックス環境 で動作確認し、その後本番へ移行する流れをおすすめします。