openapi: 3.1.0
info:
  title: Reliance API — Consulta de Pessoas
  version: 1.0.0
  summary: Busca nominal sem consumo e consulta cadastral confirmada.
  description: |
    API privada para consultar todos os candidatos de um nome completo e,
    somente após escolha do CPF e confirmação explícita de consumo, obter o
    cadastro completo. A busca nominal nunca abre cadastro automaticamente.
  license:
    name: Uso privado da Reliance
    url: https://api.reliancecrm.com.br/padrao
servers:
  - url: https://api.reliancecrm.com.br
    description: Produção
tags:
  - name: Sistema
    description: Saúde e contratos públicos do gateway.
  - name: Nominal
    description: Consultas por nome completo sem abertura de cadastro.
  - name: Completa
    description: Consultas cadastrais condicionadas à confirmação de consumo.
  - name: Lotes
    description: Processamento assíncrono com checkpoint e consulta de progresso.
security:
  - ApiKey: []
paths:
  /health:
    get:
      tags: [Sistema]
      security: []
      operationId: getHealth
      summary: Verifica a saúde do serviço
      responses:
        "200":
          description: Serviço ativo
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Health" }
              examples:
                ativo:
                  value: { status: ok, service: consulta-pessoas, version: 1.0.0 }
  /catalog.json:
    get:
      tags: [Sistema]
      security: []
      operationId: getCatalog
      summary: Lista os robôs publicados no catálogo central
      responses:
        "200":
          description: Catálogo legível por máquina
          content:
            application/json:
              schema: { type: object }
  /robot-manifest.schema.json:
    get:
      tags: [Sistema]
      security: []
      operationId: getRobotManifestSchema
      summary: Retorna o JSON Schema obrigatório para novos robôs
      responses:
        "200":
          description: JSON Schema do manifesto
          content:
            application/schema+json:
              schema: { type: object }
  /v1/nominal:
    post:
      tags: [Nominal]
      operationId: lookupByFullName
      summary: Retorna todos os candidatos de um nome completo
      description: Fluxo sem consumo observado. Não abre cadastro nem escolhe homônimo.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/NominalRequest" }
            examples:
              pessoa:
                value: { nome: PESSOA DE EXEMPLO }
      responses:
        "200":
          description: Busca concluída; a lista pode estar vazia ou conter vários candidatos
          content:
            application/json:
              schema: { $ref: "#/components/schemas/NominalResponse" }
        "400": { $ref: "#/components/responses/InvalidInput" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "502": { $ref: "#/components/responses/SourceContractChanged" }
        "503": { $ref: "#/components/responses/SourceUnavailable" }
  /v1/nominal/lotes:
    post:
      tags: [Nominal, Lotes]
      operationId: createNominalBatch
      summary: Cria um lote assíncrono de nomes
      description: Deduplica nomes normalizados antes de consultar e mantém checkpoint cifrado.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [nomes]
              properties:
                nomes:
                  type: array
                  minItems: 1
                  maxItems: 5000
                  items: { type: string, minLength: 5, maxLength: 180 }
            examples:
              lote:
                value: { nomes: [PESSOA DE EXEMPLO, OUTRA PESSOA DE EXEMPLO] }
      responses:
        "202":
          description: Lote aceito
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BatchAccepted" }
        "400": { $ref: "#/components/responses/InvalidInput" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /v1/completa:
    post:
      tags: [Completa]
      operationId: lookupFullRecord
      summary: Consulta cadastro e telefones de um CPF escolhido
      description: "Pode consumir uma consulta. Exige `confirmarConsumo: true`."
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/FullRequest" }
            examples:
              confirmada:
                value: { cpf: "11144477735", confirmarConsumo: true }
      responses:
        "200":
          description: Cadastro encontrado ou resposta reutilizada do cache
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FullResponse" }
        "400": { $ref: "#/components/responses/InvalidInput" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "409": { $ref: "#/components/responses/ConsumptionRequired" }
        "502": { $ref: "#/components/responses/SourceContractChanged" }
        "503": { $ref: "#/components/responses/SourceUnavailable" }
  /v1/completa/lotes:
    post:
      tags: [Completa, Lotes]
      operationId: createFullBatch
      summary: Cria um lote assíncrono de CPFs
      description: Cada CPF fora do cache pode consumir uma consulta.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [cpfs, confirmarConsumo]
              properties:
                cpfs:
                  type: array
                  minItems: 1
                  maxItems: 5000
                  items: { type: string, pattern: "^[0-9.\\/-]{11,14}$" }
                confirmarConsumo: { type: boolean, const: true }
            examples:
              loteConfirmado:
                value: { cpfs: ["11144477735"], confirmarConsumo: true }
      responses:
        "202":
          description: Lote aceito
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BatchAccepted" }
        "400": { $ref: "#/components/responses/InvalidInput" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "409": { $ref: "#/components/responses/ConsumptionRequired" }
  /v1/lotes/{id}:
    get:
      tags: [Lotes]
      operationId: getBatch
      summary: Consulta checkpoint e resultado de um lote
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Estado atual do lote
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BatchStatus" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404":
          description: Lote não encontrado
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorEnvelope" }
components:
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: Chave exclusiva entregue fora da documentação. A produção armazena apenas o hash.
  responses:
    InvalidInput:
      description: Entrada inválida; a fonte não foi consultada
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorEnvelope" }
    Unauthorized:
      description: API key ausente ou inválida
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorEnvelope" }
    ConsumptionRequired:
      description: Confirmação explícita de consumo ausente
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorEnvelope" }
    SourceContractChanged:
      description: A resposta da fonte mudou e o parser falhou fechado
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorEnvelope" }
    SourceUnavailable:
      description: Sessão expirada, bloqueio, mudança de schema ou fonte indisponível
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorEnvelope" }
  schemas:
    Health:
      type: object
      required: [status, service, version]
      properties:
        status: { type: string, const: ok }
        service: { type: string }
        version: { type: string }
    NominalRequest:
      type: object
      additionalProperties: false
      required: [nome]
      properties:
        nome:
          type: string
          minLength: 5
          maxLength: 180
          description: Nome completo com pelo menos duas palavras
    Candidate:
      type: object
      required: [nome, cpf]
      properties:
        nome: { type: string }
        cpf: { type: string, pattern: "^[0-9]{11}$" }
        cpfMascarado: { type: string }
        tipoPessoa: { type: string }
        idade: { type: [integer, "null"], minimum: 0 }
        cidade: { type: string }
        uf: { type: string }
    CounterSnapshot:
      type: [object, "null"]
      properties:
        usado: { type: integer, minimum: 0 }
        limite: { type: integer, minimum: 0 }
    ObservedCounter:
      type: object
      required: [antes, depois, variacaoObservada]
      properties:
        antes: { $ref: "#/components/schemas/CounterSnapshot" }
        depois: { $ref: "#/components/schemas/CounterSnapshot" }
        variacaoObservada: { type: [integer, "null"] }
    NominalResponse:
      type: object
      required: [status, total, candidatos, contador]
      properties:
        status: { enum: [nao_encontrado, sucesso_unico, multiplos_candidatos] }
        total: { type: integer, minimum: 0 }
        candidatos:
          type: array
          items: { $ref: "#/components/schemas/Candidate" }
        duracaoMs: { type: number, minimum: 0 }
        contador: { $ref: "#/components/schemas/ObservedCounter" }
    FullRequest:
      type: object
      additionalProperties: false
      required: [cpf, confirmarConsumo]
      properties:
        cpf: { type: string, pattern: "^[0-9.\\/-]{11,14}$" }
        confirmarConsumo: { type: boolean, const: true }
    Phone:
      type: object
      properties:
        ddd: { type: string }
        numero: { type: string }
        telefone: { type: string }
        tipo: { type: [string, "null"] }
        operadora: { type: [string, "null"] }
        mobilidadeCadastral: { type: [string, "null"] }
    Address:
      type: object
      additionalProperties: true
    FullResponse:
      type: object
      required: [status, cpf, contador]
      properties:
        status: { type: string }
        cpf: { type: string, pattern: "^[0-9]{11}$" }
        cpfMascarado: { type: string }
        cadastro: { type: object, additionalProperties: { type: string } }
        telefones: { type: array, items: { $ref: "#/components/schemas/Phone" } }
        emails: { type: array, items: { type: string } }
        enderecos: { type: array, items: { $ref: "#/components/schemas/Address" } }
        cache: { type: boolean }
        contador: { $ref: "#/components/schemas/ObservedCounter" }
    BatchAccepted:
      type: object
      required: [id, status, total, consultaPaga]
      properties:
        id: { type: string, format: uuid }
        status: { enum: [pendente, executando] }
        total: { type: integer, minimum: 1 }
        consultaPaga: { type: boolean }
    BatchStatus:
      type: object
      required: [id, status, total]
      properties:
        id: { type: string, format: uuid }
        status: { enum: [pendente, executando, interrompido, concluido] }
        total: { type: integer, minimum: 1 }
        completed: { type: integer, minimum: 0 }
        results: { type: array, items: { type: object } }
        contadorAntes: { $ref: "#/components/schemas/CounterSnapshot" }
        contadorDepois: { $ref: "#/components/schemas/CounterSnapshot" }
    ErrorEnvelope:
      type: object
      required: [erro]
      properties:
        erro:
          type: object
          required: [code, message]
          properties:
            code: { type: string }
            message: { type: string }
            details: { type: [object, "null"] }
