アクセス時間ウィンドウ

Access Time Windows を使用すると、パートナーの会員に、外部システムから提供される一時的なアクセスや定期的なアクセスを持たせることができます。このページは、access_time_windows と access_time_windows_revision に関する正式なプロデューサーガイドです。

Access Time Windows は、許可のみを追加する権限です。有効なウィンドウはアクセスを許可できますが、無効、期限切れ、古い、またはマッピングされていないウィンドウが、通常のルールや恒久的な許可ですでに許可されているアクセスを拒否することはありません。ユーザーの無効化、ドアの明示的なブロック、メンテナンスモード、ジオフェンス、レート制限、コントローラーの可用性は引き続き適用されます。

特定の上流システム向けの実装は、別個のアダプターとして扱います。アダプターは、フィールドの意味を変えずに、ソースデータをこのベンダー非依存の仕様に変換する必要があります。

会員スナップショットのフィールド

  • access_time_windows array 省略可

    この会員とパートナーソースに対する、完全な Access Time Window のスナップショット。1 つのスナップショットに absolute と weekly のウィンドウを混在させることができます。保存済みのスナップショットを変更しない場合はフィールドを省略し、このソースが所有するすべてのウィンドウを失効させる場合は [] を送信します。

  • access_time_windows_revision integer access_time_windows 指定時は必須

    この会員/ソースのリビジョンストリームに対する、単調増加する負でない整数(JSON で安全に扱える値)。access_time_windows が含まれる場合は常に必須で、9007199254740991 を超えることはできません。

各スナップショットに含められるウィンドウは最大 50 件です。external_id、scope_key、label は最大 120 文字です。絶対ウィンドウの期間は最大 90 日です。週次の 1 日あたり、重複しないブロックを最大 8 つまで指定できます。無効なデータは、切り詰めたり推測したりせずに拒否されます。

ウィンドウの形式

絶対ウィンドウは、1 つの正確な期間を表します。

{
  "external_id": "access-4b1782",
  "kind": "absolute",
  "from": 1792053000,
  "to": 1792058400,
  "timezone": "Australia/Perth",
  "scope_key": "after_hours_entry",
  "label": "Temporary site access"
}

必須フィールドは external_id、kind、from、to です。timezone、scope_key、label は省略可能です。

週次ウィンドウは、現地の時計時刻で繰り返す期間を表し、必要に応じて UTC の時点で範囲を区切ることができます。

{
  "external_id": "weekly-early-access",
  "kind": "weekly",
  "timezone": "America/New_York",
  "access_hours": {
    "sun": [],
    "mon": [["06:00", "08:30"]],
    "tue": [["06:00", "08:30"]],
    "wed": [["06:00", "08:30"]],
    "thu": [["06:00", "08:30"]],
    "fri": [["06:00", "08:30"]],
    "sat": []
  },
  "active_from": 1790812800,
  "active_until": 1796083200,
  "scope_key": "main_entry",
  "label": "Weekday early access"
}

必須フィールドは external_id、kind、access_hours です。timezone、active_from、active_until、scope_key、label は省略可能です。

混在した完全なリクエスト

このリクエストは、ソースが所有する会員の以前のスナップショットを、1 つの絶対ウィンドウと 1 つの週次ウィンドウで置き換えます。

curl -X POST https://partner-api.performancehub.co/partner/v1/members \
  -u sk_live_YOUR_PARTNER_SECRET_KEY: \
  -H "Content-Type: application/json" \
  -d '{
    "members": [
      {
        "member_id": "member_11111",
        "facility_id": "550e8400-e29b-41d4-a716-446655440000",
        "first_name": "John",
        "last_name": "Smith",
        "access_time_windows_revision": 1842,
        "access_time_windows": [
          {
            "external_id": "access-4b1782",
            "kind": "absolute",
            "from": 1792053000,
            "to": 1792058400,
            "timezone": "Australia/Perth",
            "scope_key": "after_hours_entry",
            "label": "Temporary site access"
          },
          {
            "external_id": "weekly-early-access",
            "kind": "weekly",
            "timezone": "America/New_York",
            "access_hours": {
              "sun": [],
              "mon": [["06:00", "08:30"]],
              "tue": [["06:00", "08:30"]],
              "wed": [["06:00", "08:30"]],
              "thu": [["06:00", "08:30"]],
              "fri": [["06:00", "08:30"]],
              "sat": []
            },
            "active_from": 1790812800,
            "active_until": 1796083200,
            "scope_key": "main_entry",
            "label": "Weekday early access"
          }
        ]
      }
    ]
  }'

設定済みのリンク方法に応じて、facility_id または linking_identifier のいずれかを使用してください。両方を送信しないでください。

時刻と境界のルール

from、to、active_from、active_until は、UTC の時点を表す JSON 整数の Unix 秒です。

  • 引用符なしの JSON 整数を送信してください。数値の文字列、浮動小数点数、RFC 3339 の文字列、Unix ミリ秒は拒否されます。
  • すべての期間は半開区間 [from, to) です。アクセスはちょうど from の時点で開始でき、ちょうど to の時点で無効になります。
  • from は to より小さくする必要があります。週次の両方の境界を指定する場合は、active_from を active_until より小さくする必要があります。
  • タイムゾーンのない現地の日付/時刻からエポックを算出しないでください。まずソースの時刻を UTC の時点に解決し、次に Unix ミリ秒を 1000 で割って、結果が整数であることを確認してください。

エポックの変換

2026-10-15T08:30:00Z は 1792053000 に変換されます。

UTC instant                 Unix milliseconds   Unix seconds sent
2026-10-15T08:30:00Z        1792053000000        1792053000

言語に依存しない変換:

instant = parse_source_timestamp_with_explicit_zone(source_timestamp)
milliseconds = instant.to_unix_milliseconds()
assert milliseconds modulo 1000 equals 0
seconds = milliseconds / 1000
assert seconds is a JSON-safe nonnegative integer

ソースに 1 秒未満の精度がある場合は、連携テストの前に、開始/終了の丸め方針を意図的に定めてください。ミリ秒を渡して、単位の推測を API に任せることは絶対に避けてください。

タイムゾーンと夏時間

timezone は、UTC、Australia/Perth、America/New_York などの有効な IANA タイムゾーンである必要があります。省略した場合の既定値は UTC です。

  • 絶対ウィンドウ: from と to はすでに正確な UTC の時点を表しています。timezone は表示と監査のためのメタデータにすぎず、変更してもどちらの時点もずれることはありません。
  • 週次ウィンドウ: timezone は、access_hours の評価に使用する現地の時計時刻と夏時間のルールを定義します。週次のブロックを事前に UTC に変換しないでください。
  • 夏時間の開始で時計が進む際、タイムゾーンで飛ばされる現地時刻は発生しません。
  • 夏時間の終了で時計が戻る際、同じ現地時刻を持つ実際の 2 つの時点の両方が週次のブロックに一致します。
  • 週次ウィンドウで UTC 以外のタイムゾーンを使用する場合でも、active_from と active_until は UTC の時点のままです。

access_hours では、小文字の sun から sat を使用します。各値は、ゼロ埋めした 24 時間形式の HH:mm による [start, end] の組の配列です。ブロックは空でなく、順序どおりに並び、重複していない必要があります。end はその時刻を含みません。ブロックは午前 0 時をまたぐことができないため、日をまたぐソースの期間は、1 日目の遅い時間帯のブロックと翌日の早い時間帯のブロックに分割してください。

安定した ID とスコープのマッピング

external_id は、論理的なウィンドウが存在する間は変わらず、会員のスナップショット内で一意である必要があります。オペレーターや監査記録でリビジョンを関連付けられるよう、時刻やラベルが変わっても同じ値を保ってください。関係のないウィンドウに再利用しないでください。

scope_key は、安定した論理的なアクセス対象です。既定値は default です。施設の管理者が、許可された各キーを Performance Hub の施設全体、ドア、またはアクセス範囲の対象にマッピングします。

Partner scope_key           Performance Hub mapping
default                     Facility's configured default target
main_entry                  Access Scope "Main entrances"
after_hours_entry           Doors approved for after-hours entry

パートナーは、Performance Hub の施設、ドア、アクセス範囲の ID をウィンドウ内で送信しません。不明、無効、またはマッピングされていない scope_key は、そのウィンドウについて拒否側に倒れ、アクセスが広がることはありません。リリース前にマッピングについて合意し、テストしてください。

完全なスナップショットとリビジョン

この 2 つのフィールドは、会員のアップサート内で個別にバージョン管理される区画として扱ってください。

入力結果
access_time_windows を省略既存のスナップショットは変更されません。リビジョンは不要です
より新しいリビジョンで指定この会員/ソースの保存済みスナップショット全体が置き換えられます
同じリビジョンで、正規化後のペイロードも同じ冪等なリプレイです。スナップショットは変更されません
同じリビジョンで、正規化後のペイロードが異なる競合です。照合した後、新しいリビジョンを割り当ててください
より古いリビジョン古いスナップショットはスキップされ、新しい状態を上書きできません
より新しいリビジョンで access_time_windows: []ソースが所有するすべてのウィンドウを明示的に失効させます

スナップショットは差分ではなく、完全な状態です。リビジョン 1842 にウィンドウ A と B があり、リビジョン 1843 に B のみが含まれる場合、A は削除されます。

Webhook のワーカー、ポーリング、リプレイ、完全な照合の全体で、パートナー/会員ごとに調整された 1 つのリビジョンストリームを維持してください。永続的なデータベースのシーケンスを推奨します。Unix ミリ秒の値をリビジョンとして使用できるのは、すべての書き込み元が調整を行い、厳密な順序と一意性を保証する場合に限られます。これはリビジョンの値であり、エポックのフィールドではありません。

API のレスポンスが判明するまで、正確なリクエストとリビジョンを保存してください。タイムアウトや一時的なエラーの後は、同じ正規化済みのスナップショットを同じリビジョンで再試行してください。転送の配信結果が不確かだという理由だけで、新しいリビジョンを割り当てないでください。

キャンセル、期限切れ、上流のエラー

  • 1 つのウィンドウをキャンセルする: 信頼できるソースの状態を正常に取得し、そのウィンドウを削除して、残りの完全なスナップショットをより新しいリビジョンで送信します。
  • すべてのウィンドウをキャンセルする: より新しいリビジョンで access_time_windows: [] を送信します。
  • 絶対ウィンドウを期限切れにする: ウィンドウは to の時点で許可を停止します。スナップショットを整理するため、後の照合で期限切れの項目を除いて送信してください。
  • 週次ウィンドウを終了する: より新しいスナップショットで削除するか、ソースに既知の終了時点がある場合は active_until を使用します。
  • ソースの取得が失敗した、または不完全な場合: [] を送信せず、部分的なスナップショットも公開しないでください。アラートを出して取得を再試行してください。上流のエラーを空のスナップショットに変換すると、意図しないアクセスの失効につながります。
  • Partner API のリクエストが検証に失敗した場合: データを修正し、正規化後のスナップショットが変わる場合にのみ、より新しいリビジョンで送信してください。一時的なエラーは元のリビジョンで再試行してください。

ソースの鮮度は、連携ごとに、約束された配信と照合の頻度に基づいて設定されます。有効化する前に、ソースデータの最大経過時間、それを超えた場合の動作、アラートのしきい値、各ウィンドウ自体の範囲のみに依存することをオペレーターが了承できるかどうかについて合意してください。古いソースのウィンドウが拒否ルールになることはなく、通常のルールと恒久的な許可は引き続きフォールバック経路となります。

プロデューサーのアルゴリズム

会員を更新するたびに、次の言語に依存しないワークフローを使用してください。

function publish_member(member_key):
    source_result = fetch_authoritative_access_state(member_key)

    if source_result.failed or source_result.incomplete:
        record_failure(member_key)
        schedule_retry(member_key)
        return without sending access_time_windows

    windows = []
    for source_window in source_result.current_windows:
        if source_window.is_absolute:
            windows.append({
                external_id: stable_window_id(source_window),
                kind: "absolute",
                from: to_integer_unix_seconds(source_window.start_instant),
                to: to_integer_unix_seconds(source_window.end_instant),
                timezone: source_window.display_timezone or "UTC",
                scope_key: stable_scope_key(source_window) or "default",
                label: safe_label(source_window)
            })
        else:
            windows.append({
                external_id: stable_window_id(source_window),
                kind: "weekly",
                timezone: valid_iana_zone(source_window.timezone) or "UTC",
                access_hours: split_validate_and_sort_weekly_blocks(source_window),
                active_from: optional_integer_unix_seconds(source_window.active_start),
                active_until: optional_integer_unix_seconds(source_window.active_end),
                scope_key: stable_scope_key(source_window) or "default",
                label: safe_label(source_window)
            })

    normalized_snapshot = validate_sort_and_deduplicate(windows)
    revision = next_durable_member_revision(member_key)
    request = member_upsert_with(normalized_snapshot, revision)
    persist_for_retry(member_key, request)

    response = post_request(request)
    if response.is_transient_or_unknown:
        retry_exact_request_with_same_revision(request)
    else if response.is_revision_conflict:
        reconcile_with_latest_committed_source_state(member_key)
    else if response.is_stale_skip:
        stop_replaying_this_revision_and_reconcile(member_key)

認定チェックリスト

最終的な scope_key のマッピングを使用して、ステージングの施設で次のチェックを完了してください。

  • タイムゾーンを省略した絶対ウィンドウが受け付けられ、UTC として評価される。
  • カスタムの IANA タイムゾーンを指定した絶対ウィンドウで、同じ from/to の時点が維持される。
  • カスタムの IANA タイムゾーンを指定した週次ウィンドウが、夏時間の開始と終了の切り替えをまたいで現地の時計時刻に従う。
  • 絶対/週次が混在したスナップショットが、以前のスナップショットを完全に置き換える。
  • アクセスがちょうど from で開始し、ちょうど to で停止する。週次ブロックの終了時刻もその時刻を含まない。
  • 同じ external_id で時刻を更新し、より新しいリビジョンで送信すると反映される。
  • access_time_windows を省略すると、ウィンドウは変更されずに会員のその他のフィールドが変更される。
  • 同じリビジョン/ペイロードのリプレイが冪等である。
  • 同じリビジョンで異なるペイロードを送信すると競合が返され、スナップショットは変更されない。
  • 順序が入れ替わった古いリビジョンが古いものとして報告され、状態を巻き戻せない。
  • より新しい [] によってソースが所有するウィンドウが失効し、上流からの取得に失敗した場合は何も送信されない。
  • 不明またはマッピングされていない scope_key が拒否側に倒れ、オペレーターに見える警告が作成される。
  • ソースデータの経過時間に関するアラートと、設定した古いソースのポリシーが、合意したしきい値で機能する。
  • Webhook と完全な照合の経路が、同じスナップショットとリビジョンの順序に収束する。
  • ストリームの配信が、合意した目標時間内にドア/ゲートアクセスに到達する。
  • 有効なウィンドウでマッピングされた物理ドアへのアクセスを許可できるが、ユーザーの無効化、blockAllAccess、メンテナンス、ジオフェンスは回避できない。
  • ウィンドウの前、範囲外、終了後も、通常のルールと恒久的な許可によって、ほかの条件を満たすユーザーのアクセスが引き続き許可される。

最低限のテストベクター

[
  {
    "name": "absolute-half-open",
    "window": {
      "external_id": "tv-absolute",
      "kind": "absolute",
      "from": 1792053000,
      "to": 1792058400
    },
    "expect": {
      "1792052999": "inactive",
      "1792053000": "active",
      "1792058399": "active",
      "1792058400": "inactive"
    }
  },
  {
    "name": "weekly-dst",
    "window": {
      "external_id": "tv-weekly-dst",
      "kind": "weekly",
      "timezone": "America/New_York",
      "access_hours": {
        "sun": [["01:00", "03:30"]],
        "mon": [],
        "tue": [],
        "wed": [],
        "thu": [],
        "fri": [],
        "sat": []
      }
    },
    "expect": "Evaluate local wall time: skipped times do not occur; both repeated 01:30 instants qualify."
  }
]

Unix ミリ秒、数値の文字列、浮動小数点数、無効な IANA タイムゾーン、from >= to、重複した external_id、重なり合うブロック、日をまたぐブロック、1 日に 8 つを超えるブロック、50 件を超えるウィンドウ、マッピングされていないスコープキーについての異常系のベクターも提出してください。