Ventanas de tiempo de acceso
Las Access Time Windows permiten que un miembro de socio tenga acceso temporal o recurrente proporcionado por un sistema externo. Esta página es la guía oficial para productores de access_time_windows y access_time_windows_revision.
Las Access Time Windows son permisos adicionales que solo permiten. Una ventana activa puede permitir el acceso, pero una ventana inactiva, vencida, obsoleta o sin asignar no deniega el acceso que ya permite una regla habitual o un permiso permanente. La desactivación del usuario, el bloqueo explícito de puertas, el modo de mantenimiento, la geovalla, los límites de frecuencia y la disponibilidad del controlador siguen aplicándose.
Las implementaciones para sistemas de origen concretos son adaptadores independientes. Deben traducir sus datos de origen a este contrato independiente del proveedor sin cambiar el significado de sus campos.
Campos de la instantánea del miembro
-
access_time_windows
arrayopcionalLa instantánea completa de Access Time Windows para este miembro y esta fuente del socio. Una instantánea puede combinar ventanas
absoluteyweekly. Omite el campo para no modificar la instantánea guardada; envía[]para revocar todas las ventanas que pertenecen a esta fuente. -
access_time_windows_revision
integerobligatorio con access_time_windowsUn entero no negativo, monótonamente creciente y seguro para JSON, correspondiente a la secuencia de revisiones de este miembro y esta fuente. Es obligatorio siempre que
access_time_windowsesté presente y no puede superar9007199254740991.
Cada instantánea acepta como máximo 50 ventanas. external_id, scope_key y label aceptan como máximo 120 caracteres. Las ventanas absolutas pueden abarcar como máximo 90 días. Un día semanal acepta como máximo ocho bloques que no se superpongan. Los datos no válidos se rechazan en lugar de truncarse o interpretarse.
Formas de las ventanas
Una ventana absoluta identifica un intervalo exacto:
{
"external_id": "access-4b1782",
"kind": "absolute",
"from": 1792053000,
"to": 1792058400,
"timezone": "Australia/Perth",
"scope_key": "after_hours_entry",
"label": "Temporary site access"
}
Los campos obligatorios son external_id, kind, from y to. timezone, scope_key y label son opcionales.
Una ventana semanal identifica intervalos recurrentes según la hora local del reloj, opcionalmente acotados por instantes 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"
}
Los campos obligatorios son external_id, kind y access_hours. timezone, active_from, active_until, scope_key y label son opcionales.
Solicitud combinada completa
Esta solicitud reemplaza la instantánea anterior del miembro que pertenece a la fuente por una ventana absoluta y una semanal:
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"
}
]
}
]
}'
Usa facility_id o linking_identifier según el método de vinculación configurado. No envíes ambos.
Reglas de hora y límites
from, to, active_from y active_until son segundos Unix como enteros JSON que identifican instantes UTC:
- Envía un entero JSON sin comillas. Se rechazan las cadenas numéricas, los números de coma flotante, las cadenas RFC 3339 y los milisegundos Unix.
- Todos los intervalos son semiabiertos:
[from, to). El acceso puede empezar exactamente enfrom; deja de estar activo exactamente ento. fromdebe ser menor queto. Cuando están presentes los dos límites semanales,active_fromdebe ser menor queactive_until.- No obtengas las épocas a partir de una fecha y hora local sin zona horaria. Primero resuelve la hora de origen a un instante UTC, luego divide los milisegundos Unix entre 1000 y verifica que el resultado sea un entero.
Conversión de épocas
2026-10-15T08:30:00Z se convierte en 1792053000:
UTC instant Unix milliseconds Unix seconds sent
2026-10-15T08:30:00Z 1792053000000 1792053000
Conversión independiente del lenguaje:
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
Si la fuente tiene precisión inferior al segundo, define una política deliberada de redondeo del inicio y el fin antes de las pruebas de integración. Nunca pases milisegundos esperando que la API deduzca la unidad.
Zonas horarias y horario de verano
timezone debe ser una zona horaria IANA válida, como UTC, Australia/Perth o America/New_York. Si se omite, el valor predeterminado es UTC.
- Ventanas absolutas:
fromytoya identifican instantes UTC exactos.timezonees solo un metadato de visualización y auditoría; cambiarlo nunca desplaza ninguno de los dos instantes. - Ventanas semanales:
timezonedefine la hora local del reloj y las reglas de horario de verano que se usan para evaluaraccess_hours. No conviertas previamente los bloques semanales a UTC. - Cuando se adelanta la hora, una hora local que la zona horaria omite nunca ocurre.
- Cuando se atrasa la hora, los dos instantes reales que tienen la hora local repetida coinciden con el bloque semanal.
active_fromyactive_untilsiguen siendo instantes UTC incluso cuando una ventana semanal usa una zona horaria distinta de UTC.
access_hours usa los valores en minúsculas de sun a sat. Cada valor es una matriz de pares [start, end] en formato HH:mm de 24 horas con ceros a la izquierda. Los bloques no pueden estar vacíos, deben estar ordenados y no pueden superponerse. end es exclusivo. Un bloque no puede cruzar la medianoche; divide un intervalo nocturno de origen en un bloque tardío el primer día y un bloque temprano el día siguiente.
ID estables y asignación de ámbitos
external_id debe ser estable durante toda la vida de una ventana lógica y único dentro de la instantánea del miembro. Mantenlo estable cuando cambien las horas o las etiquetas para que los operadores y los registros de auditoría puedan relacionar las revisiones. No lo reutilices para una ventana no relacionada.
scope_key es tu destino de acceso lógico estable. Su valor predeterminado es default. Un administrador de la instalación asigna cada clave permitida a destinos del Performance Hub que abarcan toda la instalación, una puerta o un Alcance de acceso:
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
Los socios no envían ID de instalación, de puerta ni de Alcance de acceso del Performance Hub dentro de una ventana. Un scope_key desconocido, deshabilitado o sin asignar deniega el acceso para esa ventana; no amplía el acceso. Acuerda y prueba las asignaciones antes del lanzamiento.
Instantáneas completas y revisiones
Trata los dos campos como una partición con versiones independientes dentro de la inserción o actualización del miembro:
| Entrada | Resultado |
|---|---|
Se omite access_time_windows | La instantánea existente no cambia; no se requiere una revisión |
| Presente con una revisión más reciente | Se reemplaza toda la instantánea guardada para este miembro y esta fuente |
| Misma revisión y misma carga útil normalizada | Repetición idempotente; la instantánea no cambia |
| Misma revisión y carga útil normalizada diferente | Conflicto; asigna una nueva revisión después de conciliar |
| Revisión anterior | Se omite la instantánea obsoleta; no puede sobrescribir un estado más reciente |
access_time_windows: [] con una revisión más reciente | Revoca explícitamente todas las ventanas que pertenecen a la fuente |
Una instantánea es completa, no un parche. Si la revisión 1842 tiene las ventanas A y B, la revisión 1843, que solo contiene B, elimina A.
Mantén una única secuencia de revisiones coordinada por socio y miembro entre los procesos de webhooks, el sondeo, las repeticiones y la conciliación completa. Es preferible usar una secuencia duradera de base de datos. Un valor en milisegundos Unix solo puede usarse como revisión cuando todos los procesos que escriben se coordinan y garantizan un orden estricto y valores únicos; es un valor de revisión, no un campo de época.
Conserva la solicitud y la revisión exactas hasta conocer la respuesta de la API. Después de un tiempo de espera agotado o un error transitorio, reintenta la misma instantánea normalizada con la misma revisión. No asignes una nueva revisión solo porque la entrega sea incierta.
Cancelación, vencimiento y errores de la fuente de origen
- Cancelar una ventana: consulta correctamente el estado autorizado de la fuente, quita esa ventana y envía la instantánea completa restante con una revisión más reciente.
- Cancelar todas las ventanas: envía
access_time_windows: []con una revisión más reciente. - Dejar que venza una ventana absoluta: deja de permitir el acceso en
to; envía una conciliación posterior sin la entrada vencida para mantener limpias las instantáneas. - Finalizar una ventana semanal: quítala en una instantánea más reciente o usa
active_untilcuando la fuente tenga un instante de fin conocido. - La consulta a la fuente falla o está incompleta: no envíes
[]ni publiques una instantánea parcial. Emite una alerta y reintenta la consulta. Convertir un error de la fuente de origen en una instantánea vacía supone una revocación de acceso no deseada. - La solicitud a la Partner API no supera la validación: corrige los datos y envía una revisión más reciente solo si cambia la instantánea normalizada. Reintenta los errores transitorios con la revisión original.
La actualización de la fuente se configura para cada integración según la frecuencia de entrega y de conciliación comprometida. Antes de la activación, acuerda la antigüedad máxima de la fuente, qué ocurre cuando se supera, los umbrales de alerta y si los operadores pueden aceptar basarse solo en los límites propios de cada ventana. Una ventana de una fuente obsoleta nunca se convierte en una regla de denegación; las reglas habituales y los permisos permanentes siguen siendo vías alternativas.
Algoritmo del productor
Usa este flujo de trabajo independiente del lenguaje en cada actualización de miembro:
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 de comprobación de certificación
Completa estas comprobaciones en una instalación de pruebas con la asignación final de scope_key:
- Se acepta una ventana absoluta sin zona horaria y se evalúa como UTC.
- Una ventana absoluta con una zona IANA personalizada conserva los mismos instantes
from/to. - Una ventana semanal con una zona IANA personalizada sigue la hora local del reloj en los cambios de adelanto y atraso de la hora.
- Una instantánea combinada absoluta y semanal reemplaza por completo la instantánea anterior.
- El acceso empieza exactamente en
fromy se detiene exactamente ento; los finales de los bloques semanales también son exclusivos. - Actualizar las horas con el mismo
external_idy una revisión más reciente surte efecto. - Omitir
access_time_windowsmodifica otros campos del miembro sin cambiar las ventanas. - Repetir la misma revisión y carga útil es idempotente.
- La misma revisión con una carga útil diferente devuelve un conflicto y no cambia la instantánea.
- Una revisión anterior recibida fuera de orden se notifica como obsoleta y no puede revertir el estado.
- Un
[]más reciente revoca las ventanas que pertenecen a la fuente; un error en la consulta a la fuente de origen no envía nada. - Un
scope_keydesconocido o sin asignar deniega el acceso y genera una advertencia visible para el operador. - Las alertas de antigüedad de la fuente y la política configurada para fuentes obsoletas funcionan con el umbral acordado.
- Las vías de webhook y de conciliación completa convergen en la misma instantánea y el mismo orden de revisiones.
- La entrega de la secuencia llega a Door & Gate Access dentro del objetivo acordado.
- Una ventana activa puede permitir el acceso a la puerta física asignada, pero no puede omitir la desactivación del usuario,
blockAllAccess, el mantenimiento ni la geovalla. - Antes, fuera y después de una ventana, las reglas habituales y los permisos permanentes siguen permitiendo el acceso a los usuarios que cumplen los requisitos.
Vectores de prueba mínimos
[
{
"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."
}
]
Envía también vectores negativos para milisegundos Unix, cadenas numéricas, números de coma flotante, zonas IANA no válidas, from >= to, external_id duplicados, bloques superpuestos, bloques nocturnos, más de ocho bloques en un día, más de 50 ventanas y claves de ámbito sin asignar.