統合ガイド
各言語・プラットフォームでの Lookup API の統合例です。
Node.js / TypeScript
サーバーサイドで Lookup API を呼び出す例です。Express ミドルウェアとしても利用できます。
const MEMGATE_API_KEY = process.env.MEMGATE_API_KEY;
const MEMGATE_BASE_URL = "https://www.memgate.io/api/v1";
interface LookupResult {
member: {
note_id: string;
email: string;
plan: string;
valid_from: string;
valid_until: string;
status: "active" | "expired";
} | null;
status?: "not_found";
}
async function lookupMember(
noteId: string,
email: string
): Promise<LookupResult> {
const params = new URLSearchParams({ note_id: noteId, email });
const res = await fetch(
`${MEMGATE_BASE_URL}/lookup?${params}`,
{
headers: { "X-API-Key": MEMGATE_API_KEY! },
}
);
if (!res.ok) {
const err = await res.json();
throw new Error(err.error?.message ?? "Lookup failed");
}
return res.json();
}
// Usage
const result = await lookupMember("yamada_taro", "yamada@example.com");
if (result.member && result.member.status === "active") {
console.log(`Active plan: ${result.member.plan}`);
} else {
console.log("No active membership");
}Express ミドルウェア例
import type { Request, Response, NextFunction } from "express";
async function requireMembership(
req: Request,
res: Response,
next: NextFunction
) {
const { note_id, email } = req.body;
try {
const result = await lookupMember(note_id, email);
if (result.member?.status === "active") {
req.memberPlan = result.member.plan;
return next();
}
return res.status(403).json({
error: "Active membership required",
});
} catch {
return res.status(502).json({
error: "Membership verification failed",
});
}
}Python
Python の requests ライブラリを使った例です。
import os
import requests
MEMGATE_API_KEY = os.environ["MEMGATE_API_KEY"]
MEMGATE_BASE_URL = "https://www.memgate.io/api/v1"
def lookup_member(note_id: str, email: str) -> dict:
"""MemGate Lookup API でメンバー情報を照会する"""
res = requests.get(
f"{MEMGATE_BASE_URL}/lookup",
params={"note_id": note_id, "email": email},
headers={"X-API-Key": MEMGATE_API_KEY},
timeout=10,
)
res.raise_for_status()
return res.json()
# Usage
result = lookup_member("yamada_taro", "yamada@example.com")
if result.get("member") and result["member"]["status"] == "active":
print(f"Active plan: {result['member']['plan']}")
else:
print("No active membership")Flask デコレータ例
from functools import wraps
from flask import request, jsonify
def require_membership(f):
@wraps(f)
def decorated(*args, **kwargs):
note_id = request.json.get("note_id")
email = request.json.get("email")
if not note_id or not email:
return jsonify({"error": "note_id and email required"}), 400
result = lookup_member(note_id, email)
member = result.get("member")
if not member or member["status"] != "active":
return jsonify({"error": "Active membership required"}), 403
request.member_plan = member["plan"]
return f(*args, **kwargs)
return decoratedChrome 拡張機能
クライアントキー(mck_ プレフィックス)を使うと、 自前のバックエンドサーバーなしで Chrome 拡張機能から直接 MemGate API を呼び出せます。
セットアップ
手順
- 拡張機能の ID を確認(
chrome://extensions/→ デベロッパーモード ON) - MemGate 管理画面の「APIキー → クライアントキー」タブでキーを作成
- 許可オリジンに
chrome-extension://YOUR_EXTENSION_IDを設定 manifest.jsonのhost_permissionsに"https://www.memgate.io/*"を追加- Service Worker(background script)から Lookup API を呼び出し
manifest.json の設定
{
"manifest_version": 3,
"host_permissions": [
"https://www.memgate.io/*"
],
"background": {
"service_worker": "background.js"
}
}Service Worker からの呼び出し
Popup(UI層)から直接 API を呼ばず、Service Worker 経由で通信することを推奨します。 これにより、クライアントキーが popup.js のスコープに露出せず、 レスポンスキャッシュも Service Worker レイヤーで管理できます。
// background.js (Service Worker)
const MEMGATE_CLIENT_KEY = "mck_your_client_key_here";
const MEMGATE_API_URL = "https://www.memgate.io/api/v1/lookup";
async function checkMembership(
noteId: string,
email: string
): Promise<{ active: boolean; plan?: string }> {
const params = new URLSearchParams({ note_id: noteId, email });
const res = await fetch(
`${MEMGATE_API_URL}?${params}`,
{
headers: { "X-API-Key": MEMGATE_CLIENT_KEY },
}
);
if (!res.ok) return { active: false };
const data = await res.json();
if (data.member?.status === "active") {
return { active: true, plan: data.member.plan };
}
return { active: false };
}
// Popup / Content script からのメッセージを受信
chrome.runtime.onMessage.addListener(
(message, _sender, sendResponse) => {
if (message.type === "CHECK_MEMBERSHIP") {
checkMembership(message.noteId, message.email)
.then((result) => sendResponse(result));
return true; // async response
}
}
);Origin ヘッダーについて
Chrome MV3 の挙動
Chrome MV3 の Service Worker は、host_permissions に含まれるドメインへのリクエストで Origin ヘッダーを送信しないケースがあります。 MemGate はこのケースに対応しており、Origin が未送信の場合はクライアントキー認証のみで リクエストを許可します。Origin が送信された場合は、登録済みの許可オリジンと照合します。
Chrome Web Store 公開時の注意
開発者モード(パッケージ解凍読み込み)で使用している拡張機能の ID は、 Chrome Web Store に公開すると変更されます。 公開後に新しい拡張 ID を管理画面の「許可オリジン」に追加してください。 開発用の ID は不要になったら削除を推奨します。
ベストプラクティス
キーの種類と使い分け
- サーバーキー(
mgk_): バックエンドサーバーで使用。環境変数に格納し、クライアントに公開しない - クライアントキー(
mck_): Chrome 拡張機能やフロントエンドで使用。許可オリジンで保護される - 不要になったキーは管理画面から即座に無効化する
429 レートリミットへの対応
429 レスポンスを受けた場合は、指数バックオフでリトライしてください。 クライアントキーはサーバーキーより低いレートリミット(30回/分)が適用されます。
async function lookupWithRetry(
noteId: string,
email: string,
maxRetries = 3
): Promise<LookupResult> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fetch(/* ... */);
if (res.status === 429 && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise((r) => setTimeout(r, delay));
continue;
}
if (!res.ok) throw new Error("Lookup failed");
return res.json();
}
throw new Error("Max retries exceeded");
}レスポンスのキャッシュ
同じユーザーに対する短時間の連続リクエストを避けるため、 サーバーサイドでレスポンスを短時間キャッシュすることを推奨します。 ただし、キャッシュ期間はメンバーシップの更新頻度に応じて調整してください。
エラーハンドリング
- MemGate API が一時的に利用できない場合のフォールバックを検討する
- タイムアウトを設定する(推奨: 10秒)
member: nullのケースを必ずハンドリングする