> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pictor.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Casar o cadastro com o seu

> Como ligar as câmeras do Pictor às do seu sistema — e a armadilha do identificador.

Antes de qualquer tela, os dois cadastros precisam se reconhecer. São duas
decisões, e a primeira é onde as integrações erram.

## A armadilha: qual identificador é a sua chave

`GET /api/devices` devolve dois identificadores por câmera, e eles servem para
coisas diferentes:

| Campo         | O que é                                                        | Único               |
| ------------- | -------------------------------------------------------------- | ------------------- |
| `device_id`   | o identificador do equipamento — normalmente o número de série | **dentro da conta** |
| `device_uuid` | o identificador interno da câmera                              | em qualquer lugar   |

<Warning>
  **`device_id` não é único entre contas.** Duas contas do Pictor podem ter uma
  câmera com o mesmo `device_id` — inclusive porque fabricantes repetem padrão de
  série entre lotes.
</Warning>

Consequência direta no seu banco:

```sql theme={null}
-- ERRADO: colide no dia em que você atender a segunda conta.
UNIQUE (pictor_device_id)

-- CERTO: a chave é o par.
UNIQUE (pictor_conta_id, pictor_device_id)

-- TAMBÉM CERTO, e mais simples se você não precisa do serial:
UNIQUE (pictor_device_uuid)
```

Use `device_uuid` como chave primária e guarde `device_id` como o rótulo que a
sua equipe reconhece. Todas as rotas aceitam os dois.

## A hierarquia: conta, cliente, local, câmera

```text theme={null}
Conta (uma chave = uma conta)
└── Cliente          ← o condomínio, a loja: quem paga o serviço
    └── Local Monitorado  ← o endereço vigiado
        └── Câmera
```

`GET /api/devices` já devolve tudo isso resolvido em cada linha — você não faz
uma segunda chamada para descobrir de quem é a câmera:

```json theme={null}
{
  "device_id": "G87232574",
  "device_uuid": "0b8f1c22-…",
  "name": "Guarita - entrada",
  "status": "active",
  "transmitting": "yes",
  "monitored_site_id": "…", "monitored_site_name": "Cond. Alvorada — Portaria",
  "client_id": "…",          "client_name": "Cond. Alvorada",
  "tax_id": "12.345.678/0001-90",
  "sowil_registro": "4021"
}
```

<Tip>
  `tax_id` (o CNPJ/CPF do cliente) é o campo que costuma casar com o seu cadastro
  sem nenhuma configuração — é o mesmo número nos dois lados. `sowil_registro` é a
  conta na central, quando existe.
</Tip>

## A sincronização

```python theme={null}
import httpx

def sincronizar(conta_id: str, chave: str, base: str) -> None:
    """Uma varredura por hora cobre qualquer operação real."""
    cabecalho = {"Authorization": f"Bearer {chave}"}
    with httpx.Client(base_url=base, headers=cabecalho, timeout=30) as http:
        offset, vistos = 0, set()
        while True:
            r = http.get("/api/devices", params={"limit": 500, "offset": offset})
            r.raise_for_status()
            pagina = r.json()["items"]
            if not pagina:
                break
            for camera in pagina:
                vistos.add(camera["device_uuid"])
                upsert(
                    conta_id=conta_id,
                    uuid=camera["device_uuid"],       # a chave
                    serial=camera["device_id"],       # o rótulo
                    nome=camera["name"],
                    cliente=camera.get("client_name"),
                    cnpj=camera.get("tax_id"),
                    local=camera.get("monitored_site_name"),
                )
            offset += len(pagina)

    # O que sumiu foi apagado no Pictor. NÃO apague do seu lado: marque.
    # Gravação e ocorrência antigas continuam apontando para a câmera.
    marcar_ausentes(conta_id, exceto=vistos)
```

<Warning>
  Não apague a câmera do seu lado quando ela some da listagem. As ocorrências e as
  gravações antigas continuam apontando para ela — e o Pictor mantém a gravação
  mesmo depois de a câmera sair do cadastro. Marque como inativa.
</Warning>

## Recortar por cliente

Se você quer só as câmeras de um condomínio:

```bash theme={null}
curl -s -H "Authorization: Bearer $PICTOR_KEY" \
  "$PICTOR_API/api/devices?client_id=$CLIENTE&limit=500"
```

Existe também o caminho de credencial: quem administra a conta pode emitir uma
chave **recortada a um cliente**. Ela não enxerga nada dos vizinhos — nem
inventário, nem eventos, nem vídeo, nem gravação. Se a sua arquitetura já separa
condomínios, peça uma chave por condomínio: o recorte deixa de depender de você
lembrar do filtro.

## Dois eixos de saúde, e eles discordam

| Campo          | O que é                                                                                   | O que ele responde           |
| -------------- | ----------------------------------------------------------------------------------------- | ---------------------------- |
| `status`       | herança: mistura cadastro (`active`/`inactive`/`offline`) com o estado ao vivo (`online`) | "alguém já configurou isto?" |
| `transmitting` | imagem: `yes`, `no`, `unknown`                                                            | "está chegando quadro?"      |

<Warning>
  **Para a sua tela de saúde, use `transmitting`, não `status`.** Uma câmera
  cadastrada como `active` aparece assim mesmo sem estar entregando imagem — e é
  esse o caso mais comum de reclamação de morador.
</Warning>

Os três valores de `transmitting`, e a diferença entre os dois últimos importa:

* **`yes`** — chegou quadro dentro da janela. Prova positiva de imagem.
* **`no`** — já chegou quadro desta câmera antes, e não dentro da janela. Prova
  de que **parou**. É esta a sua lista de ocorrências.
* **`unknown`** — nunca chegou quadro. Ausência de prova, não prova de ausência:
  normalmente cadastro recém-feito. Não abra ocorrência.

### A ressalva que evita o alarme falso em massa

Quando a medição de imagem está parada, **tudo** vira `unknown` — inclusive
câmeras com quadro recente. É deliberado: com a varredura morta, dizer `yes` para
as que alguém abriu e `no` para o resto seria um viés de amostragem vestido de
afirmação sobre a frota.

Então confira o envelope antes de concluir qualquer coisa:

```python theme={null}
corpo = r.json()
if not corpo["transmitting_witness_fresh"]:
    # "0 câmeras sem imagem" aqui significa "não deu para medir" — não
    # "está tudo bem". Não abra ocorrência, e não feche as que estão abertas.
    return

janela = corpo["transmitting_window_seconds"]   # a janela usada, em segundos
medido_em = corpo["transmitting_witness_last_success_at"]
```
