Janelas de Tempo de Acesso
As Access Time Windows permitem que um membro de parceiro tenha acesso temporário ou recorrente fornecido por um sistema externo. Esta página é o guia oficial para produtores de access_time_windows e access_time_windows_revision.
As Access Time Windows são permissões adicionais que apenas concedem acesso. Uma janela ativa pode permitir o acesso, mas uma janela inativa, expirada, desatualizada ou não mapeada não nega um acesso que uma regra regular ou uma concessão permanente já permite. A desativação de usuários, o bloqueio explícito de portas, o modo de manutenção, a geocerca, os limites de taxa e a disponibilidade do controlador continuam valendo.
As implementações para sistemas de origem específicos são adaptadores separados. Elas devem converter os dados de origem para este contrato independente de fornecedor sem alterar o significado dos campos.
Campos do snapshot do membro
-
access_time_windows
arrayopcionalO snapshot completo de Access Time Windows deste membro e desta origem de parceiro. Um snapshot pode combinar janelas
absoluteeweekly. Omita o campo para manter o snapshot armazenado inalterado; envie[]para revogar todas as janelas pertencentes a essa origem. -
access_time_windows_revision
integerobrigatório com access_time_windowsUm inteiro não negativo, monotonicamente crescente e seguro para JSON, para este fluxo de revisões de membro/origem. É obrigatório sempre que
access_time_windowsestiver presente e não pode exceder9007199254740991.
Cada snapshot aceita no máximo 50 janelas. external_id, scope_key e label aceitam no máximo 120 caracteres. As janelas absolutas podem abranger no máximo 90 dias. Um dia semanal aceita no máximo oito blocos sem sobreposição. Dados inválidos são rejeitados, e não truncados ou presumidos.
Formatos de janela
Uma janela absoluta identifica um intervalo exato:
{
"external_id": "access-4b1782",
"kind": "absolute",
"from": 1792053000,
"to": 1792058400,
"timezone": "Australia/Perth",
"scope_key": "after_hours_entry",
"label": "Temporary site access"
}
Os campos obrigatórios são external_id, kind, from e to. timezone, scope_key e label são opcionais.
Uma janela semanal identifica intervalos recorrentes no horário local, opcionalmente limitados por instantes em 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"
}
Os campos obrigatórios são external_id, kind e access_hours. timezone, active_from, active_until, scope_key e label são opcionais.
Solicitação combinada completa
Esta solicitação substitui o snapshot anterior do membro pertencente à origem por uma janela absoluta e uma janela 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"
}
]
}
]
}'
Use facility_id ou linking_identifier, de acordo com o método de vinculação configurado. Não envie os dois.
Regras de horário e limites
from, to, active_from e active_until são inteiros JSON em segundos Unix que identificam instantes em UTC:
- Envie um inteiro JSON sem aspas. Strings numéricas, números de ponto flutuante, strings RFC 3339 e milissegundos Unix são rejeitados.
- Todo intervalo é semiaberto:
[from, to). O acesso pode começar exatamente emfrome deixa de estar ativo exatamente emto. fromdeve ser menor queto. Quando os dois limites semanais estiverem presentes,active_fromdeve ser menor queactive_until.- Não derive épocas de uma data/hora local sem fuso horário. Primeiro, converta o horário de origem em um instante UTC, depois divida os milissegundos Unix por 1000 e verifique se o resultado é um inteiro.
Conversão de época
2026-10-15T08:30:00Z é convertido em 1792053000:
UTC instant Unix milliseconds Unix seconds sent
2026-10-15T08:30:00Z 1792053000000 1792053000
Conversão independente de linguagem:
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
Se a origem tiver precisão inferior a um segundo, defina uma política deliberada de arredondamento de início/fim antes dos testes de integração. Nunca envie milissegundos esperando que a API deduza a unidade.
Fusos horários e horário de verão
timezone deve ser um fuso horário IANA válido, como UTC, Australia/Perth ou America/New_York. O padrão é UTC quando omitido.
- Janelas absolutas:
frometojá identificam instantes UTC exatos.timezoneé apenas um metadado de exibição e auditoria; alterá-lo nunca desloca nenhum dos instantes. - Janelas semanais:
timezonedefine o horário local e as regras de horário de verão usadas para avaliaraccess_hours. Não converta previamente os blocos semanais para UTC. - Durante uma mudança em que o relógio é adiantado, um horário local que o fuso horário pula nunca ocorre.
- Durante uma mudança em que o relógio é atrasado, os dois instantes reais com o horário local repetido correspondem ao bloco semanal.
active_fromeactive_untilcontinuam sendo instantes UTC, mesmo quando uma janela semanal usa um fuso horário diferente de UTC.
access_hours usa sun a sat em minúsculas. Cada valor é uma matriz de pares [start, end] no formato HH:mm de 24 horas, com zeros à esquerda. Os blocos não podem ser vazios, devem estar ordenados e não podem se sobrepor. end é exclusivo. Um bloco não pode cruzar a meia-noite; divida um intervalo noturno de origem em um bloco no fim do primeiro dia e um bloco no início do dia seguinte.
IDs estáveis e mapeamento de escopo
external_id deve ser estável durante toda a vida de uma janela lógica e exclusivo dentro do snapshot do membro. Mantenha-o estável quando horários ou rótulos mudarem, para que os operadores e os registros de auditoria possam correlacionar as revisões. Não o reutilize para uma janela não relacionada.
scope_key é o seu destino lógico e estável de acesso. O padrão é default. Um administrador da instalação mapeia cada chave permitida para destinos do Performance Hub de toda a instalação, de portas ou de Escopo de acesso:
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
Os parceiros não enviam IDs de instalação, porta ou Escopo de acesso do Performance Hub dentro de uma janela. Um scope_key desconhecido, desativado ou não mapeado falha de forma fechada para essa janela; ele não amplia o acesso. Combine e teste os mapeamentos antes do lançamento.
Snapshots completos e revisões
Trate os dois campos como uma partição com versionamento próprio dentro do upsert de membro:
| Entrada | Resultado |
|---|---|
access_time_windows omitido | O snapshot existente permanece inalterado; nenhuma revisão é necessária |
| Presente com uma revisão mais nova | Todo o snapshot armazenado para este membro/origem é substituído |
| Mesma revisão e mesmo payload normalizado | Repetição idempotente; o snapshot não muda |
| Mesma revisão e payload normalizado diferente | Conflito; atribua uma nova revisão após a reconciliação |
| Revisão mais antiga | O snapshot desatualizado é ignorado; ele não pode substituir um estado mais recente |
access_time_windows: [] com uma revisão mais nova | Revoga explicitamente todas as janelas pertencentes à origem |
Um snapshot é completo, não um patch. Se a revisão 1842 tem as janelas A e B, a revisão 1843 contendo apenas B remove A.
Mantenha um único fluxo de revisões coordenado por parceiro/membro entre workers de webhook, polling, repetição e reconciliação completa. Uma sequência durável de banco de dados é preferível. Um valor em milissegundos Unix só pode ser usado como revisão quando todos os gravadores se coordenam e garantem ordenação e exclusividade estritas; ele é um valor de revisão, não um campo de época.
Mantenha a solicitação e a revisão exatas até que a resposta da API seja conhecida. Após um tempo limite ou erro transitório, repita o mesmo snapshot normalizado com a mesma revisão. Não atribua uma nova revisão apenas porque a entrega pelo transporte é incerta.
Cancelamento, expiração e falhas na origem
- Cancelar uma janela: consulte com sucesso o estado oficial da origem, remova essa janela e envie o snapshot completo restante com uma revisão mais nova.
- Cancelar todas as janelas: envie
access_time_windows: []com uma revisão mais nova. - Deixar uma janela absoluta expirar: ela deixa de permitir acesso em
to; envie uma reconciliação posterior sem a entrada expirada para manter os snapshots limpos. - Encerrar uma janela semanal: remova-a em um snapshot mais novo ou use
active_untilquando a origem tiver um instante de término conhecido. - A consulta à origem falha ou está incompleta: não envie
[]e não publique um snapshot parcial. Emita um alerta e repita a consulta. Transformar uma falha na origem em um snapshot vazio é uma revogação de acesso não intencional. - A solicitação à Partner API falha na validação: corrija os dados e envie uma revisão mais nova somente se o snapshot normalizado mudar. Repita falhas transitórias com a revisão original.
A atualização dos dados de origem é configurada por integração, com base na cadência de entrega e de reconciliação prometida. Antes da ativação, combine a idade máxima dos dados de origem, o que acontece quando ela é excedida, os limites de alerta e se os operadores podem aceitar depender apenas dos limites de cada janela. Uma janela de origem desatualizada nunca se torna uma regra de negação; as regras regulares e as concessões permanentes continuam sendo caminhos alternativos.
Algoritmo do produtor
Use este fluxo independente de linguagem em toda atualização de membro:
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 verificação de certificação
Conclua estas verificações em uma instalação de staging, usando o mapeamento final de scope_key:
- Uma janela absoluta é aceita com o fuso horário omitido e é avaliada em UTC.
- Uma janela absoluta com fuso horário IANA personalizado preserva os mesmos instantes
from/to. - Uma janela semanal com fuso horário IANA personalizado segue o horário local nas transições de adiantamento e atraso do relógio.
- Um snapshot combinado absoluto/semanal substitui completamente o snapshot anterior.
- O acesso começa exatamente em
frome termina exatamente emto; os fins dos blocos semanais também são exclusivos. - A atualização de horários sob o mesmo
external_idcom uma revisão mais nova entra em vigor. - Omitir
access_time_windowsaltera outros campos do membro sem alterar as janelas. - Repetir a mesma revisão/payload é idempotente.
- Mesma revisão/payload diferente retorna um conflito e não altera o snapshot.
- Uma revisão mais antiga fora de ordem é relatada como desatualizada e não pode reverter o estado.
- Um
[]mais novo revoga as janelas pertencentes à origem; uma falha na consulta à origem não envia nada. - Um
scope_keydesconhecido ou não mapeado falha de forma fechada e gera um aviso visível para o operador. - Os alertas de idade dos dados de origem e a política configurada para origem desatualizada funcionam no limite acordado.
- Os caminhos de webhook e de reconciliação completa convergem para o mesmo snapshot e a mesma ordem de revisões.
- A entrega pelo fluxo chega ao Door & Gate Access dentro da meta acordada.
- Uma janela ativa pode permitir a porta física mapeada, mas não pode contornar a desativação de usuários,
blockAllAccess, a manutenção ou a geocerca. - Antes, fora e depois de uma janela, as regras regulares e as concessões permanentes continuam permitindo os usuários que se qualificam de outra forma.
Vetores de teste 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."
}
]
Envie também vetores negativos para milissegundos Unix, strings numéricas, números de ponto flutuante, fusos IANA inválidos, from >= to, external_id duplicado, blocos sobrepostos, blocos noturnos, mais de oito blocos em um dia, mais de 50 janelas e chaves de escopo não mapeadas.