{
  "openapi": "3.0.3",
  "info": {
    "title": "Orbit Clay — API pública do tiro ao prato",
    "version": "1.0.0",
    "description": "API REST das provas de **tiro ao prato** (COMPAK, Skeet, Trap): provas, passadas, squads, inscrições, juízes, ranking e a fita prato a prato de cada atleta.\n\nTudo é escopado à entidade da chave e endereçado por **uuid** — nenhuma rota aceita ou devolve id interno do banco.\n\n## Autenticação\nÉ a **mesma chave** da API do IPSC. Crie em **Entidade → Chaves de API** (somente owner) e envie em cada request:\n\n```\nAuthorization: Bearer <access_key>.<secret>\n```\n\nA secret aparece **uma única vez** na criação/regeneração. Escopo `leitura` permite apenas GET; `leitura_escrita` libera escrita.\n\n## Módulo Clay habilitado\nTodas as rotas exigem o módulo ligado para a entidade. Sem isso a resposta é **404** (e não 403): a chave é válida, o recurso é que não existe para essa entidade. Peça ao clube que habilite o Clay.\n\n## Score não se escreve por aqui\nA fita nasce assinada no campo — HMAC do juiz encadeado com o PIN do atleta. Um POST de score criaria resultado sem essa cadeia, que é justamente o que o módulo existe para impedir. A API **lê** o resultado; quem o produz é o juiz na tela de lançamento.\n\n## Rate limit\nPor chave (padrão 120 req/min), com headers `X-RateLimit-Limit`, `X-RateLimit-Remaining` e `X-RateLimit-Reset`; excedido devolve **429** com `Retry-After`.\n\n## Erros\nSempre JSON: `{\"error\": {\"code\": \"...\", \"message\": \"...\"}}`. Códigos: `unauthorized`, `forbidden`, `not_found`, `conflict`, `confirmation_required`, `validation_error`, `invalid_json`, `rate_limited`."
  },
  "servers": [
    {
      "url": "/api/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Provas",
      "description": "A prova de prato: modalidade, datas, publicação e a regra de assinatura."
    },
    {
      "name": "Passadas",
      "description": "A unidade de disputa — um giro do squad pelos postos. A grade (postos × pratos) define o total da prova."
    },
    {
      "name": "Squads",
      "description": "Grupos de atiradores com horário e limite de vagas."
    },
    {
      "name": "Inscrições",
      "description": "Atletas na prova, ligados ao mesmo cadastro da entidade usado pelo IPSC."
    },
    {
      "name": "Anotadores",
      "description": "Juízes de campo do módulo (cadastro próprio, separado do RO do IPSC)."
    },
    {
      "name": "Resultados",
      "description": "Ranking, fita prato a prato e os links públicos da prova. Somente leitura."
    },
    {
      "name": "Campeonatos",
      "description": "Temporada: etapas, soma com descartes e ranking agregado."
    },
    {
      "name": "Catálogo",
      "description": "Categorias e as classes de cada uma, com as regras de enquadramento (idade, sexo e faixa)."
    },
    {
      "name": "Shoot-off",
      "description": "Desempate de campo do empate que sobreviveu ao count-back. Não soma ao placar: resolve só a posição."
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "access_key.secret"
      }
    },
    "schemas": {
      "Erro": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "ProvaResumo": {
        "type": "object",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          },
          "modalidade": {
            "type": "string",
            "enum": [
              "compak",
              "skeet",
              "trap",
              "percurso_caca"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "rascunho",
              "aberta",
              "andamento",
              "encerrada"
            ]
          },
          "ativo": {
            "type": "boolean"
          },
          "data_prova": {
            "type": "string",
            "format": "date"
          },
          "data_fim": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "local": {
            "type": "string",
            "nullable": true
          },
          "pratos_total": {
            "type": "integer",
            "description": "Soma dos pratos das passadas ativas (derivado)."
          },
          "codigo_publico": {
            "type": "string",
            "nullable": true
          },
          "external_id": {
            "type": "string",
            "maxLength": 64,
            "nullable": true,
            "description": "Id desta prova no **sistema do integrador** (opcional, exclusivo da API — nenhuma tela do Orbit mostra ou edita). Único por entidade, então funciona como chave de idempotência: reenviar o mesmo POST devolve 409 em vez de duplicar. Envie `null` para desvincular. Use `?external_id=` na listagem para reencontrar o registro sem guardar o nosso uuid."
          }
        }
      },
      "Prova": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProvaResumo"
          },
          {
            "type": "object",
            "properties": {
              "descricao": {
                "type": "string",
                "nullable": true
              },
              "pratos_por_passada": {
                "type": "integer"
              },
              "assinatura_modo": {
                "type": "string",
                "enum": [
                  "final",
                  "por_posto"
                ],
                "description": "`final`: o atleta assina uma vez, ao fim da passada. `por_posto`: aceite posto a posto além da assinatura final. **Congela quando a prova tem score.**"
              },
              "exige_assinatura_atleta": {
                "type": "boolean",
                "description": "Desligado, o score vale com a assinatura do juiz — a cadeia segue íntegra, com um elo só. **Congela quando a prova tem score.**"
              },
              "pin_pelo_portal": {
                "type": "boolean",
                "description": "Permite ao atleta criar ou trocar o PIN pelo portal. Desligada por padrão; o PIN do campo não depende dela."
              },
              "inscricao_publica": {
                "type": "boolean"
              },
              "inscricao_limite_em": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "resultados_publicos": {
                "type": "boolean"
              },
              "hub_publico": {
                "type": "boolean"
              },
              "criada_em": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "atualizada_em": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "campeonato_uuid": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "usa_categorias": {
                "type": "boolean",
                "nullable": true,
                "description": "null = herda do campeonato; true/false é decisão desta etapa."
              },
              "usa_classes": {
                "type": "boolean",
                "nullable": true,
                "description": "null = herda do campeonato."
              },
              "idade_modo": {
                "type": "string",
                "nullable": true,
                "description": "null = herda do campeonato."
              },
              "classe_criterio": {
                "type": "string",
                "nullable": true,
                "description": "null = herda do campeonato."
              },
              "countback_modo": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "prato",
                  "serie",
                  null
                ],
                "description": "Decisao DESTA etapa; `null` herda do campeonato."
              },
              "shootoff_ativo": {
                "type": "boolean",
                "nullable": true,
                "description": "null = herda do campeonato."
              },
              "shootoff_ate_posicao": {
                "type": "integer",
                "nullable": true,
                "description": "null = herda do campeonato."
              },
              "shootoff_ativo_etapa": {
                "type": "boolean",
                "nullable": true,
                "description": "Decisao DESTA etapa; `null` herda do campeonato."
              },
              "shootoff_ate_posicao_etapa": {
                "type": "integer",
                "nullable": true,
                "description": "Decisao DESTA etapa; `null` herda do campeonato."
              },
              "shootoff_eliminacao_etapa": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "mata_mata",
                  "serie",
                  null
                ],
                "description": "Decisao DESTA etapa; `null` herda do campeonato."
              },
              "desempate": {
                "type": "object",
                "description": "Regra de desempate JA RESOLVIDA (heranca do campeonato aplicada) — para o integrador nao reimplementar a heranca.",
                "properties": {
                  "countback_modo": {
                    "type": "string",
                    "enum": [
                      "prato",
                      "serie"
                    ]
                  },
                  "shootoff_ativo": {
                    "type": "boolean"
                  },
                  "shootoff_ate_posicao": {
                    "type": "integer"
                  },
                  "shootoff_eliminacao": {
                    "type": "string",
                    "enum": [
                      "mata_mata",
                      "serie"
                    ],
                    "description": "Como a rodada termina para quem fica atras. `mata_mata` (padrao): no lancamento em que o atleta fica atras dos outros, os lancamentos seguintes DELE nao acontecem (FITASC cap. 13, \"FIRST ZERO ELIMINATORY\"). `serie`: todos atiram a serie inteira e compara-se o total da rodada (§3.8.3 do regulamento do clube)."
                  }
                }
              }
            }
          }
        ]
      },
      "Posto": {
        "type": "object",
        "properties": {
          "posto": {
            "type": "integer",
            "description": "Número do posto de tiro (1..N)."
          },
          "pratos": {
            "type": "integer",
            "description": "Quantos pratos são lançados nesse posto."
          },
          "duplos": {
            "type": "integer",
            "description": "Quantos LANÇAMENTOS do posto são duplos; cada um consome dois pratos, e os avulsos vêm primeiro. Em 5 pratos com 1 duplo, os pratos 4 e 5 são o duplo."
          },
          "rotulo": {
            "type": "string",
            "nullable": true,
            "maxLength": 10,
            "description": "Nome exibido do posto quando o número do bloco não coincide com a pedana real. No preset do Skeet a rodada de 25 tem 9 blocos na ordem de tiro: postos 1-7, a volta à pedana 4 (bloco 8, rotulo \"4V\") e a pedana 8 (bloco 9, rotulo \"8\"). Ausente/null = a tela mostra o próprio número."
          }
        }
      },
      "Passada": {
        "type": "object",
        "description": "A passada é a unidade de disputa: um giro completo do squad pelos postos. Onde o IPSC tem stage, o tiro ao prato tem passada.",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "numero": {
            "type": "integer"
          },
          "nome": {
            "type": "string"
          },
          "qtd_pratos": {
            "type": "integer",
            "description": "Soma dos pratos dos postos (derivado)."
          },
          "postos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Posto"
            }
          },
          "horario_inicio": {
            "type": "string",
            "nullable": true,
            "example": "09:30",
            "description": "HH:MM (24h) ou null."
          },
          "ativa": {
            "type": "boolean",
            "description": "Passada inativa não conta no total nem no ranking."
          },
          "scores_lancados": {
            "type": "integer",
            "description": "Scores vivos na passada — >0 congela a configuração."
          },
          "tipo_duplo": {
            "type": "string",
            "nullable": true,
            "enum": [
              "simultaneo",
              "relatorio",
              null
            ],
            "description": "Tipo de duplo da passada (FITASC 2.5). Vale para todos os postos: \"on a Compak, the type of double shall be the same\" (4.1)."
          }
        }
      },
      "Squad": {
        "type": "object",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          },
          "numero": {
            "type": "integer",
            "nullable": true
          },
          "limite": {
            "type": "integer",
            "description": "Vagas. Padrão 5: um atirador por posto."
          },
          "horario_inicio": {
            "type": "string",
            "nullable": true,
            "example": "09:30",
            "description": "HH:MM (24h) ou null."
          },
          "observacao": {
            "type": "string",
            "nullable": true
          },
          "ativo": {
            "type": "boolean"
          },
          "inscritos": {
            "type": "integer"
          }
        }
      },
      "InscricaoResumo": {
        "type": "object",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid",
            "description": "Identifica a inscrição NESTA prova."
          },
          "perfil_uuid": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Identidade do atleta entre provas e módulos (IPSC incluído)."
          },
          "membro_entidade_uuid": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Cadastro do atleta na entidade."
          },
          "nome": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "classe": {
            "type": "string",
            "nullable": true
          },
          "categoria": {
            "type": "string",
            "nullable": true
          },
          "squad_uuid": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "ordem_tiro": {
            "type": "integer",
            "nullable": true
          },
          "status_inscricao": {
            "type": "string",
            "enum": [
              "pendente",
              "confirmada",
              "cancelada"
            ]
          },
          "status_participacao": {
            "type": "string",
            "enum": [
              "ativo",
              "dnf",
              "dq"
            ]
          },
          "external_id": {
            "type": "string",
            "maxLength": 64,
            "nullable": true,
            "description": "Id desta inscrição no **sistema do integrador** (opcional, exclusivo da API). Único por prova — chave de idempotência."
          },
          "categoria_uuid": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Catálogo vivo. `categoria` é o snapshot do nome no dia."
          },
          "classe_uuid": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        }
      },
      "Equipamento": {
        "type": "object",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "tipo_equipamento": {
            "type": "string",
            "nullable": true
          },
          "marca": {
            "type": "string",
            "nullable": true
          },
          "modelo": {
            "type": "string",
            "nullable": true
          },
          "calibre": {
            "type": "string",
            "nullable": true
          },
          "numero_serie": {
            "type": "string",
            "nullable": true
          },
          "rotulo": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Inscricao": {
        "allOf": [
          {
            "$ref": "#/components/schemas/InscricaoResumo"
          },
          {
            "type": "object",
            "properties": {
              "cpf": {
                "type": "string",
                "nullable": true
              },
              "telefone": {
                "type": "string",
                "nullable": true
              },
              "status_pgto": {
                "type": "string",
                "enum": [
                  "PENDENTE",
                  "PAGO",
                  "ISENTO"
                ]
              },
              "obs_pgto": {
                "type": "string",
                "nullable": true
              },
              "presenca": {
                "type": "string"
              },
              "campos_entidade": {
                "type": "object",
                "additionalProperties": true,
                "description": "Campos adicionais configurados pela entidade."
              },
              "equipamentos": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Equipamento"
                }
              },
              "criada_em": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "atualizada_em": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "optou_alternativa": {
                "type": "boolean",
                "description": "O atleta abriu mão da categoria de direito para atirar na alternativa (Sênior)."
              },
              "enquadramento_origem": {
                "type": "string",
                "nullable": true,
                "description": "auto | manual. manual = decidido por humano; reprocessar por regra não sobrescreve."
              },
              "enquadramento_indice": {
                "type": "number",
                "nullable": true,
                "description": "Índice usado na sugestão de classe, congelado."
              }
            }
          }
        ]
      },
      "Anotador": {
        "type": "object",
        "description": "Juiz de campo do módulo (cadastro próprio, não é o RO do IPSC).",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          },
          "username": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "telefone": {
            "type": "string",
            "nullable": true
          },
          "ativo": {
            "type": "boolean"
          },
          "gestor_implicito": {
            "type": "boolean",
            "description": "Linha técnica que identifica a assinatura do gestor que lança do painel. Não aparece na lista de juízes."
          }
        }
      },
      "Prato": {
        "type": "object",
        "properties": {
          "posto": {
            "type": "integer"
          },
          "seq": {
            "type": "integer",
            "description": "Ordem do prato dentro do posto."
          },
          "hit": {
            "type": "boolean",
            "description": "true = prato quebrado (X); false = erro (O)."
          },
          "par": {
            "type": "integer",
            "nullable": true,
            "description": "Número do duplo dentro do posto; null é prato avulso. Derivado da configuração da passada, não aceito do cliente."
          }
        }
      },
      "Advertencia": {
        "type": "object",
        "description": "Ocorrência disciplinar registrada na passada. **Não altera o placar.**",
        "properties": {
          "posto": {
            "type": "integer"
          },
          "tipo": {
            "type": "string"
          },
          "tipo_label": {
            "type": "string"
          },
          "motivo": {
            "type": "string"
          },
          "criado_em": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Assinatura": {
        "type": "object",
        "properties": {
          "assinado": {
            "type": "boolean"
          },
          "em": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "Score": {
        "type": "object",
        "description": "Resultado do atleta numa passada. Somente leitura: a fita nasce assinada no campo (HMAC do juiz encadeado com o PIN do atleta) e a API não tem como reproduzir essa cadeia.",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "passada_uuid": {
            "type": "string",
            "format": "uuid"
          },
          "inscricao_uuid": {
            "type": "string",
            "format": "uuid"
          },
          "anotador": {
            "type": "object",
            "nullable": true,
            "properties": {
              "uuid": {
                "type": "string",
                "format": "uuid"
              },
              "nome": {
                "type": "string"
              }
            }
          },
          "acertos": {
            "type": "integer",
            "description": "Somado no servidor a partir da fita — nunca informado pelo cliente."
          },
          "pratos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Prato"
            }
          },
          "aprovado": {
            "type": "boolean"
          },
          "aprovado_sem_pin": {
            "type": "boolean",
            "description": "Exceção registrada: o staff aprovou sem o PIN do atleta."
          },
          "bloqueado": {
            "type": "boolean"
          },
          "is_dnf": {
            "type": "boolean"
          },
          "versao": {
            "type": "integer",
            "description": "Sobe a cada reabertura do score."
          },
          "advertencias": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Advertencia"
            }
          },
          "assinaturas": {
            "type": "object",
            "description": "Estado das duas assinaturas. O hash não circula na API: ele é o que prova a integridade da cadeia.",
            "properties": {
              "anotador": {
                "$ref": "#/components/schemas/Assinatura"
              },
              "atleta": {
                "$ref": "#/components/schemas/Assinatura"
              }
            }
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "RankingLinha": {
        "type": "object",
        "properties": {
          "posicao": {
            "type": "integer",
            "description": "Empate real divide a mesma posição (shoot-off de campo, depois do count-back prato a prato)."
          },
          "total": {
            "type": "integer",
            "description": "Soma dos acertos nas passadas ativas."
          },
          "passadas_lancadas": {
            "type": "integer"
          },
          "advertencias": {
            "type": "integer"
          },
          "dnf": {
            "type": "boolean"
          },
          "atleta": {
            "$ref": "#/components/schemas/InscricaoResumo"
          },
          "por_passada": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "passada_uuid": {
                  "type": "string",
                  "format": "uuid"
                },
                "numero": {
                  "type": "integer"
                },
                "nome": {
                  "type": "string"
                },
                "acertos": {
                  "type": "integer",
                  "nullable": true,
                  "description": "null = passada ainda não lançada para este atleta."
                }
              }
            }
          }
        }
      },
      "UrlPublica": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "nullable": true
          },
          "ativa": {
            "type": "boolean"
          },
          "motivo": {
            "type": "string",
            "nullable": true,
            "description": "Por que o link está fechado."
          }
        }
      },
      "Campeonato": {
        "type": "object",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          },
          "ano": {
            "type": "integer"
          },
          "modalidade": {
            "type": "string",
            "nullable": true,
            "description": "compak | skeet | trap | percurso_caca. null = campeonato multimodalidade."
          },
          "ativo": {
            "type": "boolean"
          },
          "publico": {
            "type": "boolean"
          },
          "codigo_publico": {
            "type": "string",
            "nullable": true,
            "description": "Token do ranking público (/clay/c/<codigo>)."
          },
          "etapas_previstas": {
            "type": "integer"
          },
          "minimo_etapas": {
            "type": "integer",
            "description": "Abaixo disso o atleta aparece no ranking, mas fora da classificação."
          },
          "descartes": {
            "type": "integer",
            "description": "As N piores etapas saem da soma (nunca a ponto de zerar quem tem uma só)."
          },
          "usa_categorias": {
            "type": "boolean"
          },
          "usa_classes": {
            "type": "boolean"
          },
          "idade_modo": {
            "type": "string",
            "enum": [
              "dia_exato",
              "fim_ano"
            ],
            "description": "dia_exato: idade completa na data da prova (\"no dia que completar 55 anos\"). fim_ano: ano da prova menos ano de nascimento, vale o ano todo."
          },
          "classe_criterio": {
            "type": "string",
            "enum": [
              "percentual",
              "pratos"
            ],
            "description": "Unidade das faixas de classe."
          },
          "trava_primeira_participacao": {
            "type": "boolean",
            "description": "Congela a categoria/classe do atleta na primeira etapa que ele atira."
          },
          "countback_modo": {
            "type": "string",
            "enum": [
              "prato",
              "serie"
            ],
            "description": "prato: fita inteira de trás para frente (§3.8.2). serie: total da última passada primeiro e só depois prato a prato (ISSF 9.14.4.2)."
          },
          "shootoff_ativo": {
            "type": "boolean",
            "description": "Desligado, o empate segue dividindo posição."
          },
          "shootoff_ate_posicao": {
            "type": "integer",
            "description": "Até que posição vale desempate (3 = pódio)."
          },
          "shootoff_eliminacao": {
            "type": "string",
            "enum": [
              "mata_mata",
              "serie"
            ],
            "description": "Como a rodada termina para quem fica atras. `mata_mata` (padrao): no lancamento em que o atleta fica atras dos outros, os lancamentos seguintes DELE nao acontecem (FITASC cap. 13, \"FIRST ZERO ELIMINATORY\"). `serie`: todos atiram a serie inteira e compara-se o total da rodada (§3.8.3 do regulamento do clube)."
          }
        }
      },
      "Classe": {
        "type": "object",
        "description": "Classe DENTRO de uma categoria. A faixa é [valor_min, valor_max): mínimo inclusive, máximo exclusivo.",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "codigo": {
            "type": "string"
          },
          "nome": {
            "type": "string"
          },
          "ordem": {
            "type": "integer"
          },
          "ativo": {
            "type": "boolean"
          },
          "criterio": {
            "type": "string",
            "enum": [
              "faixa",
              "manual"
            ]
          },
          "valor_min": {
            "type": "number",
            "nullable": true,
            "description": "Inclusive. null = faixa aberta embaixo."
          },
          "valor_max": {
            "type": "number",
            "nullable": true,
            "description": "EXCLUSIVO. null = faixa aberta em cima."
          },
          "sem_historico": {
            "type": "boolean",
            "description": "Classe de quem ainda não tem ranking (Iniciante)."
          },
          "faixa": {
            "type": "string",
            "nullable": true,
            "description": "A faixa como o regulamento escreve: \"26–35\" em pratos, \"≥87% e <94%\" em percentual."
          },
          "descricao": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Categoria": {
        "type": "object",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "codigo": {
            "type": "string"
          },
          "nome": {
            "type": "string",
            "description": "Como está gravado."
          },
          "nome_exibicao": {
            "type": "string",
            "description": "Nome do catálogo padrão traduzido para o idioma da requisição; categoria criada pelo clube devolve o texto dele."
          },
          "ordem": {
            "type": "integer"
          },
          "ativo": {
            "type": "boolean"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "automatica",
              "manual"
            ],
            "description": "automatica aplica as regras de idade/sexo; manual só por escolha humana."
          },
          "sexo": {
            "type": "string",
            "nullable": true,
            "description": "M, F ou null. Regra de sexo vence regra de idade — Damas independe da idade."
          },
          "idade_min": {
            "type": "integer",
            "nullable": true,
            "description": "Inclusive."
          },
          "idade_max": {
            "type": "integer",
            "nullable": true,
            "description": "Inclusive."
          },
          "optar_categoria_uuid": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Categoria alternativa que o atleta pode escolher (Veterano/Júnior/Dama → Sênior)."
          },
          "descricao": {
            "type": "string",
            "nullable": true
          },
          "classes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Classe"
            }
          }
        }
      },
      "Catalogo": {
        "type": "object",
        "properties": {
          "classe_criterio": {
            "type": "string",
            "enum": [
              "percentual",
              "pratos"
            ]
          },
          "categorias": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Categoria"
            }
          },
          "escopo": {
            "type": "string",
            "nullable": true,
            "description": "Só na rota da prova: prova | campeonato | clube, dizendo de onde veio o catálogo. null = sem catálogo, classe e categoria seguem texto livre."
          },
          "usa_categorias": {
            "type": "boolean",
            "description": "Só na rota da prova (herança resolvida)."
          },
          "usa_classes": {
            "type": "boolean",
            "description": "Só na rota da prova."
          },
          "idade_modo": {
            "type": "string",
            "description": "Só na rota da prova."
          },
          "campeonato_uuid": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        }
      },
      "RankingCampeonatoLinha": {
        "type": "object",
        "properties": {
          "posicao": {
            "type": "integer"
          },
          "nome": {
            "type": "string"
          },
          "perfil_uuid": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "null para visitante sem conta — ele entra no ranking pelo nome normalizado."
          },
          "categoria": {
            "type": "string",
            "nullable": true
          },
          "classe": {
            "type": "string",
            "nullable": true
          },
          "total": {
            "type": "integer",
            "description": "Soma DEPOIS dos descartes."
          },
          "etapas_disputadas": {
            "type": "integer"
          },
          "classificado": {
            "type": "boolean",
            "description": "false = abaixo de minimo_etapas."
          },
          "etapas": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "prova_uuid": {
                  "type": "string",
                  "format": "uuid"
                },
                "total": {
                  "type": "integer",
                  "nullable": true
                },
                "descartada": {
                  "type": "boolean"
                }
              }
            }
          }
        }
      },
      "ShootoffAtleta": {
        "type": "object",
        "properties": {
          "inscricao_uuid": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          },
          "ordem": {
            "type": "integer",
            "description": "Ordem de tiro no desempate."
          },
          "presente": {
            "type": "boolean",
            "description": "false = ausente; perde por WO (§3.8.4)."
          },
          "acertos": {
            "type": "integer",
            "description": "Pratos do DESEMPATE. Não entra no total da prova."
          },
          "colocacao": {
            "type": "integer",
            "nullable": true,
            "description": "1 = venceu. É o que desempata a posição no ranking."
          },
          "pratos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "rodada": {
                  "type": "integer",
                  "description": "1 = série de duplos; 2+ = morte súbita."
                },
                "par": {
                  "type": "integer",
                  "description": "Número do duplo na rodada."
                },
                "seq": {
                  "type": "integer",
                  "enum": [
                    1,
                    2
                  ],
                  "description": "Primeiro ou segundo prato do duplo."
                },
                "hit": {
                  "type": "boolean"
                }
              }
            }
          },
          "confirmado": {
            "type": "boolean",
            "description": "Atleta aceitou a fita (PIN) ou tem excecao registrada. A conclusao exige isso quando a prova pede assinatura do atleta. Ausente (WO) conta como confirmado."
          },
          "confirmado_em": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "confirmado_sem_pin": {
            "type": "boolean",
            "description": "Excecao do staff: fechado sem o PIN deste atleta, com motivo no historico do desempate."
          },
          "eliminado_em": {
            "type": "integer",
            "nullable": true,
            "description": "Rodada em que saiu da disputa (mata-mata); null = segue nela. 1 = caiu na serie."
          }
        }
      },
      "Shootoff": {
        "type": "object",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "prova_uuid": {
            "type": "string",
            "format": "uuid"
          },
          "posicao": {
            "type": "integer",
            "description": "Posição em disputa."
          },
          "agrupamento": {
            "type": "string",
            "enum": [
              "geral",
              "classe",
              "categoria"
            ],
            "description": "Onde o empate aconteceu."
          },
          "grupo": {
            "type": "string",
            "nullable": true,
            "description": "Nome da classe/categoria quando o empate é de dentro dela."
          },
          "duplos": {
            "type": "integer",
            "description": "Duplos da primeira rodada (5 no regulamento)."
          },
          "tipo_duplo": {
            "type": "string",
            "enum": [
              "simultaneo",
              "relatorio"
            ]
          },
          "posto": {
            "type": "string",
            "nullable": true,
            "description": "Posto/trajetórias definidos pela organização."
          },
          "status": {
            "type": "string",
            "enum": [
              "aberto",
              "concluido"
            ],
            "description": "Só o concluído desempata o ranking."
          },
          "desfecho": {
            "type": "string",
            "nullable": true,
            "enum": [
              "tiro",
              "wo",
              "idade",
              null
            ],
            "description": "Como terminou: no tiro, por ausência (WO) ou pelo mais velho (§3.8.4)."
          },
          "observacao": {
            "type": "string",
            "nullable": true
          },
          "overlay_url": {
            "type": "string",
            "nullable": true,
            "description": "Caminho do overlay de transmissão, quando gerado."
          },
          "atletas": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ShootoffAtleta"
            }
          },
          "formato": {
            "type": "string",
            "enum": [
              "duplos",
              "avulsos"
            ]
          },
          "avulsos": {
            "type": "integer",
            "description": "Pratos avulsos por rodada, no formato avulso."
          },
          "tipo": {
            "type": "string",
            "enum": [
              "individual",
              "equipe"
            ]
          },
          "eliminacao": {
            "type": "string",
            "enum": [
              "mata_mata",
              "serie"
            ],
            "description": "Como a rodada termina para quem fica atras. `mata_mata` (padrao): no lancamento em que o atleta fica atras dos outros, os lancamentos seguintes DELE nao acontecem (FITASC cap. 13, \"FIRST ZERO ELIMINATORY\"). `serie`: todos atiram a serie inteira e compara-se o total da rodada (§3.8.3 do regulamento do clube). Copiada da configuracao da prova ao abrir o desempate: mudar a config depois nao reescreve como um desempate ja atirado foi julgado."
          },
          "ro_uuid": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Juiz designado para lancar o desempate."
          },
          "ro_nome": {
            "type": "string",
            "nullable": true
          },
          "assinatura_ro": {
            "type": "boolean",
            "description": "true quando o juiz assinou o fechamento (HMAC)."
          },
          "assinado_em": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "proxima_rodada": {
            "type": "integer",
            "nullable": true,
            "description": "Rodada de morte subita a abrir agora (mata-mata em curso), ou null quando a rodada corrente nao fechou ou o desempate ja tem vencedor."
          }
        }
      }
    }
  },
  "paths": {
    "/clay/provas": {
      "get": {
        "tags": [
          "Provas"
        ],
        "summary": "Listar provas de tiro ao prato",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "rascunho",
                "aberta",
                "andamento",
                "encerrada"
              ]
            }
          },
          {
            "name": "modalidade",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "compak",
                "skeet",
                "trap",
                "percurso_caca"
              ]
            }
          },
          {
            "name": "external_id",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 64
            },
            "description": "Filtra pela sua própria referência (id no sistema do integrador)."
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 25,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista paginada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "provas": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProvaResumo"
                      }
                    },
                    "page": {
                      "type": "integer"
                    },
                    "per_page": {
                      "type": "integer"
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                },
                "example": {
                  "provas": [
                    {
                      "uuid": "9f1c2b7e-51ab-4a0d-9d3f-2a1b7c4e5d60",
                      "nome": "Etapa COMPAK de Outubro",
                      "modalidade": "compak",
                      "status": "aberta",
                      "ativo": true,
                      "data_prova": "2026-10-10",
                      "data_fim": null,
                      "local": "Stand 3",
                      "pratos_total": 50,
                      "codigo_publico": "CLAY2610"
                    }
                  ],
                  "page": 1,
                  "per_page": 25,
                  "total": 1
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Provas"
        ],
        "summary": "Criar prova",
        "description": "A prova nasce em **rascunho**, já com a Passada 1 no preset da modalidade (Trap e Compak: 5 postos × 5 pratos; Skeet: a sequência oficial da rodada de 25 — 9 blocos na ordem de tiro, com a volta ao posto 4 e o posto 8 por último; Percurso de Caça / FITASC Sporting: preset de partida 5 postos × 5 pratos com 1 duplo por posto — sem sequência oficial fixa, o editor de grade reconfigura para o percurso real do clube), o Squad 1 no tamanho da modalidade (Compak: 5, um atirador por posto; Trap, Skeet e Percurso de Caça: 6), código público gerado e os gestores da entidade vinculados como anotadores implícitos — pronta para lançar assim que sair do rascunho.\n\nDesempate da modalidade: prova sem campeonato nasce com `countback_modo` da regra (Trap e Percurso de Caça: `serie`, o count-back por linha — última primeiro, só desce a prato a prato se tudo empatar; Skeet e Compak: `prato`, a fita lida prato a prato de trás para frente) e `shootoff_ativo=true` — apenas nos campos que o corpo da requisição não enviou. Etapa de campeonato não recebe padrão nenhum: lá `null` significa \"herda do campeonato\".\n\nRascunho não publica: ligar `inscricao_publica`, `resultados_publicos` ou `hub_publico` antes de mudar o status devolve **409**.\n\nDesempate: `countback_modo`, `shootoff_ativo`, `shootoff_ate_posicao` e `shootoff_eliminacao` aceitam `null` = \"herda do campeonato\". Campo ausente do corpo não é alterado.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "nome",
                  "modalidade",
                  "data_prova"
                ],
                "properties": {
                  "nome": {
                    "type": "string"
                  },
                  "modalidade": {
                    "type": "string",
                    "enum": [
                      "compak",
                      "skeet",
                      "trap",
                      "percurso_caca"
                    ]
                  },
                  "data_prova": {
                    "type": "string",
                    "format": "date"
                  },
                  "data_fim": {
                    "type": "string",
                    "format": "date"
                  },
                  "local": {
                    "type": "string"
                  },
                  "descricao": {
                    "type": "string"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "rascunho",
                      "aberta",
                      "andamento",
                      "encerrada"
                    ]
                  },
                  "assinatura_modo": {
                    "type": "string",
                    "enum": [
                      "final",
                      "por_posto"
                    ]
                  },
                  "exige_assinatura_atleta": {
                    "type": "boolean"
                  },
                  "pin_pelo_portal": {
                    "type": "boolean",
                    "description": "Permite ao atleta criar ou trocar o PIN pelo portal. Desligada por padrão; o PIN do campo não depende dela."
                  },
                  "ativo": {
                    "type": "boolean"
                  },
                  "inscricao_publica": {
                    "type": "boolean"
                  },
                  "inscricao_limite_em": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true
                  },
                  "resultados_publicos": {
                    "type": "boolean"
                  },
                  "hub_publico": {
                    "type": "boolean"
                  },
                  "external_id": {
                    "type": "string",
                    "maxLength": 64,
                    "nullable": true,
                    "description": "Id desta prova no **sistema do integrador** (opcional, exclusivo da API — nenhuma tela do Orbit mostra ou edita). Único por entidade, então funciona como chave de idempotência: reenviar o mesmo POST devolve 409 em vez de duplicar. Envie `null` para desvincular. Use `?external_id=` na listagem para reencontrar o registro sem guardar o nosso uuid."
                  }
                }
              },
              "example": {
                "nome": "Etapa COMPAK de Outubro",
                "modalidade": "compak",
                "data_prova": "2026-10-10",
                "local": "Stand 3"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Prova criada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "prova": {
                      "$ref": "#/components/schemas/Prova"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Chave com escopo somente leitura.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Rascunho com publicação ligada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "Campo obrigatório ausente ou inválido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/provas/{prova_uuid}": {
      "get": {
        "tags": [
          "Provas"
        ],
        "summary": "Detalhe da prova",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          }
        ],
        "responses": {
          "200": {
            "description": "Prova",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "prova": {
                      "$ref": "#/components/schemas/Prova"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Provas"
        ],
        "summary": "Atualizar prova",
        "description": "Só os campos enviados mudam. `assinatura_modo` e `exige_assinatura_atleta` **congelam** assim que a prova tem score: mudar a regra depois de assinar mudaria o significado do que já foi assinado (409).\n\nDesempate: `countback_modo`, `shootoff_ativo`, `shootoff_ate_posicao` e `shootoff_eliminacao` aceitam `null` = \"herda do campeonato\". Campo ausente do corpo nao e alterado.",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "status": "andamento",
                "resultados_publicos": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Prova atualizada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "prova": {
                      "$ref": "#/components/schemas/Prova"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Chave com escopo somente leitura.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Configuração de assinatura congelada ou rascunho publicando.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "Valor inválido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Provas"
        ],
        "summary": "Excluir prova (soft delete em cascata)",
        "description": "Zona de perigo: exige `?confirmacao=<nome exato da prova>`. Passadas, squads, inscrições, scores e vínculos de juiz vão junto, e os códigos públicos são liberados — o link que circulou para de responder.",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          },
          {
            "name": "confirmacao",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Nome exato da prova."
          }
        ],
        "responses": {
          "200": {
            "description": "Prova excluída",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "excluida": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Chave com escopo somente leitura.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Confirmação ausente ou diferente do nome da prova.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/provas/{prova_uuid}/passadas": {
      "get": {
        "tags": [
          "Passadas"
        ],
        "summary": "Listar passadas",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          }
        ],
        "responses": {
          "200": {
            "description": "Passadas da prova",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "passadas": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Passada"
                      }
                    },
                    "pratos_total": {
                      "type": "integer"
                    }
                  }
                },
                "example": {
                  "passadas": [
                    {
                      "uuid": "2b8d1f30-77aa-4c19-8f21-0d6b3e9a1c44",
                      "numero": 1,
                      "nome": "Passada 1",
                      "qtd_pratos": 25,
                      "postos": [
                        {
                          "posto": 1,
                          "pratos": 5
                        },
                        {
                          "posto": 2,
                          "pratos": 5
                        },
                        {
                          "posto": 3,
                          "pratos": 5
                        },
                        {
                          "posto": 4,
                          "pratos": 5
                        },
                        {
                          "posto": 5,
                          "pratos": 5
                        }
                      ],
                      "horario_inicio": "09:00",
                      "ativa": true,
                      "scores_lancados": 0
                    }
                  ],
                  "pratos_total": 25
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Passadas"
        ],
        "summary": "Criar passada",
        "description": "Informe `postos` (a grade exata, cada item podendo levar `rotulo` — ver o schema Posto) **ou** o par `qtd_postos` × `pratos_por_posto`, que é como o clube pensa a folha. Sem nenhum dos dois, entra o preset da modalidade — no Skeet, a sequência oficial de 25 (9 blocos, com a volta ao posto 4). `qtd_pratos` é sempre derivado da soma, nunca aceito do cliente.",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nome": {
                    "type": "string"
                  },
                  "postos": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Posto"
                    }
                  },
                  "qtd_postos": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10
                  },
                  "pratos_por_posto": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 25
                  },
                  "horario_inicio": {
                    "type": "string",
                    "nullable": true,
                    "example": "09:30",
                    "description": "HH:MM (24h) ou null."
                  },
                  "ativa": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "qtd_postos": 5,
                "pratos_por_posto": 5,
                "horario_inicio": "10:30"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Passada criada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "passada": {
                      "$ref": "#/components/schemas/Passada"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Chave com escopo somente leitura.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "Configuração de postos inválida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/passadas/{passada_uuid}": {
      "patch": {
        "tags": [
          "Passadas"
        ],
        "summary": "Atualizar passada",
        "description": "**Passada com score lançado congela**: mexer em postos, pratos ou nome devolve 409, porque mudaria a grade que os atletas já assinaram. Só `ativa` continua editável.",
        "parameters": [
          {
            "name": "passada_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "ativa": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Passada atualizada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "passada": {
                      "$ref": "#/components/schemas/Passada"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Chave com escopo somente leitura.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Passada congelada por score lançado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "Valor inválido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Passadas"
        ],
        "summary": "Excluir passada",
        "description": "Passada com score exige `?confirmar=1` — o que se perde é resultado assinado. Os scores vão para o histórico append-only antes de sair.",
        "parameters": [
          {
            "name": "passada_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "confirmar",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Passada excluída",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "scores_removidos": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Chave com escopo somente leitura.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Confirmação necessária: a passada tem scores.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/provas/{prova_uuid}/squads": {
      "get": {
        "tags": [
          "Squads"
        ],
        "summary": "Listar squads",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          }
        ],
        "responses": {
          "200": {
            "description": "Squads da prova",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "squads": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Squad"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Squads"
        ],
        "summary": "Criar squad",
        "description": "Sem `numero`, entra o próximo da série. Limite padrão 5 (um atirador por posto).",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nome": {
                    "type": "string"
                  },
                  "numero": {
                    "type": "integer"
                  },
                  "limite": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 50
                  },
                  "horario_inicio": {
                    "type": "string",
                    "nullable": true,
                    "example": "09:30",
                    "description": "HH:MM (24h) ou null."
                  },
                  "observacao": {
                    "type": "string"
                  },
                  "ativo": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "numero": 2,
                "limite": 5,
                "horario_inicio": "09:30"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Squad criado",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "squad": {
                      "$ref": "#/components/schemas/Squad"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Chave com escopo somente leitura.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Já existe squad com esse número na prova.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "Valor inválido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/squads/{squad_uuid}": {
      "patch": {
        "tags": [
          "Squads"
        ],
        "summary": "Atualizar squad",
        "description": "Reduzir o limite abaixo dos atletas já vinculados devolve 409 — a vaga já foi ocupada.",
        "parameters": [
          {
            "name": "squad_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "horario_inicio": "11:00",
                "limite": 6
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Squad atualizado",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "squad": {
                      "$ref": "#/components/schemas/Squad"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Chave com escopo somente leitura.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Limite menor que a ocupação atual.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "Valor inválido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Squads"
        ],
        "summary": "Excluir squad",
        "description": "Bloqueado com atleta dentro: mova as inscrições antes.",
        "parameters": [
          {
            "name": "squad_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Squad excluído",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "excluido": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Chave com escopo somente leitura.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Squad com atletas vinculados.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/provas/{prova_uuid}/inscricoes": {
      "get": {
        "tags": [
          "Inscrições"
        ],
        "summary": "Listar inscrições",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          },
          {
            "name": "status_inscricao",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pendente",
                "confirmada",
                "cancelada"
              ]
            }
          },
          {
            "name": "squad_uuid",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "external_id",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 64
            },
            "description": "Filtra pela sua própria referência (id no sistema do integrador)."
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 25,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista paginada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "inscricoes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/InscricaoResumo"
                      }
                    },
                    "page": {
                      "type": "integer"
                    },
                    "per_page": {
                      "type": "integer"
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Inscrições"
        ],
        "summary": "Inscrever atleta",
        "description": "O atleta entra no **mesmo cadastro da entidade** que o IPSC usa (e-mail → perfil → membro): quem já atira no clube é reaproveitado, quem não é passa a ser.\n\nE-mail repetido na mesma prova devolve 422. Com `squad_uuid`, a vaga é ocupada respeitando o limite (409 se lotado) e a ordem de tiro é a próxima livre.\n\nCampos adicionais configurados pela entidade vão no mesmo corpo, com o código do campo como chave.",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "nome",
                  "email"
                ],
                "properties": {
                  "nome": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "cpf": {
                    "type": "string"
                  },
                  "telefone": {
                    "type": "string"
                  },
                  "classe": {
                    "type": "string"
                  },
                  "categoria": {
                    "type": "string"
                  },
                  "squad_uuid": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "status_inscricao": {
                    "type": "string",
                    "enum": [
                      "pendente",
                      "confirmada",
                      "cancelada"
                    ],
                    "default": "confirmada"
                  },
                  "data_nascimento": {
                    "type": "string",
                    "format": "date"
                  },
                  "sexo": {
                    "type": "string"
                  },
                  "external_id": {
                    "type": "string",
                    "maxLength": 64,
                    "nullable": true,
                    "description": "Id desta inscrição no **sistema do integrador** (opcional, exclusivo da API). Único por prova — chave de idempotência."
                  }
                }
              },
              "example": {
                "nome": "Maria Souza",
                "email": "maria@exemplo.com",
                "classe": "A",
                "categoria": "Lady",
                "squad_uuid": "5c3a91be-2f44-4d0e-b7a8-1e9c0f2d3a55"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Inscrição criada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "inscricao": {
                      "$ref": "#/components/schemas/Inscricao"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Chave com escopo somente leitura.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Squad sem vagas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "Dados inválidos ou e-mail já inscrito nesta prova.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/inscricoes/{inscricao_uuid}": {
      "get": {
        "tags": [
          "Inscrições"
        ],
        "summary": "Detalhe da inscrição",
        "parameters": [
          {
            "name": "inscricao_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Inscrição",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "inscricao": {
                      "$ref": "#/components/schemas/Inscricao"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Inscrições"
        ],
        "summary": "Atualizar inscrição",
        "description": "`squad_uuid: null` tira do squad; outro uuid move (respeitando o limite do destino). `status_participacao: \"dq\"` remove o atleta do ranking; `\"dnf\"` o joga para o fim.",
        "parameters": [
          {
            "name": "inscricao_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "status_pgto": "PAGO",
                "squad_uuid": null
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Inscrição atualizada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "inscricao": {
                      "$ref": "#/components/schemas/Inscricao"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Chave com escopo somente leitura.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Squad de destino sem vagas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "422": {
            "description": "Valor inválido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Inscrições"
        ],
        "summary": "Remover inscrição",
        "description": "Bloqueado se o atleta já tem score lançado — resultado assinado não some. Nesse caso use `status_inscricao: \"cancelada\"`.",
        "parameters": [
          {
            "name": "inscricao_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Inscrição removida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "removida": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Chave com escopo somente leitura.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Inscrição com scores lançados.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/anotadores": {
      "get": {
        "tags": [
          "Anotadores"
        ],
        "summary": "Juízes cadastrados na entidade",
        "responses": {
          "200": {
            "description": "Anotadores da entidade",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "anotadores": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Anotador"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/provas/{prova_uuid}/anotadores": {
      "get": {
        "tags": [
          "Anotadores"
        ],
        "summary": "Juízes vinculados à prova",
        "description": "Sem vínculo, o juiz recebe 404 no grid de lançamento — a prova não é dele para saber que existe.",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          }
        ],
        "responses": {
          "200": {
            "description": "Anotadores da prova",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "anotadores": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Anotador"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/provas/{prova_uuid}/resultados": {
      "get": {
        "tags": [
          "Resultados"
        ],
        "summary": "Ranking da prova",
        "description": "Soma de acertos das passadas ativas, sem hit factor e sem fator. Desempate por **count-back prato a prato**: a fita inteira da prova é lida de trás para frente (prato 50, 49, 48… em duas passadas) e o primeiro dos empatados que tiver errado perde. Não é a soma da última passada. Empate que sobrevive à fita inteira é shoot-off de campo e as duas linhas mostram a mesma posição.\n\nAtleta DQ sai do ranking; DNF vai para o fim.",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          },
          {
            "name": "incluir_nao_aprovados",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Com `1`, inclui o que ainda está em rascunho no campo (passada não fechada). Sem o parâmetro, só entra score assinado e aprovado."
          }
        ],
        "responses": {
          "200": {
            "description": "Ranking",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "prova": {
                      "$ref": "#/components/schemas/ProvaResumo"
                    },
                    "somente_aprovados": {
                      "type": "boolean"
                    },
                    "passadas": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Passada"
                      }
                    },
                    "ranking": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/RankingLinha"
                      }
                    }
                  }
                },
                "example": {
                  "prova": {
                    "uuid": "9f1c2b7e-51ab-4a0d-9d3f-2a1b7c4e5d60",
                    "nome": "Etapa COMPAK de Outubro",
                    "modalidade": "compak",
                    "status": "encerrada",
                    "pratos_total": 50
                  },
                  "somente_aprovados": true,
                  "passadas": [
                    {
                      "uuid": "2b8d1f30-77aa-4c19-8f21-0d6b3e9a1c44",
                      "numero": 1,
                      "nome": "Passada 1",
                      "qtd_pratos": 25
                    },
                    {
                      "uuid": "7d0e4a11-98bc-4f2a-9c30-5b1a2e8d7f66",
                      "numero": 2,
                      "nome": "Passada 2",
                      "qtd_pratos": 25
                    }
                  ],
                  "ranking": [
                    {
                      "posicao": 1,
                      "total": 47,
                      "passadas_lancadas": 2,
                      "advertencias": 0,
                      "dnf": false,
                      "atleta": {
                        "uuid": "4a2f8c60-3b11-4d92-8e0a-6c7b9d1e2f33",
                        "nome": "Maria Souza",
                        "email": "maria@exemplo.com",
                        "classe": "A",
                        "categoria": "Lady",
                        "status_inscricao": "confirmada",
                        "status_participacao": "ativo"
                      },
                      "por_passada": [
                        {
                          "passada_uuid": "2b8d1f30-77aa-4c19-8f21-0d6b3e9a1c44",
                          "numero": 1,
                          "nome": "Passada 1",
                          "acertos": 23
                        },
                        {
                          "passada_uuid": "7d0e4a11-98bc-4f2a-9c30-5b1a2e8d7f66",
                          "numero": 2,
                          "nome": "Passada 2",
                          "acertos": 24
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/provas/{prova_uuid}/scores": {
      "get": {
        "tags": [
          "Resultados"
        ],
        "summary": "Scores da prova (fita prato a prato)",
        "description": "A folha inteira: cada prato com posto, sequência e acerto, mais as advertências e o estado das duas assinaturas.\n\n**Somente leitura, por desenho.** O score nasce assinado no campo (HMAC do juiz encadeado com o PIN do atleta); um POST criaria resultado sem cadeia de assinatura.",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          },
          {
            "name": "incluir_nao_aprovados",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Com `1`, inclui o que ainda está em rascunho no campo (passada não fechada). Sem o parâmetro, só entra score assinado e aprovado."
          },
          {
            "name": "passada_uuid",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 25,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Scores (paginado)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "scores": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Score"
                      }
                    },
                    "page": {
                      "type": "integer"
                    },
                    "per_page": {
                      "type": "integer"
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/provas/{prova_uuid}/urls": {
      "get": {
        "tags": [
          "Resultados"
        ],
        "summary": "Links públicos da prova",
        "description": "Inscrição, resultados, hub e o JSON desta API — cada um com `ativa` e, quando fechado, o motivo. Assim o integrador não precisa reimplementar o gate de publicação para saber por que o link não abre.",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          }
        ],
        "responses": {
          "200": {
            "description": "URLs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "codigo_publico": {
                      "type": "string",
                      "nullable": true
                    },
                    "urls": {
                      "type": "object",
                      "properties": {
                        "inscricao": {
                          "$ref": "#/components/schemas/UrlPublica"
                        },
                        "resultados": {
                          "$ref": "#/components/schemas/UrlPublica"
                        },
                        "hub": {
                          "$ref": "#/components/schemas/UrlPublica"
                        },
                        "resultados_json": {
                          "$ref": "#/components/schemas/UrlPublica"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "codigo_publico": "CLAY2610",
                  "urls": {
                    "inscricao": {
                      "url": "https://exemplo/clay/i/CLAY2610",
                      "ativa": true,
                      "motivo": null
                    },
                    "resultados": {
                      "url": null,
                      "ativa": false,
                      "motivo": "Publicação de resultados desligada na prova."
                    },
                    "hub": {
                      "url": null,
                      "ativa": false,
                      "motivo": "Hub público desligado na prova."
                    },
                    "resultados_json": {
                      "url": "https://exemplo/api/v1/clay/provas/9f1c2b7e-51ab-4a0d-9d3f-2a1b7c4e5d60/resultados",
                      "ativa": true,
                      "motivo": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credenciais ausentes ou inválidas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Recurso não encontrado — inclui o caso \"módulo Clay indisponível para esta entidade\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requisições excedido (header Retry-After).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/campeonatos": {
      "get": {
        "tags": [
          "Campeonatos"
        ],
        "summary": "Lista os campeonatos da entidade",
        "parameters": [
          {
            "name": "ano",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Filtra pela temporada."
          },
          {
            "name": "ativo",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Com `1`, só campeonatos ativos."
          }
        ],
        "responses": {
          "200": {
            "description": "Campeonatos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campeonatos": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Campeonato"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Campeonatos"
        ],
        "summary": "Cria um campeonato",
        "description": "Com `usa_categorias` ou `usa_classes` ligados, o catálogo padrão do regulamento (Sênior/Veterano/Júnior/Damas + classes da Sênior) é semeado junto — regra ligada com catálogo vazio não enquadra ninguém. Envie `catalogo_padrao: false` para montar o seu do zero.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "nome"
                ],
                "properties": {
                  "nome": {
                    "type": "string"
                  },
                  "ano": {
                    "type": "integer"
                  },
                  "modalidade": {
                    "type": "string",
                    "nullable": true
                  },
                  "etapas_previstas": {
                    "type": "integer"
                  },
                  "minimo_etapas": {
                    "type": "integer"
                  },
                  "descartes": {
                    "type": "integer"
                  },
                  "usa_categorias": {
                    "type": "boolean"
                  },
                  "usa_classes": {
                    "type": "boolean"
                  },
                  "idade_modo": {
                    "type": "string",
                    "enum": [
                      "dia_exato",
                      "fim_ano"
                    ]
                  },
                  "classe_criterio": {
                    "type": "string",
                    "enum": [
                      "percentual",
                      "pratos"
                    ]
                  },
                  "trava_primeira_participacao": {
                    "type": "boolean"
                  },
                  "publico": {
                    "type": "boolean"
                  },
                  "catalogo_padrao": {
                    "type": "boolean",
                    "description": "Default true quando as regras estão ligadas."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Campeonato criado",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campeonato": {
                      "$ref": "#/components/schemas/Campeonato"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/campeonatos/{campeonato_uuid}": {
      "get": {
        "tags": [
          "Campeonatos"
        ],
        "summary": "Detalhe do campeonato",
        "parameters": [
          {
            "name": "campeonato_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid do campeonato."
          }
        ],
        "responses": {
          "200": {
            "description": "Campeonato",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campeonato": {
                      "$ref": "#/components/schemas/Campeonato"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Campeonatos"
        ],
        "summary": "Atualiza o campeonato",
        "parameters": [
          {
            "name": "campeonato_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid do campeonato."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Mesmos campos do POST; só o que vier é alterado."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Campeonato",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campeonato": {
                      "$ref": "#/components/schemas/Campeonato"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Campeonatos"
        ],
        "summary": "Exclui o campeonato",
        "description": "409 quando há etapas vinculadas: etapa órfã perde o catálogo que enquadrou quem já se inscreveu. Desvincule as provas antes.",
        "parameters": [
          {
            "name": "campeonato_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid do campeonato."
          }
        ],
        "responses": {
          "204": {
            "description": "Excluído"
          },
          "409": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/campeonatos/{campeonato_uuid}/etapas": {
      "get": {
        "tags": [
          "Campeonatos"
        ],
        "summary": "Provas vinculadas ao campeonato",
        "parameters": [
          {
            "name": "campeonato_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid do campeonato."
          }
        ],
        "responses": {
          "200": {
            "description": "Etapas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "etapas": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProvaResumo"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/provas/{prova_uuid}/campeonato": {
      "put": {
        "tags": [
          "Campeonatos"
        ],
        "summary": "Vincula a prova como etapa",
        "description": "A prova passa a herdar o catálogo e as regras do campeonato.",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "campeonato_uuid"
                ],
                "properties": {
                  "campeonato_uuid": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Campeonato",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campeonato": {
                      "$ref": "#/components/schemas/Campeonato"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Campeonatos"
        ],
        "summary": "Desvincula a prova do campeonato",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          }
        ],
        "responses": {
          "204": {
            "description": "Desvinculada"
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/campeonatos/{campeonato_uuid}/ranking": {
      "get": {
        "tags": [
          "Campeonatos"
        ],
        "summary": "Ranking da temporada",
        "description": "Soma das etapas com `minimo_etapas` e `descartes` já aplicados. O atleta é identificado pelo perfil e, sem perfil, pelo nome normalizado — prova de prato vive de visitante sem conta.",
        "parameters": [
          {
            "name": "campeonato_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid do campeonato."
          },
          {
            "name": "incluir_nao_aprovados",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Com `1`, soma também o que ainda não foi assinado (parcial)."
          }
        ],
        "responses": {
          "200": {
            "description": "Ranking da temporada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ranking": {
                      "type": "object",
                      "properties": {
                        "campeonato": {
                          "$ref": "#/components/schemas/Campeonato"
                        },
                        "etapas": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "uuid": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "nome": {
                                "type": "string"
                              },
                              "data_prova": {
                                "type": "string",
                                "format": "date",
                                "nullable": true
                              }
                            }
                          }
                        },
                        "linhas": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/RankingCampeonatoLinha"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/campeonatos/{campeonato_uuid}/catalogo": {
      "get": {
        "tags": [
          "Catálogo"
        ],
        "summary": "Catálogo do campeonato",
        "parameters": [
          {
            "name": "campeonato_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid do campeonato."
          }
        ],
        "responses": {
          "200": {
            "description": "Catálogo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "catalogo": {
                      "$ref": "#/components/schemas/Catalogo"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/provas/{prova_uuid}/catalogo": {
      "get": {
        "tags": [
          "Catálogo"
        ],
        "summary": "Catálogo efetivo da prova",
        "description": "A herança já resolvida: `escopo` diz se o catálogo veio de uma exceção da etapa, do campeonato ou do padrão do clube. `escopo: null` significa prova sem catálogo — classe e categoria seguem como texto livre na inscrição.",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          }
        ],
        "responses": {
          "200": {
            "description": "Catálogo efetivo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "catalogo": {
                      "$ref": "#/components/schemas/Catalogo"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/campeonatos/{campeonato_uuid}/catalogo/padrao": {
      "post": {
        "tags": [
          "Catálogo"
        ],
        "summary": "Semeia o catálogo padrão do regulamento",
        "description": "Idempotente por código: rodar de novo não duplica nem sobrescreve o que o clube editou.",
        "parameters": [
          {
            "name": "campeonato_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid do campeonato."
          }
        ],
        "responses": {
          "200": {
            "description": "Catálogo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "catalogo": {
                      "$ref": "#/components/schemas/Catalogo"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/campeonatos/{campeonato_uuid}/categorias": {
      "post": {
        "tags": [
          "Catálogo"
        ],
        "summary": "Cria uma categoria",
        "parameters": [
          {
            "name": "campeonato_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid do campeonato."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "nome"
                ],
                "properties": {
                  "nome": {
                    "type": "string"
                  },
                  "codigo": {
                    "type": "string",
                    "description": "Derivado do nome quando omitido."
                  },
                  "ordem": {
                    "type": "integer"
                  },
                  "tipo": {
                    "type": "string",
                    "enum": [
                      "automatica",
                      "manual"
                    ]
                  },
                  "sexo": {
                    "type": "string",
                    "nullable": true,
                    "description": "M, F ou null."
                  },
                  "idade_min": {
                    "type": "integer",
                    "nullable": true
                  },
                  "idade_max": {
                    "type": "integer",
                    "nullable": true
                  },
                  "optar_categoria_uuid": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  },
                  "descricao": {
                    "type": "string"
                  },
                  "ativo": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Categoria criada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "categoria": {
                      "$ref": "#/components/schemas/Categoria"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/campeonatos/{campeonato_uuid}/categorias/{categoria_uuid}/classes": {
      "post": {
        "tags": [
          "Catálogo"
        ],
        "summary": "Cria uma classe dentro da categoria",
        "description": "A faixa é `[valor_min, valor_max)` — mínimo inclusive, máximo exclusivo. É o que faz \"AA de 87 a 94\" e \"Premium 94 acima\" não se sobreporem. `sem_historico: true` marca a classe de quem ainda não tem ranking (Iniciante) e dispensa a faixa.",
        "parameters": [
          {
            "name": "campeonato_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid do campeonato."
          },
          {
            "name": "categoria_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da categoria dona da classe."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "nome"
                ],
                "properties": {
                  "nome": {
                    "type": "string"
                  },
                  "codigo": {
                    "type": "string"
                  },
                  "ordem": {
                    "type": "integer"
                  },
                  "valor_min": {
                    "type": "number",
                    "nullable": true
                  },
                  "valor_max": {
                    "type": "number",
                    "nullable": true
                  },
                  "sem_historico": {
                    "type": "boolean"
                  },
                  "descricao": {
                    "type": "string"
                  },
                  "ativo": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Classe criada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "classe": {
                      "$ref": "#/components/schemas/Classe"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/clay/provas/{prova_uuid}/shootoffs": {
      "get": {
        "tags": [
          "Shoot-off"
        ],
        "summary": "Desempates da prova",
        "parameters": [
          {
            "name": "prova_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid da prova Clay."
          }
        ],
        "responses": {
          "200": {
            "description": "Shoot-offs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "shootoffs": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Shootoff"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        },
        "description": "Desempates da prova (leitura). Abrir, marcar e concluir shoot-off são operação da prova: a fita nasce assinada na pedana (HMAC do juiz encadeado com o PIN do atleta) e muda o pódio — pelo mesmo motivo que score não se escreve pela API."
      }
    },
    "/clay/shootoffs/{shootoff_uuid}": {
      "get": {
        "tags": [
          "Shoot-off"
        ],
        "summary": "Estado do desempate",
        "parameters": [
          {
            "name": "shootoff_uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "uuid do shoot-off."
          }
        ],
        "responses": {
          "200": {
            "description": "Shoot-off",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "shootoff": {
                      "$ref": "#/components/schemas/Shootoff"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Erro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        },
        "description": "Estado do desempate: fita de cada participante, colocação e desfecho (tiro, WO ou idade). Somente leitura."
      }
    }
  }
}
