foursquareのAPIを弄ってみた

foursquareって知ってますか?foursquareと言うよりswarmと言った方が馴染み深いかもしれません。
foursquareは元々swarmと一体のアプリでしたが、チェックイン機能が分離されてswarmに移ったという経緯があります。

さて、そのfoursquare(とswarm)にはAPIが用意されていて、APIを用いてチェックイン等を行うことができます。

ユーザー登録

https://ja.foursquare.com/developers/signup
に必要事項を記入します。既にアカウントを持っている場合は改めて登録する必要はありません。

メール認証をクリアすると、コンソールに移動します。

アプリの作成


アプリ名とURLを入力します。URLは多分適当でも大丈夫でしょう。
次にPilgrim SDKを導入するか聞かれます。自分はとりあえず不要なので下の「Get started…」を選択。

ここまで終了すると、ClientIDとSecretが発行されます。

CLIENT IDとCLIENT SECRETは他人に漏洩しないよう注意しましょう。

なお、無料のアカウントでは1アカウント毎に1つのアプリしか作成できません。

ドキュメントを読む

公式のドキュメントはhttps://developer.foursquare.com/docsに用意されています。

利用規約を読みます。

  • Foursquareのデータは最大24時間キャッシュできる
  • アプリを認証していないユーザーのデータは最大3時間キャッシュできる
  • 商用利用には商用ライセンスを買ってね
  • プライバシーポリシー書いてね
  • APIには制限があるよ(後述)

キャッシュの保持時間は上に従わなければなりません。

API制限は時間ごとの制限と日ごとの制限があり、どちらか一方が上限に達した時点で制限されます。
時間ごとの制限:1時間あたり500リクエスト(ユーザー認証)・1時間あたり5000リクエスト(ユーザーレス認証)
日ごとの制限:1日あたり950回(通常API)+50回(プレミアムAPI)もしくは1日あたり99500回(通常API)+500回(プレミアムAPI)
クレジットカードを用いた認証をクリアすると日ごとの制限が緩和されます。

ユーザー認証する

チェックイン等のAPIはユーザー認証をしないと使えません。

ユーザー認証を行うには、まずリダイレクトURIを設定する必要があります。設定画面に戻って設定しましょう。
設定が済んだら、ユーザーを

https://foursquare.com/oauth2/authenticate?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI

にアクセスさせます。YOUR_CLIENT_IDとYOUR_REGISTERED_REDIRECT_URIの値は各自の値になります。

このURLにアクセスすると、下のような画面に飛びます。

ここで[許可する]を押すと、YOUR_REGISTERED_REDIRECT_URIで設定したURLに飛びます。その際、codeパラメータが自動的に付与されますので、そのcodeを控えます。

次に、下のURLをリクエストします。

https://foursquare.com/oauth2/access_token?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=authorization_code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI&code=CODE

CODEはcodeパラメータと同一です。

リクエストを実行すると、json形式でaccess_tokenが返ります。

今後、このaccess_tokenをパラメータの一部として加えてリクエストすることになります。なお、このリクエストを実行してもYOUR_REGISTERED_REDIRECT_URIで設定したURLにリダイレクトされません。

APIを使ってみる

バージョニング

全てのリクエストにおいて、vパラメータを付与することで、指定したバージョン日付でプログラムが実行され、応答を得ることができます。形式はv=YYYYMMDDです。

例えば、仮にv=20190917とします。2019/09/27にバージョン変更があった場合でも、v=20190917を付与していれば、2019/09/17時点のバージョンの応答がサーバーから得られます。

vパラメータに現在の日付を渡すのはナンセンスです。

言語の指定

HTTPヘッダにAccept-Languageを指定することで、言語を指定できます。また、locateパラメータに指定することでも同様に指定できます。サポートされているのはen,es,fr,de,it,ja,th,tr,ko,ru,pt,idです。

チェックインの取得

では、まず手始めにユーザーのチェックインを取得してみましょう。
エンドポイントはhttps://api.foursquare.com/v2/users/USER_ID/checkinsです。
ドキュメントを見て、パラメータに値を入れていきます。
例えば、次のようなリクエストが作れるでしょう。

https://api.foursquare.com/v2/users/self/checkins?limit=10&sort=newestfirst&oauth_token=ACCESS_TOKEN&locate=ja&v=20190917

これを実行すると、以下のように結果が返ります(チェックインは10個のうち1個のみを残して後は省略してあります)。

{
	"meta": {
		"code": 200,
		"requestId": "5d805b16d03360002c4d3db3"
	},
	"notifications": [
		{
			"type": "notificationTray",
			"item": {
				"unreadCount": 1
			}
		}
	],
	"response": {
		"checkins": {
			"count": 220,
			"items": [
				{
					"id": "5d468bd2d6ce8000074bb5d3",
					"createdAt": 1564904402,
					"type": "checkin",
					"entities": [],
					"shout": "LAWSON presents TrySail Live Tour 2019 \"The TrySail Odyssey\" 千秋楽",
					"timeZoneOffset": 540,
					"venue": {
						"id": "5ae3f0d2345cbe002c639c12",
						"name": "幕張イベントホール",
						"location": {
							"address": "美浜区中瀬2-1",
							"crossStreet": "幕張メッセ",
							"lat": 35.6482004435167,
							"lng": 140.0346940755844,
							"labeledLatLngs": [
								{
									"label": "display",
									"lat": 35.6482004435167,
									"lng": 140.0346940755844
								}
							],
							"postalCode": "261-0023",
							"cc": "JP",
							"city": "千葉市",
							"state": "千葉県",
							"country": "日本",
							"formattedAddress": [
								"美浜区中瀬2-1 (幕張メッセ)",
								"千葉市, 千葉県",
								"261-0023"
							]
						},
						"categories": [
							{
								"id": "4bf58dd8d48988d171941735",
								"name": "イベントスペース",
								"pluralName": "イベントスペース",
								"shortName": "イベントスペース",
								"icon": {
									"prefix": "https://ss3.4sqi.net/img/categories_v2/building/eventspace_",
									"suffix": ".png"
								},
								"primary": true
							}
						]
					},
					"likes": {
						"count": 0,
						"groups": []
					},
					"like": false,
					"isMayor": false,
					"photos": {
						"count": 0,
						"items": []
					},
					"posts": {
						"count": 0,
						"textCount": 0
					},
					"comments": {
						"count": 0
					},
					"source": {
						"name": "Swarm for Android",
						"url": "https://www.swarmapp.com"
					}
				}
			]
		}
	}
}

・meta
code:HTTPコード
requestId:リクエスト毎に固有のID

・notifications:通知に関する情報?

・response:レスポンス本体
checkins
count:チェックイン回数(APIで取得した回数ではない)
items:<array>
id:チェックイン毎に固有のID
createdAt:チェックイン作成時のUNIX時刻
type:checkin・shout・vemielessのいずれかが入る
shout:アプリで「何をしていますか?」の欄に入力した文言がここに入る
timeZoneOffset:UTCで同時刻になるまでのオフセット(分単位)
venue:べニューの情報が格納される(後述)
likes:このチェックインにいいねされた数。groupsにはfriendsとothersが入る。
like:いいねしているかどうか
isMayor:メイヤーかどうか
photos:このチェックイン時に写真を添付していればcount・itemsに値が入る
comments:コメントをすればcount・itemsに値が入る
source:チェックインに使用したアプリの名前とURLが入る

・venueについて:venueはチェックインする施設などを示す。
id:venue固有のID
name:名前
location:場所を示す
address:住所
crossStreet:施設名?建物名?
lat・lng:緯度経度
postalCode:郵便番号
city・state・country:市区町村・都道府県・国
formattedAddress:フォーマットされた住所
categories:<array>カテゴリー

ざっくり書いたので、上のJSONか自分自身のリクエストを見て確認することをお勧めします。

ここで、ヘッダーを見てみましょう。「x-ratelimit-remaining: 499」となっています。
x-ratelimit-remainingはAPIを利用できる残り回数を示します。x-ratelimit-resetはその規制がリセットされる時刻をUNIX時刻で示しています。

べニューの検索

次に、ベニューを検索してみましょう。
エンドポイントはhttps://api.foursquare.com/v2/venues/searchで、ドキュメントはここです。

さて、このAPIではユーザー認証は必須ではありません。ユーザーレス認証を使うこともできます。
ユーザーレス認証は、oauth_tokenパラメータを削除し、client_idとclient_secretを与えることで実行できます。
パラメータにはllとnearのどちらかが必須です。intentは必須ではありませんが、これにcheckinを指定することが推奨されています。

では、新宿駅の緯度と経度を指定して、ベニューを検索してみましょう。リクエストは以下のようにしました。

https://api.foursquare.com/v2/venues/search?ll=35.689738,139.700391&limit=5&oauth_token=ACCESS_TOKEN&intent=checkin&locate=ja&v=20190917

返ってきた結果は次のようになっています(responseのみ抜き出し)。

{
	"response": {
		"venues": [
			{
				"id": "4b62a048f964a520454d2ae3",
				"name": "JR 新宿駅",
				"location": {
					"address": "新宿3-38-1",
					"lat": 35.69033220321519,
					"lng": 139.70043356646298,
					"distance": 66,
					"postalCode": "160-0022",
					"cc": "JP",
					"neighborhood": "西新宿",
					"city": "東京",
					"state": "東京都",
					"country": "日本",
					"formattedAddress": [
						"新宿3-38-1",
						"新宿区, 東京都",
						"160-0022"
					]
				},
				"categories": [
					{
						"id": "4bf58dd8d48988d129951735",
						"name": "鉄道駅",
						"pluralName": "鉄道駅",
						"shortName": "鉄道駅",
						"icon": {
							"prefix": "https://ss3.4sqi.net/img/categories_v2/travel/trainstation_",
							"suffix": ".png"
						},
						"primary": true
					}
				],
				"referralId": "v-1568696552",
				"hasPerk": false
			},
			{
				"id": "4b5bab10f964a520e00e29e3",
				"name": "京王 新宿駅 (KO01)",
				"location": {
					"address": "西新宿1-1-4",
					"lat": 35.69043229540523,
					"lng": 139.6989077928515,
					"distance": 154,
					"postalCode": "160-0023",
					"cc": "JP",
					"city": "東京",
					"state": "東京都",
					"country": "日本",
					"formattedAddress": [
						"西新宿1-1-4",
						"新宿区, 東京都",
						"160-0023"
					]
				},
				"categories": [
					{
						"id": "4bf58dd8d48988d129951735",
						"name": "鉄道駅",
						"pluralName": "鉄道駅",
						"shortName": "鉄道駅",
						"icon": {
							"prefix": "https://ss3.4sqi.net/img/categories_v2/travel/trainstation_",
							"suffix": ".png"
						},
						"primary": true
					}
				],
				"referralId": "v-1568696552",
				"hasPerk": false
			},
			{
				"id": "52c8ede4498ee1e3dc7707c5",
				"name": "KIOSK 新宿北通路店",
				"location": {
					"address": "新宿3-38-1",
					"crossStreet": "JR新宿駅 北側地下通路15・16番線ホーム階段下",
					"lat": 35.69111249898078,
					"lng": 139.70026016235352,
					"labeledLatLngs": [
						{
							"label": "display",
							"lat": 35.69111249898078,
							"lng": 139.70026016235352
						}
					],
					"distance": 153,
					"postalCode": "160-0022",
					"cc": "JP",
					"city": "東京",
					"state": "東京都",
					"country": "日本",
					"formattedAddress": [
						"新宿3-38-1 (JR新宿駅 北側地下通路15・16番線ホーム階段下)",
						"新宿区, 東京都",
						"160-0022"
					]
				},
				"categories": [
					{
						"id": "4d954b0ea243a5684a65b473",
						"name": "コンビニ",
						"pluralName": "コンビニエンスストア",
						"shortName": "コンビニ",
						"icon": {
							"prefix": "https://ss3.4sqi.net/img/categories_v2/shops/conveniencestore_",
							"suffix": ".png"
						},
						"primary": true
					}
				],
				"referralId": "v-1568696552",
				"hasPerk": false
			},
			{
				"id": "56962f6a498ec0def40b137d",
				"name": "バスタ新宿",
				"location": {
					"address": "千駄ヶ谷5-24-55",
					"lat": 35.68868393158948,
					"lng": 139.70087707042694,
					"labeledLatLngs": [
						{
							"label": "display",
							"lat": 35.68868393158948,
							"lng": 139.70087707042694
						}
					],
					"distance": 125,
					"postalCode": "151-0051",
					"cc": "JP",
					"city": "東京",
					"state": "東京都",
					"country": "日本",
					"formattedAddress": [
						"千駄ヶ谷5-24-55",
						"渋谷区, 東京都",
						"151-0051"
					]
				},
				"categories": [
					{
						"id": "4bf58dd8d48988d1fe931735",
						"name": "バスターミナル",
						"pluralName": "バスターミナル",
						"shortName": "バスターミナル",
						"icon": {
							"prefix": "https://ss3.4sqi.net/img/categories_v2/travel/busstation_",
							"suffix": ".png"
						},
						"primary": true
					}
				],
				"referralId": "v-1568696552",
				"hasPerk": false
			},
			{
				"id": "5bc98634e96d0c002c36d252",
				"name": "PUBLIC TOKYO",
				"location": {
					"address": "新宿3-38-2",
					"crossStreet": "ルミネ新宿2 3F",
					"lat": 35.689581,
					"lng": 139.700937,
					"labeledLatLngs": [
						{
							"label": "display",
							"lat": 35.689581,
							"lng": 139.700937
						}
					],
					"distance": 52,
					"postalCode": "160-0022",
					"cc": "JP",
					"city": "東京",
					"state": "東京都",
					"country": "日本",
					"formattedAddress": [
						"新宿3-38-2 (ルミネ新宿2 3F)",
						"新宿区, 東京都",
						"160-0022"
					]
				},
				"categories": [
					{
						"id": "4bf58dd8d48988d106951735",
						"name": "紳士服",
						"pluralName": "紳士服店",
						"shortName": "紳士服",
						"icon": {
							"prefix": "https://ss3.4sqi.net/img/categories_v2/shops/apparel_men_",
							"suffix": ".png"
						},
						"primary": true
					}
				],
				"referralId": "v-1568696552",
				"hasPerk": false
			}
		],
		"confident": false
	}
}

このように、

  • JR 新宿駅
  • 京王 新宿駅 (KO01)
  • KIOSK 新宿北通路店
  • バスタ新宿
  • PUBLIC TOKYO

の5つのベニューが取得できました。

distanceには、指定した緯度経度からの距離[m]が示されます(多分)。配列内で距離が近い順に要素が並ぶわけではないようです。

チェックインする

チェックインに用いるエンドポイントはhttps://api.foursquare.com/v2/checkins/addで、ドキュメントはここです。
パラメータにvenueIdは必須です。venueIdは検索もしくは履歴等から調べます。例えば、「JR 新宿駅」のvenueIdは”4b62a048f964a520454d2ae3″です(上のJSONを参照してください)。
shout:メッセージ(最大140字)
broadcast:チェックインを知らせる相手を選びます。カンマ区切りで複数選択できます。private:非公開、public:公開、followers:フォロワーに公開、facebook/twitter:それぞれのSNSに公開(連携している場合、投稿)。デフォルトはpublicです。
ll:緯度と経度をカンマ区切りで与えます。
llAcc:緯度と経度の精度(メートル単位)
alt:高度をカンマ区切りで与えます。
altAcc:高度の精度(メートル単位)
なお、緯度・経度・高度に関する情報は任意です。

では、試しに「JR 新宿駅」にチェックインしてみましょう。
リクエストを作成します。※locateとありますが、正しくはlocaleです。間違えました。

実行すると、JSONデータが返ります。

{
	"response": {
		"checkin": {
			"id": "5d8071b6c2cec90008f42a2e",
			"createdAt": 1568698806,
			"type": "checkin",
			"private": true,
			"visibility": "private",
			"entities": [],
			"shout": "テスト",
			"timeZoneOffset": 540,
			"editableUntil": 1568785206000,
			"user": {
				"id": "95874426",
				"firstName": "N",
				"lastName": " ",
				"gender": "none",
				"relationship": "self",
				"photo": {
					"prefix": "https://fastly.4sqi.net/img/user/",
					"suffix": "/95874426_OJhlt4qn_q4y0avhK40s3MuCEdZaFWlNbQay_s3a8I2i5I44-Pqc_a2ih5f7qfKsPso0PLKsp.jpg"
				},
				"coinBalance": 3555
			},
			"venue": {
				"id": "4b62a048f964a520454d2ae3",
				"name": "JR Shinjuku Station (JR 新宿駅)",
				"location": {
					"address": "新宿3-38-1",
					"lat": 35.69033220321519,
					"lng": 139.70043356646298,
					"postalCode": "160-0022",
					"cc": "JP",
					"neighborhood": "西新宿",
					"city": "Tokyo",
					"state": "Tokyo",
					"country": "Japan",
					"formattedAddress": [
						"新宿3-38-1",
						"新宿区, 東京都",
						"160-0022"
					]
				},
				"categories": [
					{
						"id": "4bf58dd8d48988d129951735",
						"name": "Train Station",
						"pluralName": "Train Stations",
						"shortName": "Train Station",
						"icon": {
							"prefix": "https://ss3.4sqi.net/img/categories_v2/travel/trainstation_",
							"suffix": ".png"
						},
						"primary": true
					}
				],
				"reasons": {
					"count": 1,
					"items": [
						{
							"summary": "You're here!",
							"type": "general",
							"reasonName": "hereNowReason",
							"target": {
								"type": "navigation",
								"object": {
									"id": "5d8071b6c2cec90008f42a62",
									"type": "checkinDetail",
									"target": {
										"type": "path",
										"url": "/checkins/5d8071b6c2cec90008f42a2e"
									},
									"ignorable": false
								}
							}
						}
					]
				}
			},
			"source": {
				"name": "test-4sq",
				"url": "https://1507t.xyz"
			},
			"photos": {
				"count": 0,
				"items": []
			},
			"posts": {
				"count": 0,
				"textCount": 0
			},
			"checkinShortUrl": "https://www.swarmapp.com/15_07_/checkin/5d8071b6c2cec90008f42a2e?s=WSvjLPcZQ_w_V-Tv6gTLFB3ruTE",
			"likes": {
				"count": 0,
				"groups": []
			},
			"like": false,
			"comments": {
				"count": 0,
				"items": []
			},
			"isMayor": false,
			"score": {
				"total": 4,
				"scores": [
					{
						"icon": "https://ss1.4sqi.net/img/points/coin_icon_lock.png",
						"message": "Off the grid check-in. If you had shared with friends you would have earned 9 coins!",
						"points": 1
					},
					{
						"icon": "https://ss1.4sqi.net/img/points/coin_icon_clock.png",
						"target": {
							"type": "path",
							"object": {
								"url": "/users/self/historysearch?venueIds=4b62a048f964a520454d2ae3&displayText=JR+Shinjuku+Station"
							}
						},
						"message": "You haven't checked in to JR Shinjuku Station since June '18.",
						"points": 1
					},
					{
						"icon": "https://ss1.4sqi.net/img/points/coin_icon_magnify.png",
						"target": {
							"type": "path",
							"object": {
								"url": "/users/self/historysearch?geoId=10004173&displayText=Nishishinjuku"
							}
						},
						"message": "Your last check-in in Nishishinjuku was in September '18 at Shinjuku Station.",
						"points": 1
					},
					{
						"icon": "https://ss1.4sqi.net/img/points/coin_icon_streak.png",
						"message": "Keep checking in to places inside Shinjuku Station for more coins!",
						"points": 1
					}
				]
			}
		},
		"notifications": [
			{
				"type": "message",
				"item": {
					"message": "You've been here 6 times (including 1 private check-in).",
					"entities": [
						{
							"indices": [
								17,
								18
							],
							"type": "count",
							"value": 6
						}
					]
				},
				"alert": false
			},
			{
				"type": "insights",
				"item": {
					"insights": {
						"count": 4,
						"items": [
							{
								"type": "pointsReward",
								"image": "https://ss1.4sqi.net/img/points/coin_icon_lock_120.png",
								"title": "Off the grid check-in. If you had shared with friends you would have earned 9 coins!",
								"points": {
									"image": {
										"prefix": "https://ss1.4sqi.net/img/points/coin_icon_lock_",
										"sizes": [
											80,
											120
										],
										"name": ".png",
										"key": "lock"
									},
									"message": "Off the grid check-in. If you had shared with friends you would have earned 9 coins!",
									"points": 1
								}
							},
							{
								"type": "pointsReward",
								"image": "https://ss1.4sqi.net/img/points/coin_icon_clock_120.png",
								"title": "You haven't checked in to JR Shinjuku Station since June '18.",
								"points": {
									"image": {
										"prefix": "https://ss1.4sqi.net/img/points/coin_icon_clock_",
										"sizes": [
											80,
											120
										],
										"name": ".png",
										"key": "clock"
									},
									"target": {
										"type": "path",
										"object": {
											"url": "/users/self/historysearch?venueIds=4b62a048f964a520454d2ae3&displayText=JR+Shinjuku+Station"
										}
									},
									"message": "You haven't checked in to JR Shinjuku Station since June '18.",
									"points": 1
								}
							},
							{
								"type": "pointsReward",
								"image": "https://ss1.4sqi.net/img/points/coin_icon_magnify_120.png",
								"title": "Your last check-in in Nishishinjuku was in September '18 at Shinjuku Station.",
								"points": {
									"image": {
										"prefix": "https://ss1.4sqi.net/img/points/coin_icon_magnify_",
										"sizes": [
											80,
											120
										],
										"name": ".png",
										"key": "magnify"
									},
									"target": {
										"type": "path",
										"object": {
											"url": "/users/self/historysearch?geoId=10004173&displayText=Nishishinjuku"
										}
									},
									"message": "Your last check-in in Nishishinjuku was in September '18 at Shinjuku Station.",
									"points": 1
								}
							},
							{
								"type": "pointsReward",
								"image": "https://ss1.4sqi.net/img/points/coin_icon_streak_120.png",
								"title": "Keep checking in to places inside Shinjuku Station for more coins!",
								"points": {
									"image": {
										"prefix": "https://ss1.4sqi.net/img/points/coin_icon_streak_",
										"sizes": [
											80,
											120
										],
										"name": ".png",
										"key": "streak"
									},
									"message": "Keep checking in to places inside Shinjuku Station for more coins!",
									"points": 1
								}
							}
						]
					}
				},
				"alert": true
			}
		],
		"notificationsOrder": [
			"special",
			"score",
			"leaderboard",
			"replies"
		]
	}
}

ここで見るべき点は、”private”: true・ “visibility”: “private”となっている点です。今回は、broadcastにprivateを指定したのでこのようになっています。
また、sourceには今回使用したアプリの名前とURLが入っています。

公式アプリで確認すると、チェックインが反映されているのが分かります。

 

ちなみに、チェックインを削除するAPIはありません。公式を使わなければなりません。

終わりに

他にも様々なAPIがあります。
今回はswarm寄りの説明でしたが、APIにはfoursquareで使われるようなものも多くあります。
組み合わせて何か新しいものが作れたらいいですね。

コメント

タイトルとURLをコピーしました