2026年6月6日
2026年6月6日
DNSimple の API 操作方法|トークンの準備、レコードの更新・削除、レート制限とエラーへの備え
DNSimple の API(v2)でレコードを操作するには、アクセストークンとアカウント ID を用意し、https://api.dnsimple.com/v2/{アカウントID}/zones/{ゾーン名}/records に対して一覧・追加・更新・削除のリクエストを送ります。この記事では、curl で一通りの操作を試したあと、何度実行しても同じ結果になり、レート制限やエラーにも耐える Python スクリプトにまとめるところまでを説明します。WordPress サイトの IP 変更や、検証用サブドメインの作成・削除を自動化したい人を想定しています。
API の仕様、トークンの種類、レート制限の値は変わることがあります(2026年10月現在)。この記事では仕組みと考え方を中心に説明するので、項目名や上限値は DNSimple の開発者向けドキュメントで確認してください。管理画面でのレコード追加や、複数ドメインの棚卸しは別の記事で扱っているため、ここでは API での更新・削除と自動化の作法に絞ります。
準備:トークン、アカウント ID、試す環境
トークンの種類を選ぶ
DNSimple のアクセストークンには、アカウントに紐づくものと、ユーザーに紐づくものがあります。スクリプトや CI から使うなら、担当者の異動や退職の影響を受けにくいアカウント単位のトークンが向いています。
トークンは管理画面で発行し、次の点を守ります。
- スクリプトや Git には書かず、環境変数や CI のシークレットで渡す
- 用途ごとに別のトークンを発行し、使わなくなったら失効させる
- ログやエラーメッセージにトークンが出力されないようにする
アカウント ID を確かめる
URL に入れるのはアカウント ID です。トークンを付けて whoami を呼ぶと確認できます。
export DNSIMPLE_TOKEN="your-api-token"
curl -s -H "Authorization: Bearer ${DNSIMPLE_TOKEN}" -H "Accept: application/json" \
https://api.dnsimple.com/v2/whoami | jq '.data'
アカウント単位のトークンなら、応答の account にアカウントの情報が入ります。
最初はサンドボックスで試す
DNSimple は本番とは別にサンドボックス環境を用意しており、API の接続先は https://api.sandbox.dnsimple.com/v2 です。サンドボックスでは本番とは別にアカウントとトークンを作る必要があります。更新や削除のスクリプトは、接続先を環境変数で切り替えられるように書き、サンドボックスで動きを確かめてから本番に向けます。
curl で一通り操作する
以下では、接続先とアカウント ID を変数にしておきます。
export DNSIMPLE_API="https://api.dnsimple.com/v2"
export DNSIMPLE_ACCOUNT="your-account-id"
H_AUTH="Authorization: Bearer ${DNSIMPLE_TOKEN}"
一覧を取得する
curl -s -H "$H_AUTH" -H "Accept: application/json" \
"${DNSIMPLE_API}/${DNSIMPLE_ACCOUNT}/zones/example.com/records?type=A&per_page=100" \
| jq -r '.data[] | [.id, .name, .type, .content, .ttl] | @tsv'
更新や削除にはレコードの id が必要なので、まず一覧から目的のレコードの ID を取ります。名前はゾーンからの相対名で、頂点は空文字です。一覧はページ単位で返り、応答の pagination に総ページ数が入っています。レコードの多いゾーンでは、1ページ目だけを見て「ない」と判断しないよう、全ページを確認します。
更新する
curl -s -X PATCH -H "$H_AUTH" -H "Content-Type: application/json" -H "Accept: application/json" \
-d '{"content":"203.0.113.20","ttl":300}' \
"${DNSIMPLE_API}/${DNSIMPLE_ACCOUNT}/zones/example.com/records/12345678"
PATCH では、送った項目だけが変わります。名前やタイプを変えたい場合は、更新ではなく新しいレコードを追加してから古いものを削除するほうが、意図がはっきりして確認もしやすくなります。
削除する
curl -s -o /dev/null -w "%{http_code}\n" -X DELETE -H "$H_AUTH" \
"${DNSIMPLE_API}/${DNSIMPLE_ACCOUNT}/zones/example.com/records/12345678"
成功すると本文のない応答(204)が返ります。削除は取り消せないので、実行前に一覧で ID と内容を照合し、ゾーンのレコードを書き出して控えておきます。
つまずきやすいエラーと原因
API からエラーが返ったときは、HTTP ステータスと応答本文のメッセージを見て、次の順に確かめます。
- 401: トークンが間違っているか、失効しています。環境変数に余計な空白や改行が入っていないかも確認します。
- 404: アカウント ID かゾーン名、レコード ID が違います。本番のトークンでサンドボックスの URL を呼ぶ(またはその逆)と、存在しない扱いになります。
- 400 番台の入力エラー: 本文に、どの項目が不正かが書かれています。相対名で書くべき名前に
www.example.comと完全修飾名を入れた、MX の優先度を指定していない、などがよくある原因です。 - 429: レート制限に達しています。次の節の方法で待ってから再試行します。
レート制限を前提に書く
DNSimple の API には、一定時間あたりのリクエスト数の上限があります。応答ヘッダーの X-RateLimit-Limit(上限)、X-RateLimit-Remaining(残り)、X-RateLimit-Reset(リセットされる時刻)を見ると、今の状況がわかります。
多数のドメインを一度に処理するスクリプトでは、429 が返ったらリセット時刻まで待って再試行する処理を入れておきます。また、一覧の取得を毎回行わずに1回の結果を使い回す、変更が不要なレコードには書き込まない、といった工夫で、そもそもリクエストの数を減らせます。
Python で「違うときだけ書き換える」スクリプトにする
ここまでの操作を、何度実行しても同じ結果になるスクリプトにまとめます。既定では何も書き換えず、実行予定の操作を表示するだけにしてあり、--apply を付けたときだけ変更します。
import os
import sys
import time
import requests
API = os.environ.get("DNSIMPLE_API", "https://api.sandbox.dnsimple.com/v2")
ACCOUNT = os.environ["DNSIMPLE_ACCOUNT"]
session = requests.Session()
session.headers.update({
"Authorization": f"Bearer {os.environ['DNSIMPLE_TOKEN']}",
"Accept": "application/json",
})
def call(method, path, **kwargs):
for _ in range(5):
res = session.request(method, f"{API}/{ACCOUNT}{path}", timeout=30, **kwargs)
if res.status_code == 429:
reset = int(res.headers.get("X-RateLimit-Reset", time.time() + 60))
time.sleep(max(1, reset - int(time.time())))
continue
if res.status_code >= 400:
sys.exit(f"{method} {path} -> {res.status_code}: {res.text[:300]}")
return res.json() if res.content else None
sys.exit("レート制限が解除されませんでした")
def find_records(zone, name, rtype):
found, page = [], 1
while True:
res = call("GET", f"/zones/{zone}/records",
params={"type": rtype, "per_page": 100, "page": page})
found += [r for r in res["data"] if r["name"] == name]
if page >= res["pagination"]["total_pages"]:
return found
page += 1
def upsert(zone, name, rtype, content, ttl, apply):
label = f"{name or '@'}.{zone} {rtype}"
records = find_records(zone, name, rtype)
if len(records) > 1:
sys.exit(f"{label} が複数あります。管理画面で確認してください")
if records and records[0]["content"] == content and records[0]["ttl"] == ttl:
print(f"変更なし: {label}")
return
print(f"{'更新' if records else '追加'}: {label} -> {content} (TTL {ttl})")
if not apply:
return
if records:
call("PATCH", f"/zones/{zone}/records/{records[0]['id']}",
json={"content": content, "ttl": ttl})
else:
call("POST", f"/zones/{zone}/records",
json={"name": name, "type": rtype, "content": content, "ttl": ttl})
if __name__ == "__main__":
apply = "--apply" in sys.argv
upsert("example.com", "", "A", "203.0.113.20", 300, apply)
upsert("example.com", "staging", "A", "203.0.113.30", 300, apply)
このスクリプトのポイントは次の3つです。
- 先に読んでから書く: 今の値と同じなら書き込まないので、cron で定期実行してもリクエストと変更履歴が無駄に増えません。
- 同じ名前のレコードが複数あれば止まる: A レコードを複数の IP で運用している場合などに、意図せず1件だけを書き換える事故を防ぎます。複数値を扱いたい場合は、この部分を用途に合わせて作り直します。
- 既定はサンドボックス・表示のみ: 本番に向けるには
DNSIMPLE_APIを明示し、--applyを付ける、という2段階にしてあります。
変更後に確かめる
API が成功を返しても、それは DNSimple に登録されたという意味です。実際に応答が変わったかは、DNSimple のネームサーバーに直接問い合わせて確認します。
dig +short example.com NS
dig +short staging.example.com A @ns1.dnsimple.com
ネームサーバー名は、1行目の結果に出たものを使います。権威サーバーで新しい値が返っていれば登録は反映されており、利用者の手元に届くまでの時間は、変更前の TTL に左右されます。
まとめ
- DNSimple の API はアカウント ID を含む URL で操作し、自動化にはアカウント単位のトークンを環境変数で渡します。
- 更新と削除にはレコード ID が必要なので、一覧を全ページ確認して ID と内容を照合してから実行します。
- エラーは 401・404・入力エラー・429 の順に原因を切り分け、429 は応答ヘッダーのリセット時刻まで待って再試行します。
- スクリプトは「読んでから書く」「複数あれば止まる」「既定は表示のみ」にしておくと、繰り返し実行しても安全です。
- 変更後は DNSimple のネームサーバーに直接問い合わせて反映を確かめます。