Временные окна доступа
Access Time Windows позволяют участнику на стороне партнера получать временный или повторяющийся доступ, предоставленный внешней системой. Эта страница — официальное руководство для поставщиков данных по полям access_time_windows и access_time_windows_revision.
Access Time Windows — это дополнительные разрешения, которые только предоставляют доступ. Активное окно может разрешить доступ, но неактивное, истекшее, устаревшее или несопоставленное окно не запрещает доступ, который уже разрешен обычным правилом или постоянным разрешением. Отключение пользователя, явная блокировка двери, режим обслуживания, геозонирование, ограничения частоты и доступность контроллера по-прежнему действуют.
Реализации для конкретных вышестоящих систем представляют собой отдельные адаптеры. Они должны преобразовывать исходные данные в этот независимый от поставщика контракт, не меняя значения полей.
Поля снимка участника
-
access_time_windows
arrayнеобязательныйПолный снимок Access Time Windows для этого участника и источника партнера. Снимок может сочетать окна
absoluteиweekly. Не указывайте поле, чтобы оставить сохраненный снимок без изменений; отправьте[], чтобы отозвать все окна, принадлежащие этому источнику. -
access_time_windows_revision
integerобязателен вместе с access_time_windowsМонотонно возрастающее неотрицательное целое число, безопасное для JSON, для потока ревизий этого участника и источника. Обязательно, если указано
access_time_windows, и не должно превышать9007199254740991.
Каждый снимок может содержать не более 50 окон. Поля external_id, scope_key и label принимают не более 120 символов. Абсолютное окно может охватывать не более 90 дней. Для одного дня недели допускается не более восьми непересекающихся блоков. Недопустимые данные отклоняются, а не обрезаются или угадываются.
Формы окон
Абсолютное окно задает один точный интервал:
{
"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 необязательны.
Полный смешанный запрос
Этот запрос заменяет предыдущий снимок участника, принадлежащий источнику, одним абсолютным и одним еженедельным окном:
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 — целые числа JSON в секундах Unix, обозначающие моменты времени UTC:
- Отправляйте целое число 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
Если исходные данные имеют точность до долей секунды, до начала интеграционного тестирования определите продуманную политику округления начала и окончания. Никогда не передавайте миллисекунды в расчете на то, что API сам определит единицу измерения.
Часовые пояса и летнее время
timezone должен быть допустимым часовым поясом IANA, например UTC, Australia/Perth или America/New_York. Если он не указан, по умолчанию используется UTC.
- Абсолютные окна:
fromиtoуже обозначают точные моменты UTC.timezoneслужит только метаданными для отображения и аудита; его изменение никогда не сдвигает ни один из этих моментов. - Еженедельные окна:
timezoneопределяет местное время и правила перехода на летнее время, используемые для оценкиaccess_hours. Не преобразуйте еженедельные блоки в UTC заранее. - При переводе часов вперед местное время, которое пропускается в этом часовом поясе, не наступает.
- При переводе часов назад еженедельному блоку соответствуют оба реальных момента с повторяющимся местным временем.
active_fromиactive_untilостаются моментами UTC, даже если еженедельное окно использует часовой пояс, отличный от UTC.
access_hours использует ключи в нижнем регистре от sun до sat. Каждое значение — массив пар [start, end] в 24-часовом формате HH:mm с ведущими нулями. Блоки должны быть непустыми, упорядоченными и непересекающимися. end не включается в интервал. Блок не может переходить через полночь; разделите ночной исходный интервал на поздний блок в первый день и ранний блок на следующий день.
Стабильные идентификаторы и сопоставление областей
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. Неизвестный, отключенный или несопоставленный scope_key приводит к отказу в доступе для этого окна; он не расширяет доступ. Согласуйте и протестируйте сопоставления до запуска.
Полные снимки и ревизии
Рассматривайте эти два поля как отдельный раздел с независимым управлением версиями внутри операции upsert для участника:
| Входные данные | Результат |
|---|---|
access_time_windows не указано | Существующий снимок остается без изменений; ревизия не требуется |
| Указано с более новой ревизией | Весь сохраненный снимок для этого участника и источника заменяется |
| Та же ревизия и та же нормализованная полезная нагрузка | Идемпотентный повтор; снимок не меняется |
| Та же ревизия и другая нормализованная полезная нагрузка | Конфликт; после сверки назначьте новую ревизию |
| Более старая ревизия | Устаревший снимок пропускается; он не может перезаписать более новое состояние |
access_time_windows: [] с более новой ревизией | Явно отзывает все окна, принадлежащие источнику |
Снимок является полным, а не исправлением. Если ревизия 1842 содержит окна A и B, то ревизия 1843, содержащая только B, удаляет A.
Поддерживайте один согласованный поток ревизий для каждой пары партнер — участник во всех обработчиках веб-перехватчиков, опросе, повторной отправке и полной сверке. Предпочтительно использовать надежную последовательность базы данных. Значение в миллисекундах Unix можно использовать в качестве ревизии, только если все записывающие процессы согласованы и гарантируют строгий порядок и уникальность; это значение ревизии, а не поле эпохи.
Сохраняйте точный запрос и ревизию, пока не станет известен ответ API. После тайм-аута или временной ошибки повторите отправку того же нормализованного снимка с той же ревизией. Не назначайте новую ревизию только потому, что доставка неизвестна.
Отмена, истечение срока и сбои вышестоящих систем
- Отмена одного окна: успешно получите состояние из основного источника, удалите это окно и отправьте оставшийся полный снимок с более новой ревизией.
- Отмена всех окон: отправьте
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приводит к отказу в доступе и создает предупреждение, видимое оператору. - Оповещения о давности источника и настроенная политика для устаревшего источника срабатывают на согласованном пороге.
- Пути веб-перехватчика и полной сверки приводят к одному и тому же снимку и порядку ревизий.
- Доставка потока достигает Door & Gate Access в пределах согласованного целевого времени.
- Активное окно может разрешить доступ к сопоставленной физической двери, но не может обойти отключение пользователя,
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, пересекающихся блоков, ночных блоков, более восьми блоков в день, более 50 окон и несопоставленных ключей областей.