Okna czasowe dostępu
Access Time Windows pozwalają przypisać członkowi partnera tymczasowy lub cykliczny dostęp dostarczany przez system zewnętrzny. Ta strona jest nadrzędnym przewodnikiem dla producentów danych access_time_windows i access_time_windows_revision.
Access Time Windows to dodatkowe uprawnienia, które wyłącznie zezwalają na dostęp. Aktywne okno może zezwolić na dostęp, ale okno nieaktywne, wygasłe, nieaktualne lub niezmapowane nie odbiera dostępu, na który już zezwala zwykła reguła lub stałe uprawnienie. Nadal obowiązują wyłączenie użytkownika, jawne zablokowanie drzwi, tryb serwisowy, geofencing, limity liczby żądań i dostępność kontrolera.
Implementacje dla poszczególnych systemów źródłowych są osobnymi adapterami. Muszą one przekształcać dane źródłowe na ten niezależny od dostawcy kontrakt bez zmieniania znaczenia jego pól.
Pola migawki członka
-
access_time_windows
arrayopcjonalnyKompletna migawka Access Time Windows dla tego członka i źródła partnera. Migawka może łączyć okna
absoluteiweekly. Pomiń to pole, aby pozostawić zapisaną migawkę bez zmian; wyślij[], aby odwołać wszystkie okna należące do tego źródła. -
access_time_windows_revision
integerwymagany z access_time_windowsMonotonicznie rosnąca, nieujemna liczba całkowita bezpieczna w JSON dla strumienia wersji danego członka/źródła. Jest wymagana zawsze, gdy obecne jest
access_time_windows, i nie może przekraczać9007199254740991.
Każda migawka może zawierać maksymalnie 50 okien. Pola external_id, scope_key i label mogą mieć maksymalnie 120 znaków. Okna bezwzględne mogą obejmować maksymalnie 90 dni. Jeden dzień tygodnia może zawierać maksymalnie osiem nienakładających się bloków. Nieprawidłowe dane są odrzucane, a nie obcinane lub zgadywane.
Kształty okien
Okno bezwzględne określa jeden dokładny przedział:
{
"external_id": "access-4b1782",
"kind": "absolute",
"from": 1792053000,
"to": 1792058400,
"timezone": "Australia/Perth",
"scope_key": "after_hours_entry",
"label": "Temporary site access"
}
Wymagane pola to external_id, kind, from i to. Pola timezone, scope_key i label są opcjonalne.
Okno tygodniowe określa cykliczne przedziały czasu lokalnego, opcjonalnie ograniczone momentami 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"
}
Wymagane pola to external_id, kind i access_hours. Pola timezone, active_from, active_until, scope_key i label są opcjonalne.
Kompletne żądanie mieszane
To żądanie zastępuje poprzednią migawkę członka należącą do źródła jednym oknem bezwzględnym i jednym tygodniowym:
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"
}
]
}
]
}'
Używaj facility_id lub linking_identifier zgodnie ze skonfigurowaną metodą łączenia. Nie wysyłaj obu.
Reguły dotyczące czasu i granic
from, to, active_from i active_until to liczby całkowite JSON w sekundach Unix, określające momenty UTC:
- Wysyłaj liczbę całkowitą JSON bez cudzysłowów. Ciągi liczbowe, liczby zmiennoprzecinkowe, ciągi RFC 3339 i milisekundy Unix są odrzucane.
- Każdy przedział jest prawostronnie otwarty:
[from, to). Dostęp może rozpocząć się dokładnie w momenciefrom; w momencietojuż nie jest aktywny. frommusi być mniejsze niżto. Gdy obecne są oba ograniczenia okna tygodniowego,active_frommusi być mniejsze niżactive_until.- Nie wyznaczaj wartości epoki na podstawie lokalnej daty/godziny bez strefy czasowej. Najpierw przekształć czas źródłowy na moment UTC, następnie podziel milisekundy Unix przez 1000 i sprawdź, czy wynik jest liczbą całkowitą.
Konwersja epoki
2026-10-15T08:30:00Z po konwersji daje 1792053000:
UTC instant Unix milliseconds Unix seconds sent
2026-10-15T08:30:00Z 1792053000000 1792053000
Konwersja niezależna od języka:
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
Jeśli źródło ma precyzję poniżej sekundy, przed testami integracyjnymi określ przemyślane zasady zaokrąglania początku i końca. Nigdy nie przekazuj milisekund w nadziei, że API samo rozpozna jednostkę.
Strefy czasowe i czas letni
timezone musi być prawidłową strefą czasową IANA, np. UTC, Australia/Perth lub America/New_York. Jeśli zostanie pominięta, domyślnie przyjmowana jest wartość UTC.
- Okna bezwzględne:
fromitojuż określają dokładne momenty UTC.timezonejest wyłącznie metadaną do wyświetlania i audytu; jej zmiana nigdy nie przesuwa żadnego z tych momentów. - Okna tygodniowe:
timezoneokreśla lokalny czas zegarowy i reguły czasu letniego używane do ocenyaccess_hours. Nie przeliczaj z góry bloków tygodniowych na UTC. - Podczas przejścia na czas letni lokalna godzina, którą strefa czasowa pomija, nigdy nie występuje.
- Podczas powrotu do czasu zimowego oba rzeczywiste momenty o powtórzonej godzinie lokalnej pasują do bloku tygodniowego.
active_fromiactive_untilpozostają momentami UTC nawet wtedy, gdy okno tygodniowe używa strefy czasowej innej niż UTC.
access_hours używa kluczy pisanych małymi literami od sun do sat. Każda wartość jest tablicą par [start, end] w 24-godzinnym formacie HH:mm z zerami wiodącymi. Bloki muszą być niepuste, uporządkowane i nienakładające się. end nie jest wliczany. Blok nie może przechodzić przez północ; podziel nocny przedział źródłowy na późny blok pierwszego dnia i wczesny blok następnego dnia.
Stabilne identyfikatory i mapowanie zakresów
external_id musi pozostawać stały przez cały okres istnienia logicznego okna i być unikatowy w obrębie migawki członka. Zachowuj go bez zmian, gdy zmieniają się godziny lub etykiety, aby operatorzy i rekordy audytu mogli powiązać kolejne wersje. Nie używaj go ponownie dla niepowiązanego okna.
scope_key to Twój stabilny logiczny cel dostępu. Domyślnie ma wartość default. Administrator obiektu mapuje każdy dozwolony klucz na cele Performance Hub obejmujące cały obiekt, konkretne drzwi lub Access Scope:
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
Partnerzy nie wysyłają w oknie identyfikatorów obiektów, drzwi ani Access Scope z Performance Hub. Nieznany, wyłączony lub niezmapowany scope_key powoduje odmowę dostępu dla danego okna; nie rozszerza dostępu. Uzgodnij i przetestuj mapowania przed uruchomieniem.
Kompletne migawki i wersje
Traktuj te dwa pola jako osobno wersjonowaną część operacji upsert członka:
| Dane wejściowe | Wynik |
|---|---|
Pominięto access_time_windows | Istniejąca migawka pozostaje bez zmian; wersja nie jest wymagana |
| Obecne z nowszą wersją | Cała zapisana migawka dla tego członka/źródła zostaje zastąpiona |
| Ta sama wersja i ten sam znormalizowany ładunek | Idempotentne ponowne odtworzenie; migawka się nie zmienia |
| Ta sama wersja i inny znormalizowany ładunek | Konflikt; po uzgodnieniu danych przydziel nową wersję |
| Starsza wersja | Nieaktualna migawka zostaje pominięta; nie może nadpisać nowszego stanu |
access_time_windows: [] z nowszą wersją | Jawnie odwołuje wszystkie okna należące do źródła |
Migawka jest kompletna, a nie jest poprawką. Jeśli wersja 1842 zawiera okna A i B, to wersja 1843 zawierająca tylko B usuwa A.
Utrzymuj jeden skoordynowany strumień wersji dla każdej pary partner/członek we wszystkich procesach obsługi webhooków, odpytywania, ponownego odtwarzania i pełnego uzgadniania. Zalecana jest trwała sekwencja w bazie danych. Wartości w milisekundach Unix można używać jako wersji tylko wtedy, gdy wszystkie procesy zapisujące są skoordynowane i gwarantują ścisłą kolejność oraz unikatowość; jest to wartość wersji, a nie pole epoki.
Przechowuj dokładne żądanie i wersję do czasu poznania odpowiedzi API. Po przekroczeniu limitu czasu lub błędzie przejściowym ponów tę samą znormalizowaną migawkę z tą samą wersją. Nie przydzielaj nowej wersji tylko dlatego, że dostarczenie jest niepewne.
Anulowanie, wygaśnięcie i błędy źródła
- Anulowanie jednego okna: pomyślnie pobierz wiarygodny stan ze źródła, usuń to okno i wyślij pozostałą kompletną migawkę z nowszą wersją.
- Anulowanie wszystkich okien: wyślij
access_time_windows: []z nowszą wersją. - Wygaśnięcie okna bezwzględnego: okno przestaje zezwalać na dostęp w momencie
to; aby utrzymać porządek w migawkach, wyślij późniejsze uzgodnienie bez wygasłego wpisu. - Zakończenie okna tygodniowego: usuń je w nowszej migawce lub użyj
active_until, gdy źródło zna moment zakończenia. - Pobranie danych ze źródła nie powiodło się lub jest niekompletne: nie wysyłaj
[]i nie publikuj częściowej migawki. Wygeneruj alert i ponów pobranie. Zamiana błędu źródła na pustą migawkę oznacza niezamierzone odwołanie dostępu. - Żądanie Partner API nie przeszło walidacji: popraw dane i wyślij nowszą wersję tylko wtedy, gdy znormalizowana migawka się zmienia. Błędy przejściowe ponawiaj z pierwotną wersją.
Aktualność danych źródłowych jest konfigurowana dla każdej integracji na podstawie obiecanej częstotliwości dostarczania i uzgadniania. Przed aktywacją uzgodnij maksymalny wiek danych źródłowych, działanie po jego przekroczeniu, progi alertów oraz to, czy operatorzy mogą zaakceptować poleganie wyłącznie na granicach poszczególnych okien. Nieaktualne okno źródłowe nigdy nie staje się regułą odmowy; zwykłe reguły i stałe uprawnienia pozostają ścieżkami zapasowymi.
Algorytm producenta
Stosuj ten niezależny od języka proces przy każdej aktualizacji członka:
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)
Lista kontrolna certyfikacji
Wykonaj te kontrole w obiekcie testowym (stage), używając ostatecznego mapowania scope_key:
- Okno bezwzględne bez strefy czasowej jest akceptowane i oceniane jako UTC.
- Okno bezwzględne z niestandardową strefą IANA zachowuje te same momenty
from/to. - Okno tygodniowe z niestandardową strefą IANA podąża za lokalnym czasem zegarowym podczas przejścia na czas letni i powrotu do czasu zimowego.
- Mieszana migawka okien bezwzględnych/tygodniowych całkowicie zastępuje poprzednią migawkę.
- Dostęp rozpoczyna się dokładnie w momencie
fromi kończy dokładnie w momencieto; końce bloków tygodniowych również nie są wliczane. - Zmiana godzin dla tego samego
external_idz nowszą wersją zaczyna obowiązywać. - Pominięcie
access_time_windowszmienia inne pola członka bez zmiany okien. - Ponowne odtworzenie tej samej wersji/ładunku jest idempotentne.
- Ta sama wersja z innym ładunkiem zwraca konflikt i nie zmienia migawki.
- Starsza wersja dostarczona poza kolejnością jest zgłaszana jako nieaktualna i nie może cofnąć stanu.
- Nowsze
[]odwołuje okna należące do źródła; błąd pobrania danych ze źródła nie powoduje wysłania niczego. - Nieznany lub niezmapowany
scope_keypowoduje odmowę dostępu i tworzy ostrzeżenie widoczne dla operatora. - Alerty dotyczące wieku danych źródłowych i skonfigurowane zasady dla nieaktualnego źródła działają przy uzgodnionym progu.
- Ścieżki webhooków i pełnego uzgadniania prowadzą do tej samej migawki i kolejności wersji.
- Dostarczanie strumienia dociera do Door & Gate Access w uzgodnionym czasie docelowym.
- Aktywne okno może zezwolić na otwarcie zmapowanych fizycznych drzwi, ale nie może obejść wyłączenia użytkownika,
blockAllAccess, trybu serwisowego ani geofencingu. - Przed oknem, poza nim i po nim zwykłe reguły i stałe uprawnienia nadal zezwalają na dostęp użytkownikom, którzy w inny sposób spełniają warunki.
Minimalne wektory testowe
[
{
"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."
}
]
Prześlij również wektory negatywne dla milisekund Unix, ciągów liczbowych, liczb zmiennoprzecinkowych, nieprawidłowych stref IANA, from >= to, zduplikowanego external_id, nakładających się bloków, bloków nocnych, więcej niż ośmiu bloków w ciągu dnia, więcej niż 50 okien i niezmapowanych kluczy zakresu.