連携の説明
このページでは、お店の今日の営業状態とお店情報を読み出す方法と、ホームページの修正依頼を送る方法を説明します。鍵はお店が発行し、お店が選んだ範囲の情報だけを返します。
鍵と呼び出し方
鍵は、お店が「連携」の画面で発行し、連携先に渡します。鍵は sn_live_ または sn_test_ で始まります。
すべての呼び出しに Authorization: Bearer <鍵> を付けます。回数は鍵ごとに1分60回までです。超えると 429 と Retry-After を返します。
依頼の送信には Idempotency-Key ヘッダーが必要です。同じキーで送り直すと、同じ応答を返します(24時間)。
お店の今日の状態とお店情報は、そのお店が「連携先に伝える」をオンにしているときだけ返します。オフのときは 403 consent_required です。お店の方のお名前・電話番号・メールアドレス・LINEのアカウントは、どの応答にも含まれません。
時刻は +09:00 付きの ISO 8601、日付は YYYY-MM-DD です。一覧は limit(1〜100、既定50)と cursor で続きを取ります。
エンドポイント
| メソッド | パス | スコープ | 内容 |
|---|---|---|---|
| GET | /api/partner/v1/ping | どれでも | 鍵の確認 |
| GET | /api/partner/v1/openapi.json | 不要 | OpenAPI 3.1 の定義(鍵は不要) |
| GET | /api/partner/v1/shops | shop_status:read / shop_facts:read / tickets:read / tickets:write | お店の一覧と、連携先に伝える同意の有無 |
| GET | /api/partner/v1/shops/{id}/status | shop_status:read | お店の今日の状態:営業日か・今営業中か・今日の営業時間・臨時休業・お知らせ |
| GET | /api/partner/v1/shops/{id}/facts | shop_facts:read | お店情報の正本(今有効なもの/as_of の日のもの/include_history=true で履歴) |
| POST | /api/partner/v1/tickets | tickets:write | 修正依頼の送信。お店の承認の流れは同じで、省略できません |
| GET | /api/partner/v1/tickets | tickets:read | 同じ鍵で送った依頼の一覧 |
| GET | /api/partner/v1/tickets/{id} | tickets:read | 同じ鍵で送った依頼の状態 |
イベント(Webhook)
| イベント | いつ | 条件 |
|---|---|---|
| shop.info_changed | お店情報の正本が変わったとき | お店が「連携先に伝える」をオンにしているときだけ |
| ticket.published | 依頼が公開されたとき | なし(依頼の本文・見本のURLは含みません) |
| ticket.needs_approval | 見本をお店に送り、承認待ちになったとき | なし(依頼の本文・見本のURLは含みません) |
本文は {"id","type","created_at","org_id","data"} の形です。同じ出来事には同じ id を付けます。
順番は保証しません。created_at で判断してください。
署名の確かめ方
ヘッダー X-SBGG-Signature: t=<UNIX秒>,v1=<署名> が付きます。署名は、宛先を登録したときに表示された秘密で、t と本文を「.」でつないだ文字列の HMAC-SHA256(16進)です。
t が今から300秒以上ずれていたら捨ててください。宛先は https だけです。
// Node.js
import crypto from 'node:crypto';
function verify(secret, rawBody, header) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const t = Number(parts.t);
if (Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}curl の例
お店の今日の状態を取る
curl -s https://sakunao.jp/api/partner/v1/shops/{shop_id}/status \
-H "Authorization: Bearer sn_live_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"修正依頼を送る
curl -s -X POST https://sakunao.jp/api/partner/v1/tickets \
-H "Authorization: Bearer sn_live_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c1e2b4a-9f10-4d2e-8a3b-5c6d7e8f9012" \
-d '{"shop_id":"{shop_id}","request_text":"営業時間を10時からに変えて","external_ref":"WEB-889"}'修正依頼は、お店がLINEで見本を確認して「公開する」を押すまで公開されません。
エラー
エラーは {"error":{"code","message"}} の形です。401 unauthorized、403 forbidden/consent_required/account_inactive、404 not_found、409 line_not_linked/quota_exceeded/conflict(同じ Idempotency-Key で違う内容)、422 validation_error、429 rate_limited。