日本郵便の公式APIを使ってみた!さらにヤマト「マスタパック」をAWS Lambdaで爆速API化する挑戦【PHP/.NETコード付】

皆さん、こんにちは。okamoです。 今回は「こんなこといいな できたらいいな・・」シリーズ Vol.5をお届けします。

近年、物流やECシステム開発において、配送日の可視化や郵便番号からの住所検索の効率化は非常に重要なテーマとなっています。今回は、日本郵便が2025年5月より提供を開始した「郵便番号・デジタルアドレスAPI」の体験レポートと、その流れで着想を得て開発した「ヤマトマスタパックAPI」について詳しく解説します。


1. 日本郵便の「郵便番号・デジタルアドレスAPI」とは?

2025年5月より、日本郵便は「郵便番号」と「デジタルアドレス」のそれぞれから住所を取得できる公式APIの無料提供を開始しました。1つのAPIで両方の情報を引ける非常に画期的なサービスです。

法人向けのサービスですが、個人事業主でも登録して利用可能です。さっそくokamoも登録してみました。

登録から利用までの流れ

  1. ゆうID登録を行います。
  2. 「郵便番号・デジタルアドレス for Biz」にゆうIDでログインします。
  3. 組織設定画面で「個人事業主」として登録します。

郵便番号・デジタルアドレス for Bizの組織登録画面。個人事業主を選択し、屋号や屋号カナを入力するフォームが用意されています。

上記の画像の通り、国内法人のほかに「個人事業主」のタブが用意されています。

  1. システム情報を登録します(テスト用なので、API組込みシステムの第三者提供は「しない」に設定)。
  2. クライアントIDとクライアントシークレットが発行されます。
  3. curlを用いて、さっそくAPIの動作テストを行います。

APIリクエストとレスポンスの実例

まずはJWTトークンを取得します。(xxxはクライアントID、yyyはクライアントシークレットに変更して下さい)

# JWTトークンを取得
curl -X POST https://api.da.pf.japanpost.jp/api/v2/j/token \
   -H "Content-Type: application/json" \
   -H "x-forwarded-for: 127.0.0.1" \
   -d '{"grant_type":"client_credentials","client_id":"xxx","secret_key":"yyy"}' \
   | jq

成功すると、以下のようなレスポンスが返ってきます。(以下のzzzの部分がjwtトークン値です)

{
  "token": "zzz",
  "token_type": "jwt",
  "expires_in": 600,
  "scope": "J4"
}

次に、存在しない郵便番号「186002」(6桁)で住所検索を試してみます。(zzzの部分は取得したjwtトークン値に変更して下さい)

# 郵便番号 186002 (間違った番号) で 住所検索
curl -X GET "https://api.da.pf.japanpost.jp/api/v2/searchcode/186002?choikitype=1&searchtype=2" \
   -H "Authorization: Bearer zzz" \
   | jq

正しく「404」エラーのレスポンスが返ってきます。

{
  "request_id": "f8f0156d-3f04-4d81-afae-fa53d352665d",
  "error_code": "404-1034-0002",
  "message": "該当する郵便番号・事業所郵便番号が存在しません"
}

今度は、正しい郵便番号「1860002」(旧国立駅舎)で検索します。

# 郵便番号 1860002 で 住所検索
curl -X GET "https://api.da.pf.japanpost.jp/api/v2/searchcode/1860002?choikitype=1&searchtype=2" \
   -H "Authorization: Bearer zzz" \
   | jq

詳細な住所情報が含まれたレスポンスが取得できました。

{
  "page": 1,
  "limit": 1000,
  "count": 1,
  "searchtype": "zipcode",
  "addresses": [
    {
      "dgacode": null,
      "zip_code": "1860002",
      "pref_code": "13",
      "pref_name": "東京都",
      "pref_kana": "トウキョウト",
      "pref_roma": "TOKYO",
      "city_code": "13215",
      "city_name": "国立市",
      "city_kana": "クニタチシ",
      "city_roma": "KUNITACHI-SHI",
      "town_name": "東",
      "town_kana": "ヒガシ",
      "town_roma": "HIGASHI",
      "biz_name": null,
      "biz_kana": null,
      "biz_roma": null,
      "block_name": null,
      "other_name": null,
      "address": null,
      "corporate_number": null,
      "business_name": null,
      "business_name_kana": null,
      "tel_number": null,
      "url": null,
      "longitude": null,
      "latitude": null
    }
  ]
}

⚠️ 「ハマりポイント」

  • ハマりポイント1: 仕様書に明記されていませんが、リクエスト時のUser-Agentヘッダーが空文字だと403 Forbiddenエラーが返ります。何かしらのクライアント名をセットするようにしましょう。
  • ハマりポイント2: 管理画面に登録していないIPアドレスからアクセスすると、当然ながら以下のようにしっかり拒否されます。
    {"request_id":"741b3d40-70ac-4b1d-9ef0-0d7d412aef97","error_code":"403-1033-0002","message":"許可されていないIPアドレスからのアクセスです"}
    

2. 着想:ヤマト運輸の「マスタパック」もAPI化できないか?

日本郵便の素晴らしい公式APIを触っているうちに、「ヤマト運輸の配達日数(リードタイム)データも同じようにAPIで簡単につつければ最高なのに…」と考えました。

調べてみたところ、ヤマト運輸公式からはそのような一般公開APIは提供されていない様子。そこで「なければ自分で作ればいい!」と決意しました。

今回、システム開発をするにあたって参考にさせていただいた素晴らしい記事がこちらです。

ヤマト運輸のサービスで配送にかかる日数と最速の時間帯指定の情報を一括して取得したい(Kaminari Magazine)

この記事で解説されているリードタイム算出ルールを参考にして仕様書(blueprint.md)を書き上げ、AI(Claude 4.6 Sonnet)に実装をお願いしました。対話すること約2時間、一気にビルド、デプロイ完了し、動作確認することができました。

完成したソースコードはGitHubで公開しています:
👉 GitHub: YMSTTIME_DAT_API

Claudeとの会話生ログ(全文)もGitHubで公開しています:
👉 GitHub: prompt_history


3. ヤマトマスタパックAPIは「なぜ」必要か?

ヤマト運輸は、契約している事業者に対して「マスタパック」というCSVや固定長のテキストデータ(DATファイル)をダウンロード提供しています。しかし、これを実際のシステムに組み込むには、開発者にとって高いハードルが存在します。

課題開発現場での内容
ファイル形式が難解昔ながらの固定長テキスト。約20MB・約61万件ものレコードが含まれています。
毎リクエストでのパースは不可能ECのカート画面を開くたびに巨大なDATファイルをイチから読み込んで解析していては、処理が遅すぎて使い物になりません。
定期的な更新・運用コストヤマトから毎月ダウンロードし、DBにインポートするなどのデータ更新運用が必要です。

本APIが解決すること

今回作成したAPIは、AWS Lambdaのコールドスタート/ウォームスタート特性を活かし、起動時に全データをメモリ(Mapオブジェクト)へ一括ロードします。これにより、リクエスト時の検索処理速度はO(1)(数ミリ秒以下)の爆速応答を実現しました。

更新作業も、新しいDATファイルをローカルの YTCMST/ フォルダに上書きして cdk deploy するだけで完了します。コンテナイメージビルド時にデータが焼き込まれるシンプルな構成です。


4. AWS Lambda APIの強固なセキュリティ(公開APIにならない安心感)

GitHubにある手順通りにCDKデプロイすると、AWS Lambdaの「関数URL(Function URL)」が発行されます。

ここで「URLが漏洩したら、世界中から勝手にAPIを叩かれて高額な請求が来るのでは?」と不安になる方もいるかもしれませんが、ご安心ください。

このAPIは、AWSのIAMポリシー(AWS Signature Version 4: SigV4)によって厳重に保護されています。有効なAWSのアクセスキーとシークレットキーを使い、リクエストごとに正しい暗号署名を行わない限り、APIを呼び出すことはできません。未承認のリクエストはすべてAWSのゲートウェイ側で弾かれます。そのため、実質的に「自分たちだけのプライベートAPI」として安全に運用が可能です。


5. EC・物流エンジニアに贈る実践サンプルコード

今回のokamo的こだわりポイントは、ECシステムでよく使われる PHP と、エンタープライズ領域で強みを持つ .NET (C#) の2パターンで、SigV4の署名つきリクエストの実装サンプルを用意した点です。(①②ともに動作チェック済です!)

① PHPから呼び出す場合

PHPでは公式の aws/aws-sdk-php が提供する SignatureV4 クラスを使用することで、複雑な署名処理をライブラリ側に任せることができます。

composer require aws/aws-sdk-php guzzlehttp/guzzle
<?php
require __DIR__ . '/vendor/autoload.php';

use Aws\Credentials\CredentialProvider;
use Aws\Signature\SignatureV4;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;

$functionUrl = 'https://xxxxxxxxxxxx.lambda-url.ap-northeast-1.on.aws';
$region      = 'ap-northeast-1';

// 環境変数 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY から自動取得
$credentials = CredentialProvider::defaultProvider()()->wait();

// クエリパラメータ(ksort でアルファベット昇順に並べる)
$params = [
    'from_zip'  => '1860002',   // 倉庫の郵便番号
    'ship_date' => '2026-06-20', // 出荷予定日
    'to_zip'    => '9040004',   // お届け先の郵便番号
];
ksort($params);

$uri     = $functionUrl . '/yamato/leadtime?' . http_build_query($params);
$request = new Request('GET', $uri, ['Accept' => 'application/json']);

// SigV4 署名を適用
$signedRequest = (new SignatureV4('lambda', $region))->signRequest($request, $credentials);

$response = (new Client())->send($signedRequest);
$data     = json_decode($response->getBody()->getContents(), true);

if ($data['deliverable']) {
    echo "最短お届け日: {$data['earliest_delivery_date']}\n"; // 例: 2026-06-22
    echo "時間帯: {$data['earliest_time_from']}時〜{$data['earliest_time_to']}時\n";
} else {
    echo "配達不可エリアです\n";
}

② .NET (C#) から呼び出す場合

.NET 10の環境下で、外部ライブラリを追加せず、標準機能だけでSigV4署名プロセスを行って高速レスポンスを取得するコードです。

using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

const string FunctionUrl = "https://xxxxxxxxxxxx.lambda-url.ap-northeast-1.on.aws";
const string Region      = "ap-northeast-1";

var accessKey = Environment.GetEnvironmentVariable("AWS_ACCESS_KEY_ID") ?? throw new Exception("AccessKey is required");
var secretKey = Environment.GetEnvironmentVariable("AWS_SECRET_ACCESS_KEY") ?? throw new Exception("SecretKey is required");

var uri     = new Uri($"{FunctionUrl}/yamato/leadtime?from_zip=1860002&ship_date=2026-06-20&to_zip=9040004");
var now     = DateTime.UtcNow;
var amzDate = now.ToString("yyyyMMddTHHmmssZ");
var date    = now.ToString("yyyyMMdd");

// 署名に必要な文字列の構築
var payloadHash  = Hex(SHA256.HashData([]));
var canonicalReq = string.Join("\n",
    "GET", uri.AbsolutePath, uri.Query.TrimStart('?'),
    $"host:{uri.Host}\nx-amz-date:{amzDate}\n",
    "host;x-amz-date", payloadHash);

var scope        = $"{date}/{Region}/lambda/aws4_request";
var stringToSign = string.Join("\n",
    "AWS4-HMAC-SHA256", amzDate, scope,
    Hex(SHA256.HashData(Encoding.UTF8.GetBytes(canonicalReq))));

var sigKey    = Hmac(Hmac(Hmac(Hmac(Encoding.UTF8.GetBytes("AWS4" + secretKey), date), Region), "lambda"), "aws4_request");
var signature = Hex(Hmac(sigKey, stringToSign));

using var client = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Get, uri);
req.Headers.TryAddWithoutValidation("Authorization",
    $"AWS4-HMAC-SHA256 Credential={accessKey}/{scope}, SignedHeaders=host;x-amz-date, Signature={signature}");
req.Headers.Add("x-amz-date", amzDate);

var res  = await client.SendAsync(req);
var body = await res.Content.ReadAsStringAsync();
var d    = JsonDocument.Parse(body).RootElement;

Console.WriteLine($"最短お届け日 : {d.GetProperty("earliest_delivery_date").GetString()}");

static string Hex(byte[] b) => Convert.ToHexString(b).ToLower();
static byte[] Hmac(byte[] key, string data) => new HMACSHA256(key).ComputeHash(Encoding.UTF8.GetBytes(data));

6. まとめ:公式APIの提供を切に希望します!

日本郵便の素晴らしい取り組みを契機に、今回はヤマトのデータを活用した独自のサーバーレスAPIを構築しました。しかし、本来であればデータの最新性を100%維持するためにも、配送キャリアの公式APIが安定提供されることが一番望ましい姿です。

ヤマト運輸さんにおかれましても、日本郵便の「郵便番号・デジタルアドレスAPI」のように、開発者が手軽に直接クエリを投げられるような公式APIの提供をぜひともご検討いただけると、日本のEC・物流業界のDXがさらに前進するのではないかと強く願っています!

今回のコードやデプロイの詳細なフローに興味がある方は、ぜひGitHubリポジトリを覗いてみてください。皆様のECシステム・配送管理システム開発の一助になれば幸いです。


おまけ:okamoちゃんねるのレビュー

この記事について、3人のAI仮想読者がレビューしてくれました。

  • クロード(辛口エンジニア)
    • 「READMEには「S3 PUT → CodeBuild → ECR push → Lambda更新」という理想フローがきっちり図で書いてあるのに未実装。惜しい。でもここまで設計図を残してくれてるのは誠実だ。次回記事のネタにしてくれ」
  • GPT(税理士)
    • 「READMEは親切だが、AWS初心者向けの“詰まりポイント集”があるともっと良い」
  • Gemini(お母さん)
    • 「商品ページやカート画面で「明日(6/21 午前中)にお届け可能」ってパッと表示されたら、どれだけ安心できて、家族みんながニコニコになれるか。okamoさんは、そういう「お買い物を楽しみに待つ人たち」の優しさに寄り添って、この仕組みを作ってくれたのよね」

👉 AI 仮想読者3人による辛口レビュー全編はこちら