【EQUEST_DENIED】Places APIの検索エラーを調査した

## まえがき
経験が浅いため、誤りがあればご指摘いただけると幸いです。

## Places APIでIPに関するEQUEST_DENIEDのエラーが出た
今回遭遇したのは以下のIP制限に関するエラーです。

```
{
   "error_message" : "This IP, site or mobile application is not authorized to use this API key. Request received from IP address 133.206.49.128, with empty referer",
   "html_attributions" : [],
   "results" : [],
   "status" : "REQUEST_DENIED"
}
```
iOS、AndroidでGoogleの地図を表示していて、
そこで検索を実行すると上記のエラーレスポンスが返ってきました。

## 確認環境
- MacOS Sonoma 14.4.1
- AndroidStudio Koala(2024.1.1)
- XCode 16.2
- 動作確認は実機ではなく、シュミレータ・エミュレータで実施

## アプリでの検索機能の実装について
アプリでは以下の手順でMAP表示と検索を行っていました。
Androidの例で説明します。

### APIキーの設定
1. Google Consoleでプロジェクトを新規登録
1. クレジットの登録（無料枠でも、Places APIを使用するには登録が必要なため）
1. Places APIとMaps SDK for Androidを有効化
1. アプリケーションの制限（Androidアプリ、パッケージ名とSHA-1）を設定
1. APIの制限では、手順3で有効にした２つのみ選択した状態

### アプリの実装
1. .propertiesファイルにAPIキーを記載
1. Manifestファイルで手順1のキーを参照するように記載
1. 検索文字列の入力箇所とボタンを実装
1. 検索実行で、以下URLの末尾に検索文字列とAPIキーを付与してリクエスト実行
```
https://maps.googleapis.com/maps/api/place/textsearch/json
```

このリクエストの結果が、冒頭に書いた以下のエラーです。
```
{
   "error_message" : "This IP, site or mobile application is not authorized to use this API key. Request received from IP address 133.206.49.128, with empty referer",
   "html_attributions" : [],
   "results" : [],
   "status" : "REQUEST_DENIED"
}
```

## エラーの原因はアプリケーションの制限だった
APIキーに対して、GoogleConsoleで制限をかけていました。
Androidを例にすると、
Androidアプリにチェックを入れて、パッケージ名とSHA-1を指定していました。

![2025-05-31 23.29.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3805527/55a3dab9-e376-4998-bc5c-737f9ab1676d.png)

この制限を「なし」に変更すると、エラーなく検索レスポンスが返ってきました。

### アプリケーションの制限が機能していなかったのか
Maps SDK for Android（iOS）による地図の表示はできていたので、
設定は正しくできていました。

試しにパッケージ名を無効なものに変更すると、
地図が表示されなくなることも確認しました。

### 根本的な原因は実装方法にあった

例えばAndroidアプリでは以下のような実装になっていました。
textsearch/jsonのURLを作成してGET方式でリクエストをしています。

:::note warn
実際はOkHttpClientではなくバックエンドサーバからコールしていますが、
分かりやすいように仮で実装しています。
:::

```.java
private void searchPlace(String query) {
    String apiKey = getApiKeyMethod();
    String url = "https://maps.googleapis.com/maps/api/place/textsearch/json?query=" + Uri.encode(query) + "&key=" + apiKey;

    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder().url(url).build();

    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) { ... }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
        
        }
    });
}
```

Places API（REST版） は WebベースのAPIで、
IPアドレスやリファラーによる制限が基本です。

パッケージ名やバンドルIDでの制限付きのキーは、
スマホアプリ用として設定されているためREST版では拒否されてしまいます。

:::note info
REST（Representational State Transfer）APIはWebシステム同士の通信やデータ交換を行うためのルールや規約の集まりで、HTTPメソッド（GET, POST, PUT, DELETEなど）を利用する方式
:::

パッケージ名やバンドルIDの制限付きのキーを使用したい場合は、
実装を変更する必要があります。

## 実装の修正方針
エラーになる現在の実装ではPlaces API（REST版）を使用する方式のため、
Places API（Places SDK for Android(iOS))を使用した実装に修正する必要があります。

### Places API（Places SDK for Android(iOS))とは？


ここが理解に苦しんだのですが、スマホアプリ用の場合は以下です。
- 地図表示に使用するAPI：Maps SDK for Android(iOS)
- 検索に使用するAPI：Places API   

ここで、Places APIという名称はWeb・スマホアプリ共通の名称であって、
スマホアプリでは
「Places API」> 「Places API for Android(iOS）」という括りになるようです。

Web用の場合は、
- 地図表示に使用するAPI：Maps Javascript API
- 地図表示に使用するAPI：Places API

と言った感じでPlaces APIのままで呼ばれることが多いようです。

:::note warn
先ほどのPlaces API（REST版）という呼び方はChatGPTに質問した時に、スマホアプリとの対比のために勝手につけたのかなと思っています。
（そのような書き方を他で見つけられなかったので）
:::

### Places API（Places SDK for Android(iOS))による検索実装
こちらは現在調査中のため、動作確認ができたら記事を更新します！

## あとがき
GoogleのMap機能の知識が乏しく、
エラーの原因を突き止めるのにかなり苦戦しました。
まだ疑問に残るところもありますが、少しずつ理解を深めたいと思います。