概要API の概要

概要

API の概要

Sofia API v1 の全エンドポイントとそのパラメータ、スコープ、例。ご自身のキーで各エンドポイントを試せるプレイグラウンド付き。

ベース URL https://api.sofia-post.com/api · OpenAPI ドキュメント

Sofia API を使うと、コードから Sofia のアカウントにアクセスできます。Workspace の店舗、その接続済みネットワーク、メディアライブラリ、投稿、統計です。HTTPS の REST API で、リクエストとレスポンスは JSON です。次のことができます。

  • 公開する:今すぐ、日時を指定して、または下書きとして、1 つまたは複数のネットワークに同時に公開します。
  • 公開前に確認する:ドライランで、何も書き込まずに、各ネットワークが投稿を受け付けるかどうかが分かります。
  • メディアを管理する:ファイルをアップロードするか、URL から店舗のメディアライブラリにインポートします。
  • 投稿を追跡する:ネットワークごとの状態、パーマリンク、失敗の理由を取得できます。署名つき Webhook で通知を受けることもできます。
  • 統計を取得する:接続済みアカウントごとに、Sofia が収集した最新の数値を取得します。
  • ネットワークを接続する:ホスト型のリンクを作成し、オーナーが足りないネットワークを接続できるようにします。

SDK と連携

  • `sofia` CLI:npm で sofia-post として公開されている CLI で、ターミナルから Sofia を操作します。
  • MCP サーバー:Sofia を AI エージェントにつなぎます。
  • Zapier:Sofia の連携は、個人キーで接続します。トリガーは「Post Published」と「Post Failed」、アクションは「Create Post」(公開、予約、下書き保存)、検索は「Find Post」です。Zapier の公開ディレクトリには、まだ申請していません。

認証

すべての呼び出しで、Authorization ヘッダーに Bearer トークンを付けます。Sofia は呼び出しのたびに、トークンに紐づく権限を再確認します。Workspace から外されたメンバーや、取り消された許可は、次のリクエストからトークンが無効になります。

認証の方法は 2 つあります。

方法用途トークン
個人キー自分のアカウントの自動化、スクリプト、エージェントsofia_pk_live_…、有効期限まで有効
OAuth、認可コード + PKCEユーザーが選んだ Workspace で、ユーザーのために動くプロダクトsofia_live_at_…、1 時間、更新可能
OAuth、クライアントクレデンシャル自分がオーナーの Workspace を自動化する、自社のサーバーsofia_live_at_…、1 時間

API キー

自分の Workspace を自動化するなら、個人キーで十分です。Sofia アプリで設定 › API を開き、個人アクセスタブでキーを作成します。名前、有効期限、権限を選びます。キーは一度しか表示されません。コピーして、コードの中ではなくシークレットの保管庫に保存してください。

設定 › API › 個人アクセスでキーを作成します。キーは、あなたのすべての Workspace で、あなたの代わりに動作します。指定した店舗によって Workspace が決まり、その中でのあなたのロールが、キーにできることを決めます。

キーは、Bearer トークンとして Authorization ヘッダーに入れて送ります。

curl https://api.sofia-post.com/api/v1/establishments \
  -H "Authorization: Bearer sofia_pk_live_…"

`GET /v1/me` で、キーが誰のために動作するか、その Workspace と権限が分かります。

必要な権限(スコープ)がない呼び出しには、403 insufficient_scope が返ります。

OAuth2 トークン

利用者の Workspace で、利用者のために動作する SaaS プロダクトでは、OAuth クライアントを登録します。ユーザーが自分で選んだ Workspace で、あなたのアプリを許可します。OAuth のアクセストークンは sofia_live_at_… で始まり、有効期間は 1 時間で、使い方は同じです。

curl https://api.sofia-post.com/api/v1/establishments \
  -H "Authorization: Bearer sofia_live_at_…"

ほかの Sofia アカウントが接続するアプリを作りますか?PKCE つきの認可コードフロー、クライアントクレデンシャル、スコープはOAuth2で説明しています。

ベース URL

すべてのエンドポイントは、1 つのベース URL の下にあります。

https://api.sofia-post.com/api/v1

レート制限

Sofia API は、15 分、24 時間、30 日の 3 つの期間でリクエストを数えます。

トークン15 分24 時間30 日
個人キー(アカウントごと)1501,00010,000
OAuth クライアント10050015,000
OAuth クライアント、許可したユーザーごと(最大 1,000)+10+25+100

ヘッダー

すべてのレスポンスで、IETF 形式で残りが分かります。

RateLimit: "15m";r=144;t=43, "24h";r=994;t=13543, "30d";r=9994;t=13543
RateLimit-Policy: "15m";q=150;w=900, "24h";q=1000;w=86400, "30d";q=10000;w=2592000

r は残りのリクエスト数、t は期間がリセットされるまでの秒数、q はクォータ、w は期間の秒数です。

期間の上限に達した時

レスポンスは 429 rate_limit_exceeded で、Retry-After ヘッダーと、上限に達した期間が details に含まれます。その時間だけ待ってから再試行してください。

API を効率よく使う

  • 投稿を繰り返し確認する代わりに、Webhook を登録します。
  • limit を最大 100 まで使い、一覧を少ないページ数で読み込みます。
  • めったに変わらないものはキャッシュします:プラットフォーム、店舗、接続済みアカウント。

エラー

すべてのエラーは、HTTP ステータスと同じ JSON エンベロープで返ります。code はコードが読む契約で、message は人間向けに書かれており、変わることがあります。

{
  "error": {
    "code": "invalid_request",
    "message": "The request is invalid",
    "details": [
      { "field": "targets[0].text", "code": "required", "message": "targets[0].text is required" }
    ],
    "docUrl": "https://www.sofia-post.com/developers/errors#invalid_request",
    "requestId": "2aab5943-6333-4caf-8478-25a85ebd4abf"
  }
}
  • details は詳細を示します:拒否されたフィールド、ネットワークの理由、上限に達した期間。
  • docUrl は、このページにあるコードの説明へのリンクです。
  • requestId はリクエストを識別します。サポートに伝えてください。

エラーコード

ステータスコード意味
400invalid_requestボディ、クエリ、またはパラメータが無効です。拒否されたフィールドごとに 1 件の詳細(field、code(required、is_string、max など)、message)が付きます。
400invalid_jsonボディが有効な JSON ではありません。
400invalid_cursorこのカーソルは Sofia API が返したものではありません。
400webhook_url_invalidwebhook のアドレスが https ではないか、プライベートアドレスを指しています。
400webhook_event_invalid不明な webhook イベントです。
400application_name_invalid設定 › API: クライアント名がない、または長すぎます。
400application_profile_invalid設定 › API: クライアントのプロフィールに無効な項目があります。詳細にその項目名が示されます。
400logo_invalid設定 › API: ロゴが拒否されました(180×180 px 以上、PNG、JPEG または WebP、2 MB 以下)。
401invalid_tokenトークンがない、不明、期限切れ、または取り消されています。あるいは、トークンの持ち主が、トークンに付与された権限を失っています。WWW-Authenticate ヘッダーを参照してください。
401unauthorized認証が必要です。
402subscription_requiredlive の書き込み(ドライランを含む)には、有効なサブスクリプションまたはトライアルが必要です。
403insufficient_scopeトークンにエンドポイントのスコープがありません。詳細に該当のスコープが示されます。
403insufficient_roleこの Workspace でのあなたのロールでは、この操作は許可されていません。
403live_mode_requiredテストトークンでの実際の書き込みです。
403route_closed_to_applicationsこのエンドポイントはアプリケーションには公開されていません。
403forbiddenアクセスが拒否されました。
403session_required設定 › API: この操作にはトークンではなく Sofia のセッションが必要です。
403impersonation_forbidden設定 › API: この操作は借用したセッションでは拒否されます。
403workspace_owner_requiredこの操作は Workspace のオーナーだけが行えます。
403workspace_member_requiredあなたはこの Workspace のメンバーではありません。
404route_not_foundこのメソッドとパスに応答するエンドポイントはありません。
404establishment_not_found不明な店舗、またはトークンの範囲外です。
404media_not_found不明なメディア、またはトークンの範囲外です。
404post_not_found不明な投稿、またはトークンの範囲外です。
404webhook_not_found不明な webhook、または別のアプリケーションのものです。
404not_foundリソースが存在しません。
404application_not_found設定 › API: 不明なクライアントです。
404grant_not_found不明な認可、またはすでに取り消されています。
404personal_key_not_found設定 › API: 不明な個人キーです。
405method_not_allowedこのエンドポイントでは、このメソッドは許可されていません。
409post_in_flightネットワークが投稿を送信中です。少し待ってから削除を再試行してください。
409post_not_editableネットワークがすでに投稿を送信したため、編集できません。
409account_already_connectedこのネットワークはすでに店舗に連携されています。
409webhook_limit_reachedアプリケーションの webhook がすでに 10 件に達しています。
409idempotency_in_progressこの Idempotency-Key での最初のリクエストがまだ実行中です。
409conflictリクエストがリソースの現在の状態と競合しています。
409scope_removal_forbidden設定 › API: クライアントの権限は追加できますが、削除はできません。
409public_client_has_no_secret設定 › API: パブリッククライアントにはシークレットがありません。
413payload_too_largeファイルがアップロードの上限を超えています。
415unsupported_media_typeサポートされていないコンテンツタイプです。
422post_refusedネットワーク、店舗、またはご利用のプランが投稿を拒否します。理由ごとに 1 件の詳細があり、それぞれに message が付きます。
422media_import_refusedこの URL からメディアをインポートできませんでした。理由は詳細に記載されます。
422media_upload_refusedアップロードされたファイルは、サポートされている画像または動画ではありません。
422platform_not_offeredこのネットワークは連携の対象外です。
422idempotency_key_reusedこの Idempotency-Key は別のボディで使用されました。
422unprocessable_entityリクエストは理解されましたが、処理できません。
429rate_limit_exceededレート制限のウィンドウが上限に達しました。Retry-After の時間だけ待ってください。

投稿が拒否される理由

post_refused は、details[].code に各理由を一覧します。

コード意味
unsupported_platformこのネットワークはサポートされていません。
duplicate_platform同じネットワークが 2 回指定されています。
unsupported_formatこのネットワークはこのフォーマットに対応していません。
caption_too_longテキストが長すぎます(max、actual)。
too_many_hashtagsハッシュタグが多すぎます(max、actual)。
too_many_mentionsメンションが多すぎます(max、actual)。
too_many_thread_postsスレッドが長すぎます(max、actual)。
too_many_mediaメディアが多すぎます(max、actual)。
media_mix_refusedこのネットワークでは、写真と動画を混在させることはできません。
media_requiredこのネットワークには写真または動画が必要です。
media_type_refusedこのネットワークは、この種類のメディアを受け付けません。
scheduled_in_pastscheduledAt が過去の日時です。
scheduled_at_invalid予約投稿には、未来の scheduledAt が必要です。
account_not_connectedこの店舗には、このネットワークに連携されたアカウントがありません。
account_ambiguousこのネットワークには複数のアカウントが連携されています。accountId で 1 つ指定してください。
media_not_foundこのメディアは、この店舗のライブラリにありません。
media_url_invalidメディアの URL は、公開された https のアドレスである必要があります。
platform_unavailableこのネットワークは、まだ Sofia では利用できません。
publication_quota_exceededご利用のプランの投稿数の上限に達しました。

エラーを処理する

  • ロジックは code に基づき、message には決して基づかせないでください。
  • 429 は Retry-After の後に、5xx は指数バックオフで再試行します。
  • 4xx を同じ内容で再試行してはいけません。まずリクエストを直してください。
  • 失敗するたびに requestId をログに記録します。

ページネーション

一覧(GET /v1/posts、GET /v1/media)は、新しい順に 1 ページずつ返ります。limit でページサイズを 1〜100(既定は 20)で指定します。

{
  "data": [ … ],
  "nextCursor": "WyIyMDI2LTEwLTI3VDE1OjExOjU5LjAxN1oiLCIx…"
}

nextCursor を cursor として渡すと、次のページを読めます。nextCursor がなければ、最後に達しています。

let cursor;
do {
  const url = new URL('https://api.sofia-post.com/api/v1/posts');
  url.searchParams.set('limit', '100');
  if (cursor) url.searchParams.set('cursor', cursor);
  const page = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${key}` } }).then((r) => r.json());
  handle(page.data);
  cursor = page.nextCursor;
} while (cursor);

カーソルは不透明な値です。作成したり変更したりしないでください。Sofia が返していないカーソルには 400 invalid_cursor が返ります。

冪等性

ネットワークエラーのせいで、投稿が作成されたか分からなくなることがあります。POST /v1/posts と POST /v1/media に Idempotency-Key ヘッダーを付け、同じキーで再試行してください。Sofia は最初と同じレスポンスを返し、重複して作成することはありません。

curl -X POST https://api.sofia-post.com/api/v1/posts \
  -H "Authorization: Bearer $SOFIA_API_KEY" \
  -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -d @post.json
  • 新しい投稿ごとに新しいランダムなキー(UUID)を使い、その投稿の再試行では同じキーを使います。
  • 同じキーで異なるボディを送ると、422 idempotency_key_reused が返ります。
  • 最初のリクエストが処理中の間は、同じキーに 409 idempotency_in_progress が返ります。少し待って再試行してください。

テストモードとドライラン

ドライラン。POST /v1/posts に ?dry_run=true を付けると、Sofia は実際の投稿と同じチェック(各ネットワークのルール、接続済みアカウント、メディア、日付、プラン)を行い、何も保存も送信もしません。レスポンスの詳細は公開するページにあります。

テストモード。 テストモード(トークンは sofia_test_at_…)で作成したアプリは、本物の Workspace を読み取れますが、書き込みは一切しません。投稿はシミュレートされ、同じルールで判定され、実際の書き込みには 403 live_mode_required が返ります。設定 › API で作成したキーとクライアントはライブモードです。公開せずに確認するには、ドライランを使ってください。

OAuth

アカウント

プラットフォーム

店舗

メディア

投稿

Webhook