REST API

AppCotton は、**公開(public)認証必須(private)**の 2 系統の REST API を提供します。
基本思想は「プロダクト/プランの公開参照」と「ライセンスの発行・検証・ドメイン紐づけ」を分離することです。


基本情報

  • ベース URL:https://{your-site}/wp-json/appcotton/v1/
  • 文字コード:UTF-8 / JSON
  • タイムスタンプ:UTC(ISO 8601)
  • 安定性:バージョン付きv1)。後方互換が壊れる変更は v2 以降で提供。

認証方式

  • 公開系(/products, /plans 等):原則認証不要(読み取り)
  • ライセンス系(/licenses/):
    • 検証(validate)は、サーバ間問い合わせ用途で認証不要(レート制限あり)
    • 発行・アクティベーション・解除は管理者/購入者のログイン済み(Cookie/Nonce)またはサーバ側キーによる保護
  • CSRF 対策:管理画面/フロント UI 経由で叩く場合は X-WP-Nonce を必須化
  • CORS:GET 公開エンドポイントのみ許可(既定)。POST は same-origin 前提(必要に応じて allowlist)

共通ヘッダ

Content-Type: application/json
Accept: application/json
X-WP-Nonce: {nonce}            // 認証を要する操作時

エラー形式(共通)

{
  "success": false,
  "error": "license_not_found",
  "message": "ライセンスが見つかりません。",
  "details": { "hint": "キーを確認してください" }
}
  • 主な HTTP ステータス
    • 200 / 201: 成功
    • 400: バリデーション失敗/不正リクエスト
    • 401: 未認証
    • 403: 権限不足/Nonce 不正
    • 404: 対象が存在しない
    • 409: 競合(すでにアクティブ等)
    • 429: レート制限
    • 500: 予期せぬエラー

エンドポイント一覧

1) Products / Plans(公開)

GET /products

プロダクトの一覧を返します。

レスポンス例

{
  "success": true,
  "items": [
    {
      "id": 1,
      "name": "CombPass Premium",
      "product_slug": "combpass_premium",
      "description": "予約管理の上位機能",
      "active": true
    }
  ]
}

GET /products/{id}

特定プロダクトの詳細。

{
  "success": true,
  "product": {
    "id": 1,
    "product_slug": "combpass_premium",
    "name": "CombPass Premium",
    "description": "…"
  }
}

GET /products/{id}/plans

該当プロダクトに紐づく 販売プランを返します(例:1サイト/3サイト/5サイト、one_time/subscription 等)。

{
  "success": true,
  "plans": [
    {
      "plan_id": 101,
      "plan_name": "1サイト",
      "activation_limit": 1,
      "billing_type": "one_time",
      "price": 9800,
      "currency": "JPY",
      "sort_order": 10
    },
    {
      "plan_id": 102,
      "plan_name": "3サイト",
      "activation_limit": 3,
      "billing_type": "one_time",
      "price": 19800,
      "currency": "JPY",
      "sort_order": 20
    }
  ]
}

GET /products/{id}/price

単発購入に使う 現在価格(税/通貨処理後)を返します。
無料配布の可否など UI 分岐に使用。

{ "success": true, "price": 19800, "currency": "JPY" }

2) Licenses(検証・発行・ドメイン紐づけ)

GET /licenses/validate?license_key={key}&product_id={id}&domain={origin}

ライセンスの存在・有効性・残枠を検証し、指定ドメインがアクティブかも返します。
クライアント SDK の is_premium() が内部で叩く想定。

成功例

{
  "valid": true,
  "message": "OK",
  "expires_at": null,
  "is_activated": true,
  "usage": {
    "used": 2,
    "limit": 3,
    "remaining": 1,
    "activations": [
      { "domain": "https://site-a.com", "status": "active" },
      { "domain": "https://site-b.com", "status": "active" }
    ]
  }
}

エラー例

{ "valid": false, "error": "limit_exceeded", "message": "上限に達しています" }

POST /licenses/activate

ドメインをアクティベーション(紐づけ)します。

リクエスト

{
  "license_key": "AC-XXXX-YYYY",
  "domain": "https://example.com",
  "site_url": "https://example.com",
  "instance_id": "optional-guid"
}

レスポンス

{
  "success": true,
  "message": "Activated",
  "usage": { "used": 1, "limit": 3, "remaining": 2 }
}

POST /licenses/deactivate

ドメインの解除(枠を戻す)。

{
  "license_key": "AC-XXXX-YYYY",
  "domain": "https://example.com"
}

成功時:

{
  "success": true,
  "message": "Deactivated",
  "usage": { "used": 0, "limit": 3, "remaining": 3 }
}

GET /licenses/usage?license_key={key}

使用状況のみ(管理画面やポータルでの表示用)。

{
  "success": true,
  "usage": {
    "used": 2,
    "limit": 3,
    "remaining": 1,
    "activations": [ ... ]
  }
}

POST /licenses/request-free

無料プランのライセンス発行(メール送付)。
※ いたずら対策として reCAPTCHA・レート制限・メールドメイン制限を推奨。

{
  "product_id": 1,
  "customer_email": "user@example.com"
}

3) Checkout / Upgrade(差額課金の開始)

POST /checkout/create-session

新規購入(Stripe Checkout セッション作成)。
バックエンドでプラン ID・金額・セッション URL を生成し、redirect_url を返す。

リクエスト

{ "product_id": 1, "plan_id": 102 }

レスポンス

{ "success": true, "redirect_url": "https://checkout.stripe.com/..." }

POST /licenses/upgrade/start

上位プランへの差額課金(現在のライセンスからターゲットプランへ)。
バックエンドで差額(現在価格 − 既払い相当)を計算し Checkout を生成。

リクエスト

{
  "license_key": "AC-XXXX-YYYY",
  "target_plan_id": 103,
  "success_url": "https://your-site/thanks",
  "cancel_url": "https://your-site/cancel"
}

レスポンス

{
  "success": true,
  "redirect_url": "https://checkout.stripe.com/...",
  "diff_cents": 5000,
  "currency": "JPY"
}

ページング・並び順・検索

  • 一覧は ?page=1&per_page=20&orderby=sort_order&order=asc などを受け付け
  • レスポンスヘッダに X-Total, X-Total-Pages を付与(可能なら)

レート制限(推奨)

  • 公開 GET:IP 単位で1 分 60 回
  • /licenses/validate1 分 20 回(ドメイン/ライセンスキー単位のバースト抑制)
  • 429 時の再試行は Retry-After 秒数を返却

べき等性(idempotency)

  • /licenses/activate/deactivate
    同一 license_key + domain に対する短時間の重複 POST は無害(同じ結果)で返す
  • Checkout 作成:クライアントが重送する場合は Idempotency-Key(UUID)を任意ヘッダで受け取り、短期間は同一セッションを返す設計が望ましい

セキュリティ注意

  • ドメイン正規化https://example.com / 末尾スラッシュ除去 / サブドメイン一致方針を統一)
  • IP・UAの保存はプライバシーポリシー記載
  • Nonce 検証(管理画面/同一オリジン UI)
  • 不正アクセスログの監査(activation の連打や違反パターン)

具体例

cURL:ライセンス検証

curl -G "https://your-site/wp-json/appcotton/v1/licenses/validate" \
  --data-urlencode "license_key=AC-XXXX-YYYY" \
  --data-urlencode "product_id=1" \
  --data-urlencode "domain=https://client-site.com"

PHP(WordPress)での検証

$response = wp_remote_get( add_query_arg([
  'license_key' => $license_key,
  'product_id'  => $product_id,
  'domain'      => home_url()
], rest_url('appcotton/v1/licenses/validate') ) );

if ( is_wp_error($response) ) { /* ネットワークエラー処理 */ }
$body = json_decode( wp_remote_retrieve_body($response), true );

if ( ! empty($body['valid']) ) {
  // is_premium = true
}

JS(フロント)でのアクティベーション

const res = await fetch('/wp-json/appcotton/v1/licenses/activate', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': window.appcottonNonce },
  body: JSON.stringify({
    license_key: form.license.value,
    domain: window.location.origin,
    site_url: window.location.origin
  })
});
const json = await res.json();
if (json.success) { /* usage 表示 */ }

バージョニングと互換性

  • 非互換変更は v2 で提供
  • v1 ではレスポンスにフィールド追加は行うが、既存キーの意味と型は維持

よくあるエラーコード(抜粋)

error意味
missing_param必須パラメータ不足
license_not_foundキーが存在しない
product_mismatchプロダクト不一致
limit_exceeded有効化上限超過
already_activated既に同ドメインで有効
not_activated解除対象が見つからない
forbidden権限不足/Nonce 不正
rate_limitedレート制限
checkout_failed決済セッション生成失敗

この章をベースに、**SDK(is_premium などのラッパー)UI自動生成(プラン一覧/購入・アップグレードボタン)**を組み合わせれば、プラグイン開発者は最小実装で組み込み可能になります。