カーソルが空になるまで観測値エンドポイントのページを順に取得します。2025年11月3日から2026年10月23日までのUSD/IDR日次レートは249日分で、1ページ100行なら3回のリクエストで済みます。どのアカウントでも系列の先頭まで遡れます。
APIでバックフィルする
観測値エンドポイントは、日付範囲、ページ上限、カーソルを受け付けます。為替レートは /v1/fx/rates/{base}/observations、CPIは /v1/economics/{country}/cpi/observations、コモディティのバーは /v1/commodities/spot/{commodity}/{currency}/bars を使います。日付パラメーターは、エンドポイントによって start と end、または start_date と end_date です。各レスポンスには next_cursor があり、最後のページでは null になります。
- APIキーを設定 無料のAPIキーを取得し、環境変数
NARWHAL_API_KEYに保存します。スクリプトはその変数を読み取り、Authorization: Bearer YOUR_KEYとしてキーをhttps://api.narwhalapi.comに送信します。 - 系列と日付範囲を指定する この例では、2025年11月1日から2026年10月23日までのUSD/IDRを、
limitを100にしてリクエストします。直前のレスポンスで返されたカーソルを送るときも、すべてのページで同じフィルターを維持してください。 - 各ページをファイルに保存する スクリプトを使って、各観測値をJSON行として
usd_idr.jsonlに追記します。next_cursorに値がある間は次のリクエストを送り、値が空になったら止まります。import json, os import requests H = {"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"} params = { "currencies": "IDR", "start_date": "2025-11-01", "end_date": "2026-10-23", "limit": 100, } rows, pages, cursor = 0, 0, None with open("usd_idr.jsonl", "a") as out: while True: if cursor: params["cursor"] = ( cursor # the cursor carries the filters; resend them unchanged ) r = requests.get( "https://api.narwhalapi.com/v1/fx/rates/USD/observations", params=params, headers=H, timeout=15, ) r.raise_for_status() body = r.json() for day in body["observations"]: out.write(json.dumps(day) + "\n") rows += 1 pages += 1 cursor = body.get("next_cursor") if not cursor: break print(pages, "pages,", rows, "days") - 返された日付を確認する 2025年11月1日から2026年10月23日までの範囲では、スクリプトは
3 pages, 249 daysと出力します。レスポンスはリクエストした開始日より後から始まることがあるため、最後の日付だけでなく、最初に保存された日付も確認してください。
スクリプトはページ数と日数を別々に数えます。次のページをリクエストする前に各観測値を書き込み、カーソルのループが終わったあとで合計を出力します。ファイルは追記モードで開かれ、保存される各行には観測値が1件入ります。リクエストは、USDを基準通貨とするIDR系列を読み取ります。
各ページはクレジットに対して1回の呼び出しとして数えられ、無料クレジットのアカウントには1分あたり50リクエストの上限があります。多数の系列をバックフィルするときは、各系列が数ページで済む場合でも、1分あたりの上限を超えないようにページ間に間隔を空ける必要があります。
レスポンスには X-RateLimit-Remaining が含まれます。ページのリクエストが 429 rate_limit_exceeded を返したら、次のページをリクエストする前に Retry-After の値だけ待ちます。402 insufficient_credits は残高を使い切ったことを意味するので、続行する前にクレジットを追加してください。エラーは application/problem+json で返され、status、code、detail、request_id を含みます。バックフィルを続けられるかどうかは、エラーコードを読んで判断します。
シートやノーコードのワークフローを使う
数百行程度ならSheetsへのインポートで十分です。1回限りのバックフィルなら、ページをループするワークフローよりスクリプトのほうが簡単です。観測値を届いた順に書き込みたいときはファイルを使う方法が向いています。Pythonの例は、バックフィルのあいだ行を追記し続けます。
n8n、Make、Zapierでもカーソルを使ってページをループできます。ワークフローは、カーソルを送るたびに同じ日付と系列のフィルターを送り、next_cursor が null になったら止める必要があります。継続的なフィードは、バックフィルから始めて毎日のデータ取得につなげられます。
バックフィルの方法を選ぶ
| 状況 | 適した方法 | 理由 |
|---|---|---|
| 1つの系列の1年分を1回だけ取得する | スクリプト | 範囲をページ送りで取得し、行をファイルに追記します |
| 多数の系列 | 系列をループし、ページ間でスリープを入れる | 間隔を空けることで、リクエストが1分あたりの上限内に収まります |
| 継続的なフィード | 一度バックフィルしてから、毎日取得する | バックフィルが、毎日の取得が始まる前の履歴を補います |
バックフィルが途中で止まったら?
最後に保存した日付から再開します。スクリプトは観測値を受け取るたびにJSON行を追記するため、ジョブが止まっても、すでに書き込まれた行はファイルに残ります。その保存済みの日付を、範囲を再開する位置として使ってください。
保存したカーソルは期限切れになることがあります。リクエストが 409 cursor_expired を返したら、カーソルなしでやり直し、最後に保存した日付から再開します。カーソルにはその範囲のフィルターが含まれているため、その範囲をページ送りしているあいだはフィルターを変えないでください。
1ページに何行までリクエストできますか?
limit はエンドポイントごとの上限まで設定できます。為替レートの観測値は366、経済データの観測値は1,000、コモディティのバーは500、経済カレンダーは100です。デフォルトは100です。limitはページの大きさを決めるだけなので、次のページがなくなるまで next_cursor をたどり続けます。
バックフィルがリクエストより遅い日付から始まるのはなぜですか?
すべてのアカウントで全履歴を取得できます。最初に返された日付が、系列が実際に始まる時点を示します。そのため、開始日を早く指定しても、それより前の行が返されるとは限りません。
計算例:2025年11月1日から2026年10月23日までのUSD/IDR
開始日を2025年11月1日、終了日を2026年10月23日、ページ上限を100としてUSD/IDRをリクエストします。返されたカーソルは同じフィルターで順にたどります。この範囲では、レスポンスは3ページにわたって249行を含むため、バックフィルのリクエストは3回です。
最初の行は2025年11月3日の月曜日です。2025年11月1日と2日は週末にあたるためです。2025年11月3日のUSD/IDRレートは16,710.3、2026年10月23日は18,107.8です。
| 項目 | 結果 |
|---|---|
| リクエストした範囲 | 2025年11月1日から2026年10月23日 |
| ページ上限 | 100行 |
| この範囲のページ数とリクエスト数 | 3ページ、リクエスト3回 |
| この範囲の行数 | 249 |
| 最初の観測値 | 2025年11月3日:1 USDあたり16,710.3 IDR |
| 最後の観測値 | 2026年10月23日:1 USDあたり18,107.8 IDR |
公開。