クイックスタート

1 · キーを確認する

最初の呼び出しは無料で、何も変更せず、そのキーで何ができるか、どれだけ使ったかを返します。これが 200 を返せば、このドキュメントの他のすべてが利用可能です。

curl https://api.xsignalfeed.com/v1/account \
  -H "Authorization: Bearer xsf_your_key"
200 OK
{
  "name": "acme-trading",
  "plan": "pro",
  "key_id": "3f9a1c2b7d4e",
  "scopes": [
    "targets:read",
    "targets:write",
    "stream",
    "replay",
    "reads",
    "webhook",
    "keywords:read",
    "keywords:write"
  ],
  "event_types": [],
  "event_types_all": true,
  "limits": {
    "max_targets": 200,
    "max_conns": 5,
    "max_keywords": 20,
    "requests_per_minute": 600
  },
  "usage": {
    "targets": 12,
    "keywords": 3
  }
}

2 · アカウントを監視する

アカウントは @ を付けずにハンドルで指定します。当社が初めて見るアカウントであれば、X 上で解決して配信を有効化します。これは 1 分以内に完了し、お客様側の作業は不要です。どちらのケースだったかはレスポンスで分かります。

リクエスト
curl -X POST https://api.xsignalfeed.com/v1/monitor/targets \
  -H "Authorization: Bearer xsf_your_key" -H "Content-Type: application/json" \
  -d '{"screen_name": "elonmusk", "event_types": ["followers.verified.add"]}'
201 · 202
{
  "status": "subscribed",
  "state": "subscribed",
  "id": "44196397",
  "screen_name": "elonmusk",
  "name": "Elon Musk",
  "event_types": [
    "followers.verified.add"
  ],
  "event_types_all": false
}
{
  "status": "queued",
  "state": "queued",
  "screen_name": "newaccount",
  "user_id": null,
  "event_types": [],
  "event_types_all": true,
  "note": "target will be resolved and placed shortly; poll GET /v1/monitor/targets/{screen_name}"
}

既知のアカウントは即座に購読されます(201。すでに購読済みなら 200)。未知のアカウントは 202 です。state が subscribed になるまで GET /v1/monitor/targets/{target} を確認するか、details.state が failed の 404 を読んでください。その details.reason が理由を示します:user_not_found、user_suspended、user_protected のいずれかです。直近 10 分以内に X からそう伝えられていれば、POST 自体がその 404 を返します。既定ではキーが許可するすべてのイベント種類が配信されます。一部だけ受け取るには event_types を送ってください(例:followers.verified.add(方法) だけ)。まとめて追加するならバッチエンドポイントを使ってください。

3 · ストリームを開く

接続すると、イベントが 1 行ずつの JSON で届きます。それ以外は何も必要ありません。購読メッセージもチャンネルもありません。

import asyncio, json, websockets

async def main():
    url = "wss://ws.xsignalfeed.com/v1/stream?api_key=xsf_your_key"
    async with websockets.connect(url) as ws:
        async for raw in ws:
            ev = json.loads(raw)
            if ev["t"].startswith("system."): continue
            print(ev["t"], ev["target"]["screen_name"], ev["d"].get("text", "")[:60])

asyncio.run(main())

4 · 最初のイベントを読む

最初のフレームは必ず system.hello です。その後に本番のイベントが続きます:

ストリームに届いたばかりの新規投稿
{
  "v": 1,
  "t": "tweet.create",
  "op": "create",
  "id": "tweet:2097879002992070835",
  "ts": 1789012345678,
  "sid": "1789012346012-0",
  "target": {
    "id": "1652541",
    "screen_name": "Reuters",
    "name": "Reuters"
  },
  "d": {
    "tweet_id": "2097879002992070835",
    "kind": "tweet",
    "text": "Ash-covered shoes and a president in crisis…",
    "lang": "en",
    "author": {
      "id": "1652541",
      "screen_name": "Reuters",
      "name": "Reuters",
      "bio": "Top and breaking news, pictures and videos from Reuters.",
      "avatar": "https://pbs.twimg.com/profile_images/1874154135869616128/nJDmubGJ_normal.jpg",
      "banner": "https://pbs.twimg.com/profile_banners/1652541/1573687397",
      "followers": 25900000,
      "following": 1150,
      "tweets": 1080000,
      "location": "Around the world",
      "url": "https://www.reuters.com",
      "verified": true,
      "verified_type": "Business",
      "protected": false,
      "created_at": "2007-03-20T17:46:05Z",
      "category": null
    },
    "reply_to": null,
    "retweet_of": null,
    "quoted": null,
    "media": [
      "https://pbs.twimg.com/media/….jpg"
    ],
    "media_items": [
      {
        "type": "photo",
        "url": "https://pbs.twimg.com/media/….jpg",
        "expanded_url": "https://x.com/Reuters/status/2097879002992070835/photo/1",
        "width": 1200,
        "height": 675
      }
    ],
    "urls": [
      "https://reut.rs/…"
    ],
    "counts": {
      "reply": 12,
      "rt": 40,
      "like": 131,
      "quote": 3,
      "bookmark": 9
    },
    "views": 20411,
    "url": "https://x.com/Reuters/status/2097879002992070835",
    "full": true,
    "kind_final": true
  }
}