{
  "openapi": "3.0.3",
  "info": {
    "title": "Facial BR API",
    "version": "1.0.0",
    "description": "API REST do Facial BR para integrar sistemas (escolas, academias, RH, condomínios).\n\n- Autenticação: `Authorization: Bearer fbr_...` (chave criada em Painel › Integrações; mostrada uma vez).\n- Cada chave pertence a uma empresa e só enxerga os dados dela.\n- Limite: 120 chamadas por minuto por chave (cabeçalhos `X-RateLimit-*`; 429 com `Retry-After`).\n- Erros: `{\"erro\": {\"codigo\", \"mensagem\", \"campos\"?}}`.\n- Listas: `{\"dados\": [...], \"meta\": {pagina, por_pagina, total, paginas}}`.\n- Datas: ISO 8601. Toda alteração em usuários vai aos leitores logo em seguida, sem cron.\n- Webhooks de saída: POST JSON assinado (`X-FacialBR-Assinatura: sha256=HMAC(segredo, timestamp + \".\" + corpo)`).",
    "contact": {
      "name": "Facial BR",
      "url": "https://facialbr.com.br/contato"
    }
  },
  "servers": [
    {
      "url": "https://facialbr.com.br/api/v1",
      "description": "Produção"
    }
  ],
  "security": [
    {
      "chave": []
    }
  ],
  "tags": [
    {
      "name": "Status"
    },
    {
      "name": "Conta"
    },
    {
      "name": "Usuários"
    },
    {
      "name": "Horários"
    },
    {
      "name": "Leitores"
    },
    {
      "name": "Registros"
    },
    {
      "name": "Webhooks"
    }
  ],
  "paths": {
    "/eu": {
      "get": {
        "tags": [
          "Conta"
        ],
        "summary": "Dados da chave em uso",
        "responses": {
          "200": {
            "description": "Chave, empresa e escopos.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "$ref": "#/components/schemas/Eu"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        }
      }
    },
    "/usuarios": {
      "get": {
        "tags": [
          "Usuários"
        ],
        "summary": "Listar usuários",
        "responses": {
          "200": {
            "description": "Lista paginada.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Usuario"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Paginacao"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Escopo: `usuarios:ler`.",
        "parameters": [
          {
            "name": "cpf",
            "in": "query",
            "required": false,
            "description": "Filtra pelo CPF (com ou sem máscara).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "matricula",
            "in": "query",
            "required": false,
            "description": "Filtra pelo código externo.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "ativo, inativo ou bloqueado.",
            "schema": {
              "type": "string",
              "enum": [
                "ativo",
                "inativo",
                "bloqueado"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Parte do nome.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "atualizado_desde",
            "in": "query",
            "required": false,
            "description": "Só os alterados desde esta data/hora (ISO 8601).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pagina",
            "in": "query",
            "required": false,
            "description": "Página (a partir de 1).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "por_pagina",
            "in": "query",
            "required": false,
            "description": "Itens por página (máx. 200).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ]
      },
      "post": {
        "tags": [
          "Usuários"
        ],
        "summary": "Criar ou atualizar (upsert)",
        "responses": {
          "200": {
            "description": "Atualizado (`criado: false`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "$ref": "#/components/schemas/Usuario"
                    },
                    "criado": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "201": {
            "description": "Criado (`criado: true`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "$ref": "#/components/schemas/Usuario"
                    },
                    "criado": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/JsonInvalido"
          },
          "409": {
            "description": "CPF já cadastrado em outra empresa ou como administrador.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Validacao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Procura pelo CPF; se não vier CPF, pela matrícula. Encontrou: atualiza só os campos enviados. Não encontrou: cria (CPF, nome e data de nascimento obrigatórios). Senha não é aceita.\n\nEscopo: `usuarios:escrever`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cpf": {
                    "type": "string",
                    "example": "529.982.247-25",
                    "description": "Obrigatório no cadastro. Aceita com ou sem máscara; dígitos verificadores conferidos."
                  },
                  "nome": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 160,
                    "example": "Maria da Silva"
                  },
                  "data_nascimento": {
                    "type": "string",
                    "example": "1990-05-10",
                    "description": "Obrigatória no cadastro. AAAA-MM-DD ou DD/MM/AAAA; não pode estar no futuro."
                  },
                  "matricula": {
                    "type": "string",
                    "maxLength": 40,
                    "description": "Código externo (id no seu sistema). Usado no upsert quando o CPF não for enviado."
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "telefone": {
                    "type": "string",
                    "example": "(85) 99999-0000"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "ativo",
                      "inativo",
                      "bloqueado"
                    ]
                  },
                  "horario_id": {
                    "type": "integer",
                    "nullable": true
                  },
                  "valido_de": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  },
                  "valido_ate": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  },
                  "grupos": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  },
                  "foto_base64": {
                    "type": "string",
                    "description": "JPEG/PNG/WebP em base64 (com ou sem prefixo data:). Mínimo 200 px no menor lado."
                  }
                }
              },
              "example": {
                "cpf": "529.982.247-25",
                "nome": "Maria da Silva",
                "data_nascimento": "1990-05-10",
                "matricula": "ALU-1020",
                "grupos": [
                  1
                ]
              }
            }
          }
        }
      }
    },
    "/usuarios/{id}": {
      "get": {
        "tags": [
          "Usuários"
        ],
        "summary": "Buscar usuário",
        "responses": {
          "200": {
            "description": "Usuário.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "$ref": "#/components/schemas/Usuario"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NaoEncontrado"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Escopo: `usuarios:ler`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      },
      "put": {
        "tags": [
          "Usuários"
        ],
        "summary": "Alterar usuário",
        "responses": {
          "200": {
            "description": "Atualizado.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "$ref": "#/components/schemas/Usuario"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NaoEncontrado"
          },
          "422": {
            "$ref": "#/components/responses/Validacao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Só os campos enviados mudam. PATCH também aceito.\n\nEscopo: `usuarios:escrever`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cpf": {
                    "type": "string",
                    "example": "529.982.247-25",
                    "description": "Obrigatório no cadastro. Aceita com ou sem máscara; dígitos verificadores conferidos."
                  },
                  "nome": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 160,
                    "example": "Maria da Silva"
                  },
                  "data_nascimento": {
                    "type": "string",
                    "example": "1990-05-10",
                    "description": "Obrigatória no cadastro. AAAA-MM-DD ou DD/MM/AAAA; não pode estar no futuro."
                  },
                  "matricula": {
                    "type": "string",
                    "maxLength": 40,
                    "description": "Código externo (id no seu sistema). Usado no upsert quando o CPF não for enviado."
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "telefone": {
                    "type": "string",
                    "example": "(85) 99999-0000"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "ativo",
                      "inativo",
                      "bloqueado"
                    ]
                  },
                  "horario_id": {
                    "type": "integer",
                    "nullable": true
                  },
                  "valido_de": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  },
                  "valido_ate": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  },
                  "grupos": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  },
                  "foto_base64": {
                    "type": "string",
                    "description": "JPEG/PNG/WebP em base64 (com ou sem prefixo data:). Mínimo 200 px no menor lado."
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Usuários"
        ],
        "summary": "Alterar usuário (parcial)",
        "responses": {
          "200": {
            "description": "Atualizado.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "$ref": "#/components/schemas/Usuario"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Escopo: `usuarios:escrever`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cpf": {
                    "type": "string",
                    "example": "529.982.247-25",
                    "description": "Obrigatório no cadastro. Aceita com ou sem máscara; dígitos verificadores conferidos."
                  },
                  "nome": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 160,
                    "example": "Maria da Silva"
                  },
                  "data_nascimento": {
                    "type": "string",
                    "example": "1990-05-10",
                    "description": "Obrigatória no cadastro. AAAA-MM-DD ou DD/MM/AAAA; não pode estar no futuro."
                  },
                  "matricula": {
                    "type": "string",
                    "maxLength": 40,
                    "description": "Código externo (id no seu sistema). Usado no upsert quando o CPF não for enviado."
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "telefone": {
                    "type": "string",
                    "example": "(85) 99999-0000"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "ativo",
                      "inativo",
                      "bloqueado"
                    ]
                  },
                  "horario_id": {
                    "type": "integer",
                    "nullable": true
                  },
                  "valido_de": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  },
                  "valido_ate": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  },
                  "grupos": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  },
                  "foto_base64": {
                    "type": "string",
                    "description": "JPEG/PNG/WebP em base64 (com ou sem prefixo data:). Mínimo 200 px no menor lado."
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Usuários"
        ],
        "summary": "Desativar usuário",
        "responses": {
          "200": {
            "description": "Desativado (status inativo); sai dos leitores.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "$ref": "#/components/schemas/Usuario"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NaoEncontrado"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Não apaga o histórico: muda o status para `inativo` e remove a pessoa dos leitores.\n\nEscopo: `usuarios:escrever`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/usuarios/{id}/foto": {
      "post": {
        "tags": [
          "Usuários"
        ],
        "summary": "Enviar foto facial",
        "responses": {
          "200": {
            "description": "Foto gravada; vai aos leitores em seguida.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "$ref": "#/components/schemas/Usuario"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Validacao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Rosto de frente, bem iluminado. A imagem é ajustada para o tamanho aceito pelo leitor (JPEG até ~200 KB).\n\nEscopo: `usuarios:escrever`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "foto": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "foto_base64": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/horarios": {
      "get": {
        "tags": [
          "Horários"
        ],
        "summary": "Listar horários",
        "responses": {
          "200": {
            "description": "Lista paginada.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Horario"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Paginacao"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Escopo: `horarios:ler`.",
        "parameters": [
          {
            "name": "pagina",
            "in": "query",
            "required": false,
            "description": "Página (a partir de 1).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "por_pagina",
            "in": "query",
            "required": false,
            "description": "Itens por página (máx. 200).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ]
      }
    },
    "/horarios/{id}": {
      "get": {
        "tags": [
          "Horários"
        ],
        "summary": "Buscar horário",
        "responses": {
          "200": {
            "description": "Horário.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "$ref": "#/components/schemas/Horario"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NaoEncontrado"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Escopo: `horarios:ler`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/leitores": {
      "get": {
        "tags": [
          "Leitores"
        ],
        "summary": "Listar leitores",
        "responses": {
          "200": {
            "description": "Lista paginada (sem endereço nem credenciais).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Leitor"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Paginacao"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Escopo: `leitores:ler`.",
        "parameters": [
          {
            "name": "pagina",
            "in": "query",
            "required": false,
            "description": "Página (a partir de 1).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "por_pagina",
            "in": "query",
            "required": false,
            "description": "Itens por página (máx. 200).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ]
      }
    },
    "/leitores/{id}": {
      "get": {
        "tags": [
          "Leitores"
        ],
        "summary": "Buscar leitor",
        "responses": {
          "200": {
            "description": "Leitor.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "$ref": "#/components/schemas/Leitor"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NaoEncontrado"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Escopo: `leitores:ler`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/registros": {
      "get": {
        "tags": [
          "Registros"
        ],
        "summary": "Listar marcações (frequência)",
        "responses": {
          "200": {
            "description": "Com `desde_id`: meta {desde_id, proximo_cursor, por_pagina, tem_mais}. Sem: paginação comum.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Registro"
                      }
                    },
                    "meta": {
                      "type": "object"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Validacao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Para sincronizar a frequência com o seu sistema use o cursor: comece com `desde_id=0` e repita com `desde_id=meta.proximo_cursor` enquanto `tem_mais` for true. Ou receba em tempo real pelo webhook `registro.criado`.\n\nEscopo: `registros:ler`.",
        "parameters": [
          {
            "name": "desde_id",
            "in": "query",
            "required": false,
            "description": "Cursor: registros com id maior que este, em ordem crescente.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "de",
            "in": "query",
            "required": false,
            "description": "Início (AAAA-MM-DD ou ISO 8601).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ate",
            "in": "query",
            "required": false,
            "description": "Fim (AAAA-MM-DD ou ISO 8601).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "usuario_id",
            "in": "query",
            "required": false,
            "description": "Id do usuário.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "cpf",
            "in": "query",
            "required": false,
            "description": "CPF do usuário.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "matricula",
            "in": "query",
            "required": false,
            "description": "Código externo do usuário.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "leitor_id",
            "in": "query",
            "required": false,
            "description": "Id do leitor.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "tipo",
            "in": "query",
            "required": false,
            "description": "entrada, saida ou indefinido.",
            "schema": {
              "type": "string",
              "enum": [
                "entrada",
                "saida",
                "indefinido"
              ]
            }
          },
          {
            "name": "resultado",
            "in": "query",
            "required": false,
            "description": "liberado ou negado.",
            "schema": {
              "type": "string",
              "enum": [
                "liberado",
                "negado"
              ]
            }
          },
          {
            "name": "pagina",
            "in": "query",
            "required": false,
            "description": "Página (a partir de 1).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "por_pagina",
            "in": "query",
            "required": false,
            "description": "Itens por página (máx. 200).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ]
      }
    },
    "/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Listar webhooks",
        "responses": {
          "200": {
            "description": "Lista.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    },
                    "eventos_disponiveis": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Escopo: `webhooks:gerenciar`."
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Criar webhook",
        "responses": {
          "201": {
            "description": "Criado. O `segredo` só aparece nesta resposta.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Webhook"
                        },
                        {
                          "type": "object",
                          "properties": {
                            "segredo": {
                              "type": "string",
                              "example": "whsec_..."
                            }
                          }
                        }
                      ]
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Validacao"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Escopo: `webhooks:gerenciar`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEntrada"
              },
              "example": {
                "nome": "ERP",
                "url": "https://erp.exemplo.com.br/facialbr",
                "eventos": [
                  "registro.criado"
                ]
              }
            }
          }
        }
      }
    },
    "/webhooks/{id}": {
      "put": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Alterar webhook",
        "responses": {
          "200": {
            "description": "Atualizado.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "$ref": "#/components/schemas/Webhook"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NaoEncontrado"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Escopo: `webhooks:gerenciar`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEntrada"
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Alterar webhook (parcial)",
        "responses": {
          "200": {
            "description": "Atualizado.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dados": {
                      "$ref": "#/components/schemas/Webhook"
                    }
                  },
                  "required": [
                    "dados"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Escopo: `webhooks:gerenciar`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEntrada"
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Excluir webhook",
        "responses": {
          "204": {
            "description": "Excluído."
          },
          "404": {
            "$ref": "#/components/responses/NaoEncontrado"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutorizado"
          },
          "403": {
            "$ref": "#/components/responses/SemPermissao"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          }
        },
        "description": "Escopo: `webhooks:gerenciar`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    }
  },
  "x-webhooks": {
    "evento": {
      "post": {
        "summary": "Entrega de evento (Facial BR → seu sistema)",
        "description": "Enviado logo depois do evento. Responda 2xx em até 10 s. Falhou: novas tentativas após 15 s, 1 min, 5 min, 30 min, 1 h, 3 h e 6 h. Confira a assinatura e o timestamp (rejeite se > 5 min).",
        "parameters": [
          {
            "name": "X-FacialBR-Evento",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "registro.criado, usuario.criado, usuario.alterado, usuario.excluido ou leitor.status"
          },
          {
            "name": "X-FacialBR-Entrega",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Id da entrega (use para descartar repetidas)."
          },
          {
            "name": "X-FacialBR-Timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Unix timestamp do envio."
          },
          {
            "name": "X-FacialBR-Assinatura",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "sha256=HMAC-SHA256(segredo, timestamp + \".\" + corpo) em hexadecimal."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventoWebhook"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recebido."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "chave": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "fbr_<48 hex>"
      }
    },
    "responses": {
      "NaoAutorizado": {
        "description": "Chave ausente, inválida ou revogada.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            }
          }
        }
      },
      "SemPermissao": {
        "description": "A chave não tem o escopo necessário.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            }
          }
        }
      },
      "NaoEncontrado": {
        "description": "Não encontrado (ou de outra empresa).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            }
          }
        }
      },
      "Validacao": {
        "description": "Campos inválidos (`erro.campos`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            }
          }
        }
      },
      "JsonInvalido": {
        "description": "Corpo não é JSON válido.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            }
          }
        }
      },
      "LimiteExcedido": {
        "description": "Mais de 120 chamadas no minuto.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            }
          }
        },
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        }
      }
    },
    "schemas": {
      "Erro": {
        "type": "object",
        "properties": {
          "erro": {
            "type": "object",
            "properties": {
              "codigo": {
                "type": "string",
                "example": "validacao"
              },
              "mensagem": {
                "type": "string"
              },
              "campos": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            },
            "required": [
              "codigo",
              "mensagem"
            ]
          }
        }
      },
      "Paginacao": {
        "type": "object",
        "properties": {
          "pagina": {
            "type": "integer"
          },
          "por_pagina": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "paginas": {
            "type": "integer"
          }
        }
      },
      "Eu": {
        "type": "object",
        "properties": {
          "integracao": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "nome": {
                "type": "string"
              },
              "prefixo": {
                "type": "string"
              },
              "escopos": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "empresa": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "nome": {
                "type": "string"
              },
              "cnpj": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "limite_por_minuto": {
            "type": "integer"
          }
        }
      },
      "Usuario": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "nome": {
            "type": "string"
          },
          "cpf": {
            "type": "string",
            "example": "52998224725"
          },
          "data_nascimento": {
            "type": "string",
            "format": "date"
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "telefone": {
            "type": "string",
            "nullable": true,
            "example": "85999990000"
          },
          "matricula": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "ativo",
              "inativo",
              "bloqueado"
            ]
          },
          "valido_de": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "valido_ate": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "horario_id": {
            "type": "integer",
            "nullable": true
          },
          "tem_foto": {
            "type": "boolean"
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "grupos": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          }
        }
      },
      "Horario": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "nome": {
            "type": "string"
          },
          "descricao": {
            "type": "string",
            "nullable": true
          },
          "ativo": {
            "type": "boolean"
          },
          "regras": {
            "type": "object",
            "properties": {
              "tolerancia_entrada_min": {
                "type": "integer"
              },
              "tolerancia_saida_min": {
                "type": "integer"
              },
              "intervalo_minimo_min": {
                "type": "integer"
              },
              "fora_da_janela": {
                "type": "string"
              }
            }
          },
          "faixas": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "dia_semana": {
                  "type": "integer",
                  "description": "0 = domingo"
                },
                "dia": {
                  "type": "string"
                },
                "inicio": {
                  "type": "string",
                  "example": "07:00"
                },
                "fim": {
                  "type": "string",
                  "example": "12:00"
                }
              }
            }
          },
          "resumo": {
            "type": "string"
          }
        }
      },
      "Leitor": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "nome": {
            "type": "string"
          },
          "local": {
            "type": "string",
            "nullable": true
          },
          "modelo": {
            "type": "string",
            "nullable": true
          },
          "numero_serie": {
            "type": "string",
            "nullable": true
          },
          "sentido": {
            "type": "string",
            "enum": [
              "entrada",
              "saida",
              "ambos"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "ativo",
              "inativo"
            ]
          },
          "online": {
            "type": "boolean"
          },
          "ultimo_contato": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "sincronizacao": {
            "type": "object",
            "properties": {
              "pendentes": {
                "type": "integer"
              },
              "com_erro": {
                "type": "integer"
              }
            }
          }
        }
      },
      "Registro": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "data_hora": {
            "type": "string",
            "format": "date-time"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "entrada",
              "saida",
              "indefinido"
            ]
          },
          "metodo": {
            "type": "string",
            "example": "face"
          },
          "resultado": {
            "type": "string",
            "enum": [
              "liberado",
              "negado"
            ]
          },
          "situacao": {
            "type": "string",
            "example": "no_horario"
          },
          "motivo": {
            "type": "string",
            "nullable": true
          },
          "origem": {
            "type": "string",
            "enum": [
              "push",
              "coleta",
              "manual"
            ]
          },
          "usuario": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "integer"
              },
              "nome": {
                "type": "string"
              },
              "cpf": {
                "type": "string"
              },
              "matricula": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "leitor": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "integer"
              },
              "nome": {
                "type": "string"
              },
              "local": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "employee_no": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "nome": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "eventos": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "ativo": {
            "type": "boolean"
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookEntrada": {
        "type": "object",
        "properties": {
          "nome": {
            "type": "string",
            "minLength": 3
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "HTTPS pública em produção."
          },
          "eventos": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "registro.criado",
                "usuario.criado",
                "usuario.alterado",
                "usuario.excluido",
                "leitor.status"
              ]
            }
          },
          "ativo": {
            "type": "boolean"
          }
        }
      },
      "EventoWebhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "evento": {
            "type": "string",
            "example": "registro.criado"
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          },
          "empresa_id": {
            "type": "integer"
          },
          "dados": {
            "type": "object",
            "description": "Registro, Usuario ou estado do leitor, conforme o evento."
          }
        }
      }
    }
  }
}
