> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sorsa.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 公式X APIからの移行

# 公式X API v2からSorsa API v3へ移行する

既存のTwitter/X API v2連携をSorsa API v3へ移すためのリファレンスです。認証、エンドポイントの対応、レスポンス形式、ページネーション、HTTPメソッド、検索構文、エラー処理、`curl`・Python・JavaScriptのコード例を説明します。

Sorsa APIは読み取り専用です。投稿、DM、「いいね」、フォローなどの書き込みも行う連携では、書き込み用に公式APIキーを残し、読み取り部分だけを移行してください。新規アカウントにはカード不要の無料100リクエストがあり、本番を切り替える前に対応する機能を検証できます。

> **注：** 費用比較と手順に沿った例は、ブログの[Twitter/X APIからの移行：開発者向け完全ガイド](https://api.sorsa.io/blog/migrate-from-twitter-api)を参照してください。

## 変更点の概要

| 項目       | 公式X API v2                                | Sorsa API v3              |
| -------- | ----------------------------------------- | ------------------------- |
| ベースURL   | `https://api.x.com/2`                     | `https://api.sorsa.io/v3` |
| 認証       | OAuth 2.0 Bearer / OAuth 1.0a             | `ApiKey`ヘッダーのAPIキー        |
| フィールド選択  | `tweet.fields`、`user.fields`、`expansions` | デフォルトですべて返す               |
| レスポンスの外枠 | `data` + `includes` + `meta`              | フラットなオブジェクトに関連情報を内包       |
| ページネーション | `pagination_token` / `meta.next_token`    | 最上位の`next_cursor`         |
| レート制限    | エンドポイント別・15分単位                            | 共通で毎秒20リクエスト              |
| エラー形式    | `type`、`title`、`detail`を持つ`errors[]`      | `{ "message": "..." }`    |

## 認証

公式APIは、アプリ専用リクエストにOAuth 2.0 Bearerトークン、ユーザーの権限で行うリクエストにOAuth 1.0a User Contextを使います。

```bash theme={null}
# Official API (OAuth 2.0 App-Only)
curl "https://api.x.com/2/users/by/username/elonmusk" \
  -H "Authorization: Bearer $BEARER_TOKEN"
```

Sorsaは単一のAPIキーを`ApiKey`ヘッダーで渡します。[ダッシュボード](https://api.sorsa.io/overview/keys)でキーを発行してください。

```bash theme={null}
curl "https://api.sorsa.io/v3/info?username=elonmusk" \
  -H "ApiKey: $API_KEY"
```

```python theme={null}
import requests

response = requests.get(
    "https://api.sorsa.io/v3/info",
    params={"username": "elonmusk"},
    headers={"ApiKey": API_KEY},
)
user = response.json()
```

```javascript theme={null}
const response = await fetch("https://api.sorsa.io/v3/info?username=elonmusk", {
  headers: { ApiKey: API_KEY },
});
const user = await response.json();
```

詳細は[認証](https://docs.sorsa.io/ja/authentication)を参照してください。

## エンドポイントの対応

### ユーザー

| 操作                    | 公式X API v2                           | Sorsa API v3                                |
| --------------------- | ------------------------------------ | ------------------------------------------- |
| ユーザー名でユーザー取得          | `GET /2/users/by/username/:username` | `GET /info?username=:username`              |
| IDでユーザー取得             | `GET /2/users/:id`                   | `GET /info?user_id=:id`                     |
| 複数ユーザー取得              | `GET /2/users?ids=...`               | `GET /info-batch?user_ids=...&user_ids=...` |
| フォロワー取得               | `GET /2/users/:id/followers`         | `GET /followers?user_id=:id`                |
| フォロー中ユーザー取得           | `GET /2/users/:id/following`         | `GET /follows?user_id=:id`                  |
| 認証済みフォロワー             | 未提供                                  | `GET /verified-followers?user_id=:id`       |
| アカウントの「このアカウントについて」情報 | 未提供                                  | `GET /about?username=:username`             |

* `GET /info-batch`は1リクエストで最大100ユーザー名またはIDに対応します。`?usernames=a&usernames=b`のようにクエリパラメーターを繰り返します。
* `GET /followers`と`GET /follows`は、自己紹介、フォロワー数、認証状態を含む**完全なプロフィールを1ページ最大200件**返します。

### ツイート

| 操作          | 公式X API v2                       | Sorsa API v3                                           |
| ----------- | -------------------------------- | ------------------------------------------------------ |
| 単一ツイートの取得   | `GET /2/tweets/:id`              | `POST /tweet-info` 本文： `{ "tweet_link": ":id" }`       |
| 複数ツイートの取得   | `GET /2/tweets?ids=...`          | `POST /tweet-info-bulk` 本文： `{ "tweet_links": [...] }` |
| ユーザーのタイムライン | `GET /2/users/:id/tweets`        | `POST /user-tweets` 本文： `{ "user_id": ":id" }`         |
| 引用ツイート      | `GET /2/tweets/:id/quote_tweets` | `POST /quotes` 本文： `{ "tweet_link": ":id" }`           |
| リツイートしたユーザー | `GET /2/tweets/:id/retweeted_by` | `POST /retweeters` 本文： `{ "tweet_link": ":id" }`       |
| 返信（コメント）    | 専用エンドポイントなし                      | `POST /comments` 本文： `{ "tweet_link": ":id" }`         |
| 長文のX記事      | 未提供                              | `POST /article` 本文： `{ "tweet_link": ":id" }`          |

* `tweet_link`には完全なURL（`https://x.com/user/status/123`）または数値ID（`"123"`）を指定できます。
* `POST /tweet-info-bulk`は1回最大100ツイートです。`POST /tweet-info`の繰り返しから替えると、リクエストを最大100分の1に減らせます。
* `POST /user-tweets`には3,200件の上限がありません。`next_cursor`がなくなるまで取得すると、最初のツイートまでたどれます。[過去のデータ](https://docs.sorsa.io/ja/historical-data)を参照してください。

### 検索

| 操作        | 公式X API v2                              | Sorsa API v3                                   |
| --------- | --------------------------------------- | ---------------------------------------------- |
| 最近のツイート検索 | `GET /2/tweets/search/recent?query=...` | `POST /search-tweets` 本文： `{ "query": "..." }` |
| 全アーカイブ検索  | `GET /2/tweets/search/all?query=...`    | `POST /search-tweets` （過去のデータを含む）              |
| メンション検索   | `GET .../search/recent?query=@user`     | `POST /mentions` 本文： `{ "query": "user" }`     |
| ユーザー検索    | v2では未提供                                 | `POST /search-users` 本文： `{ "query": "..." }`  |

* `/search-tweets`はXのWeb版高度な検索の構文を使います。基本的な語は多くの場合そのまま使えますが、API v2固有の演算子は再利用前に確認してください。[検索演算子](https://docs.sorsa.io/ja/search-operators)を参照してください。
* `POST /mentions`には、公式APIにない`min_likes`、`min_replies`、`min_retweets`、`since_date`、`until_date`があります。

### リスト

| 操作        | 公式X API v2                   | Sorsa API v3                        |
| --------- | ---------------------------- | ----------------------------------- |
| リストのメンバー  | `GET /2/lists/:id/members`   | `GET /list-members?list_id=:id`     |
| リストのフォロワー | `GET /2/lists/:id/followers` | `GET /list-followers?list_link=:id` |
| リストのツイート  | `GET /2/lists/:id/tweets`    | `GET /list-tweets?list_id=:id`      |

### コミュニティ

公式X APIはコミュニティのエンドポイントを公開していません。Sorsa独自の機能です。

| 操作          | Sorsa API v3                                                                      |
| ----------- | --------------------------------------------------------------------------------- |
| コミュニティのメンバー | `POST /community-members` 本文： `{ "community_link": ":id" }`                       |
| コミュニティのフィード | `POST /community-tweets` 本文： `{ "community_id": ":id", "order": "popular" }`      |
| コミュニティ内検索   | `POST /community-search-tweets` 本文： `{ "community_link": ":id", "query": "..." }` |

リクエストの詳細と提供状況は[リストとコミュニティ](https://docs.sorsa.io/ja/lists-and-communities)を参照してください。移行前に現在の対応状況を確認します。

### アクションの確認

以下は1回の呼び出しで参加・行動の有無を調べる機能です。公式APIに同等機能はなく、同じ処理には一覧全体の取得とクライアント側の検索が必要です。

| 確認内容                  | Sorsa API v3                                     |
| --------------------- | ------------------------------------------------ |
| ユーザーAはBをフォローしているか     | `POST /check-follow`                             |
| ユーザーXはツイートYにコメントしたか   | `GET /check-comment?tweet_link=...&username=...` |
| ユーザーXはYを引用またはリツイートしたか | `POST /check-quoted`                             |
| ユーザーXはYをリツイートしたか      | `POST /check-retweet`                            |
| ユーザーXはコミュニティYに参加しているか | `POST /check-community-member`                   |

[マーケティングキャンペーンの確認](https://docs.sorsa.io/ja/Marketing-Campaign-Verification)を参照してください。

### 分析（Sorsa独自）

| 操作             | Sorsa API v3                         |
| -------------- | ------------------------------------ |
| 影響力スコア         | `GET /score?username=...`            |
| スコア変化量（7日、30日） | `GET /score-changes?username=...`    |
| カテゴリー別フォロワー内訳  | `GET /followers-stats?username=...`  |
| スコア上位20人のフォロワー | `GET /top-followers?username=...`    |
| スコア上位20件のフォロー先 | `GET /top-following?username=...`    |
| 新規フォロワー（7日間）   | `GET /new-followers-7d?username=...` |
| 新規フォロー先（7日間）   | `GET /new-following-7d?username=...` |

暗号資産関連のアカウント（インフルエンサー、プロジェクト、VC）の一部を索引化したデータです。[Sorsa Scoreと暗号資産分析](https://docs.sorsa.io/ja/sorsa-score-and-crypto-analytics)を参照してください。

### ユーティリティ

| 操作               | Sorsa API v3                  |
| ---------------- | ----------------------------- |
| ユーザー名から数値IDへ     | `GET /username-to-id/:handle` |
| 数値IDからユーザー名へ     | `GET /id-to-username/:id`     |
| プロフィールURLから数値IDへ | `GET /link-to-id?link=...`    |
| APIキー使用統計        | `GET /key-usage-info`         |

[ID変換](https://docs.sorsa.io/ja/ID-Conversion)を参照してください。

## レスポンス形式の変更

移行で最も大きい変更点です。公式v2は`data`、`includes`、`meta`で包まれますが、Sorsaはフラットなオブジェクトで、投稿者情報を各ツイートに直接含めます。

### ユーザープロフィール

**公式X API v2**（フィールド選択あり）：

```json theme={null}
{
  "data": {
    "id": "44196397",
    "name": "Elon Musk",
    "username": "elonmusk",
    "verified": false,
    "profile_image_url": "https://pbs.twimg.com/...",
    "public_metrics": {
      "followers_count": 100000000,
      "following_count": 500,
      "tweet_count": 30000,
      "listed_count": 12000
    }
  }
}
```

**Sorsa API v3：**

```json theme={null}
{
  "id": "44196397",
  "username": "elonmusk",
  "display_name": "Elon Musk",
  "description": "...",
  "location": "Austin, TX",
  "profile_image_url": "https://pbs.twimg.com/...",
  "profile_background_image_url": "...",
  "followers_count": 100000000,
  "followings_count": 500,
  "tweets_count": 30000,
  "favourites_count": 50000,
  "media_count": 1200,
  "verified": false,
  "protected": false,
  "can_dm": true,
  "possibly_sensitive": false,
  "created_at": "2009-06-02T20:12:29Z",
  "bio_urls": ["https://example.com"],
  "pinned_tweet_ids": ["17823..."]
}
```

### ツイート

**公式X API v2**（`expansions=author_id`あり）：

```json theme={null}
{
  "data": {
    "id": "1234567890",
    "text": "Hello world",
    "created_at": "2024-01-15T12:00:00.000Z",
    "author_id": "44196397",
    "conversation_id": "1234567890",
    "lang": "en",
    "public_metrics": {
      "retweet_count": 100,
      "reply_count": 50,
      "like_count": 500,
      "quote_count": 25,
      "bookmark_count": 10,
      "impression_count": 50000
    }
  },
  "includes": {
    "users": [
      { "id": "44196397", "name": "Elon Musk", "username": "elonmusk" }
    ]
  }
}
```

**Sorsa API v3：**

```json theme={null}
{
  "id": "1234567890",
  "full_text": "Hello world",
  "created_at": "2024-01-15T12:00:00Z",
  "lang": "en",
  "conversation_id_str": "1234567890",
  "likes_count": 500,
  "retweet_count": 100,
  "reply_count": 50,
  "quote_count": 25,
  "view_count": 50000,
  "bookmark_count": 10,
  "is_reply": false,
  "is_quote_status": false,
  "is_replies_limited": false,
  "in_reply_to_tweet_id": null,
  "in_reply_to_username": null,
  "user": {
    "id": "44196397",
    "username": "elonmusk",
    "display_name": "Elon Musk",
    "followers_count": 100000000
  },
  "entities": [],
  "quoted_status": null,
  "retweeted_status": null
}
```

### フィールドの対応

#### ユーザーのフィールド

| 公式X API v2                       | Sorsa API v3                   | 注記        |
| -------------------------------- | ------------------------------ | --------- |
| `id`                             | `id`                           | 同じ        |
| `username`                       | `username`                     | 同じ        |
| `name`                           | `display_name`                 | 改名        |
| `description`                    | `description`                  | 同じ        |
| `location`                       | `location`                     | 同じ        |
| `verified`                       | `verified`                     | 同じ        |
| `protected`                      | `protected`                    | 同じ        |
| `profile_image_url`              | `profile_image_url`            | 同じ        |
| `created_at`                     | `created_at`                   | 同じ        |
| `public_metrics.followers_count` | `followers_count`              | 最上位へ移動    |
| `public_metrics.following_count` | `followings_count`             | 最上位へ移動し改名 |
| `public_metrics.tweet_count`     | `tweets_count`                 | 最上位へ移動し改名 |
| `public_metrics.listed_count`    | 未提供                            |           |
| 未提供                              | `favourites_count`             | Sorsa専用   |
| 未提供                              | `media_count`                  | Sorsa専用   |
| 未提供                              | `can_dm`                       | Sorsa専用   |
| 未提供                              | `bio_urls`                     | Sorsa専用   |
| 未提供                              | `pinned_tweet_ids`             | Sorsa専用   |
| 未提供                              | `profile_background_image_url` | Sorsa専用   |
| 未提供                              | `possibly_sensitive`           | Sorsa専用   |

#### ツイートのフィールド

| 公式X API v2                         | Sorsa API v3                                        | 注記                |
| ---------------------------------- | --------------------------------------------------- | ----------------- |
| `id`                               | `id`                                                | 同じ                |
| `text`                             | `full_text`                                         | 改名                |
| `created_at`                       | `created_at`                                        | 同じ                |
| `lang`                             | `lang`                                              | 同じ                |
| `conversation_id`                  | `conversation_id_str`                               | 改名                |
| `in_reply_to_user_id`              | `in_reply_to_username`                              | 数値IDではなくユーザー名を返す  |
| `public_metrics.like_count`        | `likes_count`                                       | 最上位へ移動し改名（複数形に注意） |
| `public_metrics.retweet_count`     | `retweet_count`                                     | 最上位へ移動            |
| `public_metrics.reply_count`       | `reply_count`                                       | 最上位へ移動            |
| `public_metrics.quote_count`       | `quote_count`                                       | 最上位へ移動            |
| `public_metrics.bookmark_count`    | `bookmark_count`                                    | 最上位へ移動            |
| `public_metrics.impression_count`  | `view_count`                                        | 最上位へ移動し改名         |
| `author_id` + `includes.users[]`   | `user` （完全なオブジェクトを内包）                               | 埋め込み              |
| 参照ツイートを取得： `includes`              | `quoted_status`, `retweeted_status`                 | 内包されたオブジェクト       |
| 未提供                                | `is_reply`, `is_quote_status`, `is_replies_limited` | Sorsa専用の真偽値       |
| 未提供                                | `in_reply_to_tweet_id`                              | Sorsa専用           |
| `entities` （URL、メンション、ハッシュタグ、メディア） | `entities` 配列： `{ type, link, preview }`            | 異なる構造             |

## ページネーション

公式APIはクエリに`pagination_token`を渡し、`meta.next_token`を返します。Sorsaは両方向で同じ`next_cursor`を使います。

**GETでは**クエリパラメーターとして渡します。

```bash theme={null}
curl "https://api.sorsa.io/v3/followers?username=elonmusk&next_cursor=ABC123" \
  -H "ApiKey: $API_KEY"
```

**POSTでは**JSON本文に含めます。

```bash theme={null}
curl -X POST "https://api.sorsa.io/v3/search-tweets" \
  -H "ApiKey: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "query": "from:elonmusk", "next_cursor": "ABC123" }'
```

レスポンスの`next_cursor`は最上位にあります。

```json theme={null}
{
  "tweets": [...],
  "next_cursor": "XYZ789"
}
```

`next_cursor`がない、または`null`なら次のページはありません。詳細は[ページネーション](https://docs.sorsa.io/ja/pagination)を参照してください。

## HTTPメソッドの違い

公式APIでは`GET`でも、Sorsaでは`POST`の機能があります。

| 操作              | 公式API        | Sorsa API |
| --------------- | ------------ | --------- |
| ツイート取得          | GET          | **POST**  |
| ツイート検索          | GET          | **POST**  |
| ユーザーのタイムライン     | GET          | **POST**  |
| 引用ツイート          | GET          | **POST**  |
| リツイートしたユーザー     | GET          | **POST**  |
| 返信（コメント）        | （直接対応する機能なし） | **POST**  |
| ユーザープロフィール      | GET          | GET       |
| フォロワー・フォロー中ユーザー | GET          | GET       |
| リスト             | GET          | GET       |

基本的に、ツイート内容、検索、コミュニティはJSON本文付きの`POST`です。ユーザー識別子を取る`/user-tweets`もPOSTです。ユーザー、リスト、ユーティリティは、クエリまたはパスのパラメーターを使う`GET`です。例外の`/check-comment`はツイートリンクを取りますが`GET`です。不明な場合は各リファレンスを確認してください。

## コード移行の例

### ユーザープロフィールを取得する

**移行前（公式API）：**

```bash theme={null}
curl "https://api.x.com/2/users/by/username/elonmusk?user.fields=description,public_metrics,profile_image_url,verified,created_at" \
  -H "Authorization: Bearer $BEARER_TOKEN"
```

```python theme={null}
import requests

response = requests.get(
    "https://api.x.com/2/users/by/username/elonmusk",
    params={"user.fields": "description,public_metrics,profile_image_url,verified,created_at"},
    headers={"Authorization": f"Bearer {BEARER_TOKEN}"},
)
user = response.json()["data"]
followers = user["public_metrics"]["followers_count"]
name = user["name"]
```

```javascript theme={null}
const url = "https://api.x.com/2/users/by/username/elonmusk" +
  "?user.fields=description,public_metrics,profile_image_url,verified,created_at";
const res = await fetch(url, {
  headers: { Authorization: `Bearer ${BEARER_TOKEN}` },
});
const { data: user } = await res.json();
const followers = user.public_metrics.followers_count;
const name = user.name;
```

**移行後（Sorsa API）：**

```bash theme={null}
curl "https://api.sorsa.io/v3/info?username=elonmusk" \
  -H "ApiKey: $API_KEY"
```

```python theme={null}
import requests

response = requests.get(
    "https://api.sorsa.io/v3/info",
    params={"username": "elonmusk"},
    headers={"ApiKey": API_KEY},
)
user = response.json()
followers = user["followers_count"]
name = user["display_name"]
```

```javascript theme={null}
const res = await fetch("https://api.sorsa.io/v3/info?username=elonmusk", {
  headers: { ApiKey: API_KEY },
});
const user = await res.json();
const followers = user.followers_count;
const name = user.display_name;
```

### ツイートを検索する

**移行前：**

```bash theme={null}
curl "https://api.x.com/2/tweets/search/recent?query=from%3Aelonmusk%20since%3A2024-01-01&tweet.fields=created_at,public_metrics,lang&expansions=author_id&user.fields=username,name&max_results=10" \
  -H "Authorization: Bearer $BEARER_TOKEN"
```

```python theme={null}
params = {
    "query": "from:elonmusk since:2024-01-01",
    "tweet.fields": "created_at,public_metrics,lang",
    "expansions": "author_id",
    "user.fields": "username,name",
    "max_results": 10,
}
response = requests.get(
    "https://api.x.com/2/tweets/search/recent",
    headers={"Authorization": f"Bearer {BEARER_TOKEN}"},
    params=params,
)
data = response.json()

tweets = data["data"]
users = {u["id"]: u for u in data.get("includes", {}).get("users", [])}
next_token = data.get("meta", {}).get("next_token")

for tweet in tweets:
    author = users.get(tweet["author_id"])
    print(tweet["text"], "by", author["username"])
```

```javascript theme={null}
const params = new URLSearchParams({
  query: "from:elonmusk since:2024-01-01",
  "tweet.fields": "created_at,public_metrics,lang",
  expansions: "author_id",
  "user.fields": "username,name",
  max_results: "10",
});
const res = await fetch(`https://api.x.com/2/tweets/search/recent?${params}`, {
  headers: { Authorization: `Bearer ${BEARER_TOKEN}` },
});
const data = await res.json();

const tweets = data.data || [];
const users = Object.fromEntries((data.includes?.users || []).map(u => [u.id, u]));

for (const t of tweets) {
  const author = users[t.author_id];
  console.log(t.text, "by", author.username);
}
```

**移行後：**

```bash theme={null}
curl -X POST "https://api.sorsa.io/v3/search-tweets" \
  -H "ApiKey: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "from:elonmusk since:2024-01-01"}'
```

```python theme={null}
response = requests.post(
    "https://api.sorsa.io/v3/search-tweets",
    headers={"ApiKey": API_KEY},
    json={"query": "from:elonmusk since:2024-01-01"},
)
data = response.json()

for tweet in data["tweets"]:
    print(tweet["full_text"], "by", tweet["user"]["username"])
```

```javascript theme={null}
const res = await fetch("https://api.sorsa.io/v3/search-tweets", {
  method: "POST",
  headers: { ApiKey: API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ query: "from:elonmusk since:2024-01-01" }),
});
const data = await res.json();

for (const t of data.tweets) {
  console.log(t.full_text, "by", t.user.username);
}
```

### 全フォロワーを順に取得する

**移行前：**

```python theme={null}
def fetch_all_followers_official(user_id, token):
    url = f"https://api.x.com/2/users/{user_id}/followers"
    headers = {"Authorization": f"Bearer {token}"}
    followers = []
    pagination_token = None

    while True:
        params = {"max_results": 1000}
        if pagination_token:
            params["pagination_token"] = pagination_token

        r = requests.get(url, headers=headers, params=params)
        r.raise_for_status()
        data = r.json()

        followers.extend(data.get("data", []))
        pagination_token = data.get("meta", {}).get("next_token")
        if not pagination_token:
            break

    return followers
```

**移行後：**

```bash theme={null}
CURSOR=""
while :; do
  RES=$(curl -s "https://api.sorsa.io/v3/followers?username=elonmusk${CURSOR:+&next_cursor=$CURSOR}" \
    -H "ApiKey: $API_KEY")
  echo "$RES" | jq '.users'
  CURSOR=$(echo "$RES" | jq -r '.next_cursor // empty')
  [ -z "$CURSOR" ] && break
done
```

```python theme={null}
def fetch_all_followers(user_id, api_key):
    url = "https://api.sorsa.io/v3/followers"
    headers = {"ApiKey": api_key}
    followers = []
    next_cursor = None

    while True:
        params = {"user_id": user_id}
        if next_cursor:
            params["next_cursor"] = next_cursor

        r = requests.get(url, headers=headers, params=params)
        r.raise_for_status()
        data = r.json()

        followers.extend(data.get("users", []))
        next_cursor = data.get("next_cursor")
        if not next_cursor:
            break

    return followers
```

```javascript theme={null}
async function fetchAllFollowers(userId, apiKey) {
  const followers = [];
  let nextCursor = null;

  do {
    const params = new URLSearchParams({ user_id: userId });
    if (nextCursor) params.set("next_cursor", nextCursor);

    const res = await fetch(
      `https://api.sorsa.io/v3/followers?${params}`,
      { headers: { ApiKey: apiKey } }
    );
    const json = await res.json();

    followers.push(...(json.users || []));
    nextCursor = json.next_cursor || null;
  } while (nextCursor);

  return followers;
}
```

Sorsaは1ページ最大200件の完全なプロフィールを返します。公式APIは通常IDと最小限の情報を返すため、プロフィールの補完には別の取得が必要です。

## 検索構文

SorsaはXのWeb版高度な検索の構文を使い、公式API v2の演算子とは異なります。基本キーワード、フレーズ、`from:`、`to:`など使えるものは維持し、API固有のフィルターを変換して結果をテストしてください。たとえば、ネイティブリツイートの除外は`-filter:nativeretweets`です。

| 演算子                 | 例                                               |
| ------------------- | ----------------------------------------------- |
| `from:`             | `from:elonmusk`                                 |
| `to:`               | `to:elonmusk`                                   |
| `since:` / `until:` | `since:2024-01-01 until:2024-02-01`             |
| ハッシュタグ              | `#bitcoin`                                      |
| 完全一致フレーズ            | `"hello world"`                                 |
| `OR`                | `bitcoin OR ethereum`                           |
| 除外                  | `-filter:nativeretweets`                        |
| 組み合わせ               | `from:elonmusk #bitcoin -filter:nativeretweets` |

完全なリファレンス：[検索演算子](https://docs.sorsa.io/ja/search-operators)。

`/mentions`には、サーバー側の`min_likes`、`min_replies`、`min_retweets`、`since_date`、`until_date`もあります。[メンションの追跡](https://docs.sorsa.io/ja/search-mentions)を参照してください。

## エラー処理

公式APIは、構造化された`errors`配列を返します。

```json theme={null}
{
  "errors": [
    {
      "message": "Not Found",
      "type": "https://api.x.com/2/problems/resource-not-found",
      "title": "Not Found Error",
      "detail": "Could not find tweet with id: [123].",
      "status": 404
    }
  ]
}
```

Sorsaは単純な形式です。

```json theme={null}
{ "message": "Tweet not found" }
```

コードは全エンドポイント共通で`400`、`401`、`403`、`404`、`429`、`500`です。[エラーコード](https://docs.sorsa.io/ja/error-codes)を参照してください。

`429`では待機して再試行します。上限は全エンドポイント共通で**毎秒20リクエスト**です。個別の時間枠を追う必要はありません。[レート制限](https://docs.sorsa.io/ja/rate-limits)を参照してください。

両方のAPIに対応した再試行ラッパー：

```python theme={null}
import time
import requests

def call_with_retry(method, url, max_retries=3, **kwargs):
    for attempt in range(max_retries):
        r = requests.request(method, url, **kwargs)
        if r.status_code == 429:
            time.sleep(2 ** attempt)
            continue
        r.raise_for_status()
        return r.json()
    raise RuntimeError(f"Failed after {max_retries} retries")
```

## 移行チェックリスト

* `Authorization: Bearer ...`を`ApiKey: ...`に替える。
* OAuth 1.0aの署名処理（コンシューマーキー、アクセストークン、署名生成）を取り除く。
* ベースURLを`https://api.x.com/2`から`https://api.sorsa.io/v3`に替える。
* 上記の表で全エンドポイントのパスを対応させる。
* ツイート、検索、コメント、引用、リツイートしたユーザーの取得をGETからPOSTに替える。
* `tweet.fields`、`user.fields`、`media.fields`、`expansions`を削除する。
* パーサーを変更し、`data`・`includes`・`meta`の外枠を取り出す処理をなくす。
* フィールド名を変更する（`name`→`display_name`、`text`→`full_text`など）。
* `public_metrics`の外枠をなくし、指標へ直接アクセスする。
* `pagination_token`・`next_token`を`next_cursor`に替える。
* `{ "message": "..." }`に合わせてエラー処理を変える。
* レート制限を、個別の時間枠なしの共通20件／秒へ調整する。
* [API Playground](https://api.sorsa.io/playground)で主要なエンドポイントをテストする。
* `GET /key-usage-info`で割り当て量を監視する。
* 投稿・DMなどの書き込みが必要なら公式APIキーを保持する。

## 公式APIに対応機能がないもの

| 機能                 | エンドポイント                                             |
| ------------------ | --------------------------------------------------- |
| 3,200件の上限なしのタイムライン | `POST /user-tweets`                                 |
| 1回の呼び出しでフォロー確認     | `POST /check-follow`                                |
| 1回の呼び出しでリツイート確認    | `POST /check-retweet`                               |
| 1回の呼び出しでコメント確認     | `GET /check-comment`                                |
| 1回の呼び出しで引用・リツイート確認 | `POST /check-quoted`                                |
| コミュニティへの参加確認       | `POST /check-community-member`                      |
| コミュニティのメンバーとフィード   | `POST /community-members`, `POST /community-tweets` |
| コミュニティ内検索          | `POST /community-search-tweets`                     |
| 長文X記事の内容           | `POST /article`                                     |
| 認証済みフォロワーのみの絞り込み   | `GET /verified-followers`                           |
| アカウントの国とユーザー名変更履歴  | `GET /about`                                        |
| 影響力スコア             | `GET /score`, `GET /score-changes`                  |
| 影響力上位のフォロワーとフォロー先  | `GET /top-followers`, `GET /top-following`          |
| 分類されたフォロワー内訳       | `GET /followers-stats`                              |

## 関連リファレンス

* [認証](https://docs.sorsa.io/ja/authentication)
* [ベースURLとバージョン管理](https://docs.sorsa.io/ja/base-url-and-versioning)
* [レート制限](https://docs.sorsa.io/ja/rate-limits)
* [ページネーション](https://docs.sorsa.io/ja/pagination)
* [エラーコード](https://docs.sorsa.io/ja/error-codes)
* [レスポンス形式](https://docs.sorsa.io/ja/response-format)
* [検索演算子](https://docs.sorsa.io/ja/search-operators)
* [API利用の最適化](https://docs.sorsa.io/ja/optimizing-api-usage)
* [APIリファレンス](https://docs.sorsa.io/ja/api-reference-guide)
