{
  "openapi": "3.0.3",
  "info": {
    "title": "MOJ API",
    "version": "v1",
    "description": "API REST do MOJ (Melhor Online Judge). Backend bash atrás de nginx+fcgiwrap. Auth via `Authorization: Bearer <token>`. Histórico e placar são TXT; o resto é JSON com envelope `{success:true,…}` ou `{success:false,error:{message,code}}`. Horários em EPOCH."
  },
  "servers": [
    {
      "url": "/api/v1",
      "description": "Instância atual"
    }
  ],
  "tags": [
    {
      "name": "auth"
    },
    {
      "name": "index"
    },
    {
      "name": "treino"
    },
    {
      "name": "submission"
    },
    {
      "name": "contest"
    },
    {
      "name": "admin"
    },
    {
      "name": "ops"
    },
    {
      "name": "treino-admin"
    },
    {
      "name": "problems",
      "description": "Gestão e autoria de problemas do treino. Escrita keyless (git escondido; commit autorado pelo login via git-broker.sh). source/create/edit cobrem o pacote canônico inteiro."
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer"
      }
    },
    "parameters": {
      "contest": {
        "name": "contest",
        "in": "query",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "id do contest (ex.: treino)"
      }
    },
    "schemas": {
      "ProblemExample": {
        "type": "object",
        "description": "Exemplo (sample) — sempre aparente no enunciado.",
        "properties": {
          "input": {
            "type": "string"
          },
          "output": {
            "type": "string"
          },
          "explanation": {
            "type": "string",
            "description": "Markdown opcional; aparece logo após o exemplo no enunciado (gravado em docs/sample-notes.json, na ordem)."
          }
        },
        "required": [
          "input",
          "output"
        ]
      },
      "ProblemTest": {
        "type": "object",
        "description": "Teste oculto.",
        "properties": {
          "name": {
            "type": "string"
          },
          "input": {
            "type": "string"
          },
          "output": {
            "type": "string"
          },
          "group": {
            "type": "string",
            "description": "Grupo de pontuação (subtask), opcional."
          }
        },
        "required": [
          "input",
          "output"
        ]
      },
      "ProblemSol": {
        "type": "object",
        "properties": {
          "filename": {
            "type": "string"
          },
          "code": {
            "type": "string"
          }
        },
        "required": [
          "filename",
          "code"
        ]
      },
      "ProblemSols": {
        "type": "object",
        "description": "Soluções por categoria (cada lista de {filename,code}). good define o tempo-limite; wrong deve falhar; slow deve estourar o tempo.",
        "properties": {
          "good": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProblemSol"
            }
          },
          "slow": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProblemSol"
            }
          },
          "wrong": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProblemSol"
            }
          },
          "pass": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProblemSol"
            }
          },
          "upcoming": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProblemSol"
            }
          }
        }
      },
      "ProblemScore": {
        "type": "object",
        "description": "Pontuação por grupos (subtasks). O valor do problema é a soma dos pesos (pode passar de 100).",
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "groups": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "weight": {
                  "type": "number"
                },
                "glob": {
                  "type": "string",
                  "description": "Padrão glob dos testes do grupo. Pode ser uma LISTA de padrões separados por \", \" (ex.: \"g2_*, g3_*\") — todos casam o mesmo peso."
                }
              }
            }
          }
        }
      },
      "ProblemFields": {
        "type": "object",
        "description": "Campos do pacote canônico — source/create/edit cobrem o todo. Em edit, só os campos presentes são gravados.",
        "properties": {
          "title": {
            "type": "string",
            "description": "Título — vem do CAMPO (não de '% Título' no texto); o renderizador injeta o <h1>."
          },
          "enunciado_md": {
            "type": "string"
          },
          "enunciado_format": {
            "type": "string",
            "enum": [
              "md",
              "org",
              "tex"
            ]
          },
          "author": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "conf_text": {
            "type": "string",
            "description": "conf do pacote (TLMOD/ULIMITS/STOPWHEN/MEMLIMITMB/…)."
          },
          "public": {
            "type": "boolean"
          },
          "collections": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "languages": {
            "type": "array",
            "description": "Ids de linguagem de submissão permitidos deste problema ([]/ausente = todas as PADRÃO). Linguagens exóticas/opt-in (pddl/grepe/sas/l/lpp/downward, e ids custom futuros) só aparecem no dropdown quando DECLARADAS aqui. Em edit: ausente = não toca; [] = limpa.",
            "items": {
              "type": "string"
            }
          },
          "examples": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProblemExample"
            }
          },
          "tests": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProblemTest"
            }
          },
          "sols": {
            "$ref": "#/components/schemas/ProblemSols"
          },
          "score": {
            "$ref": "#/components/schemas/ProblemScore"
          },
          "editorial_md": {
            "type": "string",
            "description": "Resolução em markdown (docs/solucao.md). SÓ p/ setter — não vai ao aluno; opcional para liberar."
          }
        }
      }
    }
  },
  "paths": {
    "/auth/login": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Login → token",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "username": {
                    "type": "string"
                  },
                  "password": {
                    "type": "string"
                  }
                },
                "required": [
                  "username",
                  "password"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{token, logged_in, username, name, contest}"
          },
          "401": {
            "description": "credenciais inválidas"
          }
        }
      }
    },
    "/auth/status": {
      "get": {
        "tags": [
          "auth"
        ],
        "summary": "Status da sessão",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "contest",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{logged_in, login, name, is_admin, is_judge, is_staff, is_cstaff, is_chief} (.cstaff = chefe de sede, não herda is_staff)"
          }
        }
      }
    },
    "/auth/logout": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Logout",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{logged_out:true}"
          }
        }
      }
    },
    "/index/news": {
      "get": {
        "tags": [
          "index"
        ],
        "summary": "Notícias",
        "responses": {
          "200": {
            "description": "{news:[{id,title,date,summary,url}]}"
          }
        }
      }
    },
    "/index/contests": {
      "get": {
        "tags": [
          "index"
        ],
        "summary": "Contests (abertos/por vir/encerrados)",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{open,upcoming,closed:{items,page,pages}}"
          }
        }
      }
    },
    "/index/open_training": {
      "get": {
        "tags": [
          "index"
        ],
        "summary": "Destaques do treino (top10, recentes, semana)",
        "responses": {
          "200": {
            "description": "{top_users,recent_solved,most_solved_week}"
          }
        }
      }
    },
    "/treino/problems": {
      "get": {
        "tags": [
          "treino"
        ],
        "summary": "Lista de problemas",
        "responses": {
          "200": {
            "description": "[{id,title,tags,collections,solved_count,attempted_count}] (collections = .moj-meta.json do pacote; um problema pode estar em várias)"
          }
        }
      }
    },
    "/treino/problem": {
      "get": {
        "tags": [
          "treino"
        ],
        "summary": "Problema (enunciado b64, autor, time limits, tags, coleções)",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{id,title,author,statement_html_b64,time_limits,tags,collections,languages} (author = arquivo 'author' do pacote, verbatim; vários juntados por ', '; vazio se ausente; collections = coleções do .moj-meta.json; languages = ids de linguagem de submissão permitidos, [] = todas as PADRÃO — o front filtra o dropdown; exóticas/opt-in (pddl/grepe/sas/l/lpp/downward) só aparecem quando o problema as declara)"
          },
          "404": {
            "description": "não encontrado"
          }
        }
      }
    },
    "/treino/solvetry": {
      "get": {
        "tags": [
          "treino"
        ],
        "summary": "Resolvidos/tentados de um usuário",
        "parameters": [
          {
            "name": "user",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{solved:[ids],attempted:[ids]}"
          }
        }
      }
    },
    "/treino/history": {
      "get": {
        "tags": [
          "treino"
        ],
        "summary": "Histórico do usuário no problema (TXT)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "TXT: tempo:user:probid:lang:veredito:epoch:subid — veredito SEMPRE canônico (Accepted/Wrong Answer/…; pendentes e strings desconhecidas intactos); o detalhe (testes/pontos/grupos) vem do /submission/summary",
            "content": {
              "text/plain": {}
            }
          }
        }
      }
    },
    "/treino/history-full": {
      "get": {
        "tags": [
          "treino"
        ],
        "summary": "Histórico completo do usuário (TXT)",
        "parameters": [
          {
            "name": "user",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "TXT (7 campos) — veredito SEMPRE canônico; visitante do perfil público vê só o rótulo (o resumo do /submission/summary é só do dono)",
            "content": {
              "text/plain": {}
            }
          }
        }
      }
    },
    "/treino/profile": {
      "get": {
        "tags": [
          "treino"
        ],
        "summary": "Perfil (próprio com Bearer; ?user=X = visão pública, respeita privacidade)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "user",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "ver perfil público de outro usuário"
          }
        ],
        "responses": {
          "200": {
            "description": "perfil"
          }
        }
      },
      "post": {
        "tags": [
          "treino"
        ],
        "summary": "Atualiza nome, universidade, editor favorito e/ou privacidade",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "university": {
                    "type": "string"
                  },
                  "favorite_editor": {
                    "type": "string"
                  },
                  "profile_public": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "perfil atualizado"
          }
        }
      }
    },
    "/treino/profile/photo": {
      "get": {
        "tags": [
          "treino"
        ],
        "summary": "Foto de perfil (png 100x100)",
        "parameters": [
          {
            "name": "user",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "image/png",
            "content": {
              "image/png": {}
            }
          },
          "404": {
            "description": "sem foto / privado"
          }
        }
      },
      "post": {
        "tags": [
          "treino"
        ],
        "summary": "Envia foto (redimensiona p/ 100x100)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "image_b64": {
                    "type": "string"
                  }
                },
                "required": [
                  "image_b64"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{updated:true}"
          }
        }
      }
    },
    "/treino/editors": {
      "get": {
        "tags": [
          "treino"
        ],
        "summary": "Ranking dos editores favoritos declarados",
        "responses": {
          "200": {
            "description": "{editors:[{editor,count}], total}"
          }
        }
      }
    },
    "/treino/problem-stats": {
      "get": {
        "tags": [
          "treino"
        ],
        "summary": "Estatísticas de um problema (cacheado)",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{total_submissions,distinct_attempted,distinct_solved,acceptance_rate,verdicts,by_language:[{lang,submissions,accepted,solvers}],editors,solver_avatars,solvers_public_count}"
          }
        }
      }
    },
    "/treino/admin/sessions": {
      "get": {
        "tags": [
          "treino-admin"
        ],
        "summary": "Sessões ativas (login, IP, User-Agent)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{count,sessions:[{login,name,ip,user_agent,login_at}]}"
          },
          "403": {
            "description": "não-admin"
          }
        }
      }
    },
    "/treino/admin/access-log": {
      "get": {
        "tags": [
          "treino-admin"
        ],
        "summary": "Log de acessos (filtra por dia)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "day",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "YYYY-MM-DD"
          }
        ],
        "responses": {
          "200": {
            "description": "{day,entries:[{time,login,ip,user_agent}]}"
          }
        }
      }
    },
    "/treino/admin/queue": {
      "get": {
        "tags": [
          "treino-admin"
        ],
        "summary": "Submissões pendentes por lista + spool + fila de calibração",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{total_pending,spool_queued,calib_pending,calib_inflight,calib_targeted,lists:[{contest,name,pending}]} — calib_* = fila de calibração (kind=calibrate, separada de index; calib_targeted = recalibrações direcionadas por host)"
          }
        }
      }
    },
    "/treino/admin/judges": {
      "get": {
        "tags": [
          "treino-admin"
        ],
        "summary": "Máquinas de juiz (modelo pull) + o que cada uma roda agora",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{online,busy,machines:[{host,online,busy,langs,cage_root,cache,tl,current,queued_calibrate,report}]} — current = job em execução {kind:submission|calibrate|index|unknown_busy, problem_id, ...} ou null; queued_calibrate = calibrações direcionadas na fila do host"
          }
        }
      }
    },
    "/treino/admin/stats": {
      "get": {
        "tags": [
          "treino-admin"
        ],
        "summary": "Números p/ gráficos + contagem/entrada de problemas + por autor",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{users,active_sessions,problems:{total,public,private},by_author:[{author,owner,total,public,private}],problems_public_by_day:[{day,count}],logins_per_day,submissions_per_day} — contagens da plataforma (privados contados, NÃO listados); public_by_day = entrada de públicos (data aproximada)"
          }
        }
      }
    },
    "/treino/admin/response-stats": {
      "get": {
        "tags": [
          "treino-admin"
        ],
        "summary": "Tempo de resposta + volume de submissões (média/dia + mapas de calor)",
        "description": "Cacheado (regenera quando history/results mudam). Tempo (espera/julgamento/fila) só de submissões com finalized_at; VOLUME (subs_per_day/subs_by_dow_hour) conta TODAS as linhas do history. EPOCH/UTC.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{coverage, overall, per_day, by_dow_hour, subs_per_day:[{day,count}], subs_by_dow_hour:[{dow,hour,n}]}"
          },
          "403": {
            "description": "não-admin (admin_required)"
          }
        }
      }
    },
    "/treino/admin/calib-activity": {
      "get": {
        "tags": [
          "treino-admin"
        ],
        "summary": "Volume de calibrações no tempo (por dia + dia-da-semana × hora)",
        "description": "Cacheado (regenera quando o log run/updates/log muda). Conta os eventos 'cacheado+calibrado' do log append-only. run/ NÃO é versionado e pode rotacionar -> cobertura histórica parcial.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{calib_per_day:[{day,count}], calib_by_dow_hour:[{dow,hour,n}], total}"
          },
          "403": {
            "description": "não-admin (admin_required)"
          }
        }
      }
    },
    "/treino/admin/logout-user": {
      "post": {
        "tags": [
          "treino-admin"
        ],
        "summary": "Desloga um usuário (remove sessões)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "login": {
                    "type": "string"
                  },
                  "logins": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{logged_out,sessions_removed}"
          }
        }
      }
    },
    "/treino/admin/lock-user": {
      "post": {
        "tags": [
          "treino-admin"
        ],
        "summary": "Trava acesso (troca senha por aleatória + desloga)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "login": {
                    "type": "string"
                  },
                  "logins": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{locked,sessions_removed}"
          }
        }
      }
    },
    "/treino/admin/logout-ip": {
      "post": {
        "tags": [
          "treino-admin"
        ],
        "summary": "Desloga todas as sessões de um IP",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ip": {
                    "type": "string"
                  }
                },
                "required": [
                  "ip"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{logged_out,ip,sessions_removed,users,users_count}"
          }
        }
      }
    },
    "/treino/profile/password": {
      "post": {
        "tags": [
          "treino"
        ],
        "summary": "Troca a própria senha",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "old_password": {
                    "type": "string"
                  },
                  "new_password": {
                    "type": "string"
                  }
                },
                "required": [
                  "old_password",
                  "new_password"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{updated:true}"
          },
          "403": {
            "description": "senha atual incorreta"
          }
        }
      }
    },
    "/treino/profile/username": {
      "post": {
        "tags": [
          "treino"
        ],
        "summary": "Troca o próprio username (máx. 2/ano + cascata)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "new_username": {
                    "type": "string"
                  }
                },
                "required": [
                  "new_username"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{updated:true,new_username,username_changes_remaining}"
          },
          "403": {
            "description": "limite anual atingido"
          },
          "409": {
            "description": "em uso ou submissões pendentes"
          }
        }
      }
    },
    "/submit": {
      "post": {
        "tags": [
          "submission"
        ],
        "summary": "Submeter solução (assíncrono). Gate por fase+papel: admin/judge sempre; staff nunca (403 submit_forbidden); normal e mon só durante a janela (403 contest_not_started / contest_ended; mon fica fora do placar)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "problem_id": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "code_b64": {
                    "type": "string"
                  }
                },
                "required": [
                  "problem_id",
                  "code_b64"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{submission_id, status:\"queued\"}"
          }
        }
      }
    },
    "/submission/source": {
      "get": {
        "tags": [
          "submission"
        ],
        "summary": "Código-fonte de uma submissão",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "time",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "código (texto)",
            "content": {
              "text/plain": {}
            }
          }
        }
      }
    },
    "/submission/log": {
      "get": {
        "tags": [
          "submission"
        ],
        "summary": "Log do julgamento (report.html; expõe input+diff de todos os testes — dono vê conforme o SHOWLOG efetivo: explícito manda, ausente = oculto em modo icpc)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "time",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "log (texto)",
            "content": {
              "text/plain": {}
            }
          }
        }
      }
    },
    "/submission/summary": {
      "get": {
        "tags": [
          "submission"
        ],
        "summary": "Resumo estruturado em lote (detalhe sob o veredicto canônico), REDIGIDO pelo modo do contest",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "ids",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "csv de submission ids (até 1000)"
          }
        ],
        "responses": {
          "200": {
            "description": "{\"<id>\":{verdict,verdict_canon,score,score_max,score_kind,correct,total,groups,heur_score?,heur_adjusted?}} — redigido por modo (lib/verdict.sh): full (treino/lista)=tudo; score (obi/heurístico/outro)=canônico+score/groups/heur sem correct/total; none (icpc/ausente)=só o canônico (nos níveis redigidos verdict=canônico; juiz/admin sempre full com verdict cru). groups=[{earned,max},…] na ordem do tests/score (earned null = grupo não executado; results antigos: canon derivado da string, groups da cauda 'Pontos | … |' com max:null). Mesmo gate do log; ids de terceiros omitidos"
          }
        }
      }
    },
    "/contest/basic": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Info pública (nome, horários, login_start_time)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{contest_id,contest_name,start_time,end_time,login_start_time,locale,login_enabled,freeze_time,score_anon,languages[],secret} — languages = whitelist do conf LANGUAGES= ([] = todas); locale = pt|en explícito (IMPÕE o idioma da UI do contest) ou \"\" quando não setado (o front cai no seletor/idioma do browser)"
          }
        }
      }
    },
    "/contest/userinfo": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Info do usuário logado",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{login,name,…}"
          }
        }
      }
    },
    "/contest/navbuttons": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Botões de navegação por papel (.admin/.judge/.staff/.cstaff/.mon; .cstaff ganha Etiquetas e — pós-fim p/ todas as sedes — Revelação; .staff sem Etiquetas)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "[{label,url}]"
          }
        }
      }
    },
    "/contest/problems": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Problemas da prova. Gate: admin/judge veem sempre; staff nunca; normal só após o início (antes -> {problems:[], locked:'not_started'})",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{problems:[{short_name,full_name,problem_id,statement_html_b64,statement_pdf_b64,time_limits,languages}]} (time_limits = MAX entre juízes; com pool de juízes definido — problem-judges.json -> CONTEST_JUDGES — só entre os hosts do pool efetivo. languages = ids permitidos: override por-problema-no-contest -> whitelist do contest -> default do próprio pacote (.moj-meta.json) -> [] (todas))"
          }
        }
      }
    },
    "/contest/news": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Notícias do contest (opcional)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          }
        }
      }
    },
    "/contest/updates": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Resumo de novidades p/ polling (notícias + clarifications não lidas)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{news:{last,count,unread}, clar:{last,count,unread}}"
          }
        }
      }
    },
    "/contest/backup": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Lista os backups do próprio usuário",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{backups:[{id,name,size,time}]}"
          }
        }
      },
      "post": {
        "tags": [
          "contest"
        ],
        "summary": "Guarda/remove um backup de arquivo (versão de solução)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{filename,file_b64} guarda; {action:remove,id} remove"
          }
        }
      }
    },
    "/contest/backup-file": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Baixa um backup (próprio; login= só admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "arquivo (octet-stream)"
          }
        }
      }
    },
    "/contest/print": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Lista os pedidos de impressão do próprio usuário (+ flags staff_exists/allow_print)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{requests:[{id,seq,filename,mime,size,time,status,pages}], staff_exists, allow_print}"
          }
        }
      },
      "post": {
        "tags": [
          "contest"
        ],
        "summary": "Cria um pedido de impressão (gera nº sequencial)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{id,seq,status:pending} — body {filename,file_b64}, máx 10MB"
          },
          "403": {
            "description": "print_unavailable (sem staff) | print_disabled (PRINT=0)"
          }
        }
      }
    },
    "/contest/print-file": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Baixa o arquivo cru de um pedido (dono/admin/staff no escopo; .cstaff não — 403)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "arquivo (Content-Disposition)"
          }
        }
      }
    },
    "/contest/staff/queue": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Fila de tarefas do staff (impressão + balões; escopo por staff-filters; .cstaff SÓ LÊ — ações/PDF 403). Reconcilia balões pendentes (1ª-AC) ao carregar",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{requests:[{id,seq,kind,login,team,univ,short,color_hex,color_name,filename,status,…}]}"
          }
        }
      }
    },
    "/contest/staff/print-action": {
      "post": {
        "tags": [
          "contest"
        ],
        "summary": "Máquina de estado da tarefa: claim|processed|delivered (.staff/admin; .cstaff não — 403)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{updated:{id,seq,status,…}} — body {id,action,mode?}"
          },
          "409": {
            "description": "already_claimed | not_pending | not_printed"
          }
        }
      }
    },
    "/contest/staff/print-pdf": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "PDF inline (build-once): kind=print → folha de rosto + documento A4 (código numerado); kind=balloon → folha do balão (time/univ/login/problema/cor desenhada+nome/nº/assinatura). .staff/admin; .cstaff não — 403",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "application/pdf"
          }
        }
      }
    },
    "/contest/badges": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Credenciais p/ etiquetas imprimíveis (SENHA EM CLARO, sempre presente; auditado badges-view). Admin/.cstaff apenas — .staff recebe 403 cstaff_required. Admin: tudo ou o arquivo de uma sede via staff=<login .cstaff>; .cstaff: só o próprio recorte (staff-filters) + a própria conta e as .staff/.cstaff do mesmo escopo. include_disabled=1 inclui contas desativadas",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{users:[{login,name,fullname,password,disabled,region,univ}], staff:[…só admin, logins .cstaff], regions, contest_name, start_epoch}"
          },
          "403": {
            "description": "cstaff_required | staff_scope"
          }
        }
      }
    },
    "/contest/admin/staff-filters": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Escopo de cada usuário .staff/.cstaff (no .cstaff vale p/ fila-leitura, etiquetas e cerimônia) + lista de staff/regiões",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{staff:[{login,fullname,disabled}], filters:{login:[regex]}, regions:[{name,regex}]}"
          }
        }
      },
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Grava o escopo por staff/cstaff (entradas region:<nome> — igualdade com a sede do time — ou regex no login; vazio = vê tudo)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{saved:true,filters} — body {filters:{login:[regex]}}"
          }
        }
      }
    },
    "/contest/admin/preflight": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Checklist pré-prova (janela, SHOWLOG, freeze, juízes online, pool de juízes — estrito: fail se o pool do contest está todo offline; pool_problems p/ overrides por problema — toolchains e TL calibrado no pool efetivo, staff, contas, spool)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{checks:[{id,level:ok|warn|fail,label,detail}], summary:{ok,warn,fail}}"
          }
        }
      }
    },
    "/contest/admin/time-overrides": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Prorrogação de vigência por sede/grupo: regras [{regex,end,reason?}] no login (1ª que casa estende o fim SÓ daquele grupo)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{rules:[{regex,end,reason}], contest_end, regions} — aplicado no /submit e no countdown (contest_end_effective)"
          }
        }
      },
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Substitui as regras de prorrogação (valida regex/end; só estende, nunca encurta; auditado)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{saved:true,rules} — body {rules:[{regex,end,reason?}]}"
          }
        }
      }
    },
    "/contest/admin/backups": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Lista todos os backups (filtros user/q) + resumo por usuário",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{backups:[{login,id,name,size,time}],users:[{login,count,bytes}]}"
          }
        }
      }
    },
    "/contest/admin/backup-zip": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Zip com todos os backups de um usuário",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "application/zip"
          }
        }
      }
    },
    "/contest/admin/report": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Relatório estático da prova (tar.gz navegável offline: placar aberto, runs, enunciados, clarifications anônimas, estatísticas, tarefas do staff, infra)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "application/gzip (relatorio-<c>-<data>.tar.gz)"
          },
          "429": {
            "description": "busy — já existe uma geração em andamento"
          }
        }
      }
    },
    "/contest/news-file": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Baixa o anexo de uma notícia",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "arquivo (octet-stream, Content-Disposition: attachment)"
          }
        }
      }
    },
    "/contest/resources": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Materiais do contest (opcional)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          }
        }
      }
    },
    "/contest/history": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Histórico do usuário (TXT)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "TXT: tempo:user:probid:lang:veredito:epoch:subid — veredito (campo 5) SEMPRE canônico em TODOS os modos (a string com score/grupos fica no disco; o detalhe por modo vem do /submission/summary redigido). Pendentes/strings desconhecidas intactos; sufixo ' (Ignored)' preservado",
            "content": {
              "text/plain": {}
            }
          }
        }
      }
    },
    "/contest/balloons": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Cores de balão por problema",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "mapa letra→cor"
          }
        }
      }
    },
    "/contest/regions": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Regiões p/ filtro do placar",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          }
        }
      }
    },
    "/contest/score": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Placar (TXT, 1ª linha = modo); privilegiado recebe o completo — view=public força o congelado (usado pela cerimônia de revelação); scope=mine (só .cstaff) recorta à sede",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "view",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "public"
              ]
            },
            "description": "public = visão pública/congelada mesmo com sessão privilegiada"
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "mine"
              ]
            },
            "description": "mine (honrado só p/ .cstaff) = recorta o placar servido (frozen e full) aos usuários do escopo dele (staff-filters); fora da allowlist SCORE_FULL_USERS, o full só sai quando o contest terminou p/ TODAS as sedes (fim do conf + maior end de time-overrides.json) — cerimônia por sede"
          }
        ],
        "responses": {
          "200": {
            "description": "TXT",
            "content": {
              "text/plain": {}
            }
          },
          "401": {
            "description": "contest SUPER SECRETO sem sessão do contest (secret_login_required)"
          }
        }
      }
    },
    "/contest/allsubmissions": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Todas as submissões (admin, TXT 9 campos)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "TXT",
            "content": {
              "text/plain": {}
            }
          }
        }
      }
    },
    "/contest/final-verdicts": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Opções de veredicto manual (judge): {verdicts:[labels], options:[{label,verdict}]}",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          }
        }
      },
      "post": {
        "tags": [
          "contest"
        ],
        "summary": "Grava as opções de veredicto (admin/chief): {options:[{label,verdict}]}",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{saved,options}; 403 config_forbidden"
          }
        }
      }
    },
    "/contest/auto-verdicts": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Matriz de veredicto automático problema×lang×veredicto (judge)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{matrix,problems,verdicts}"
          }
        }
      },
      "post": {
        "tags": [
          "contest"
        ],
        "summary": "Grava a matriz auto (admin/chief): {matrix:{cid:{lang|*:[verdict]}}}",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{saved,matrix}; 403 config_forbidden"
          }
        }
      }
    },
    "/contest/review/list": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Fila de revisão de veredicto manual (judge; votos detalhados só p/ admin/chief — anti-anchoring)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{manual,options,items,counts:{not_evaluated,being_evaluated,awaiting_second,conflicts},my_active}"
          }
        }
      }
    },
    "/contest/review/claim": {
      "post": {
        "tags": [
          "contest"
        ],
        "summary": "Pega/renova/larga avaliação (judge): {id,action:claim|extend|giveup}; máx 2, 1 ativa, TTL 5min",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{updated}; 409 already_evaluating|slots_full"
          }
        }
      }
    },
    "/contest/review/vote": {
      "post": {
        "tags": [
          "contest"
        ],
        "summary": "Vota o veredicto (judge): {id,label}; voto permanente e libera o juiz (pega outra); 2 iguais liberam, 2 diferentes = conflito",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{status,released_verdict?}"
          }
        }
      }
    },
    "/contest/review/resolve": {
      "post": {
        "tags": [
          "contest"
        ],
        "summary": "Juiz-chefe resolve conflito (admin/chief): {id,verdict}",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{status:released,released_verdict}; 403 chief_required"
          }
        }
      }
    },
    "/contest/review/conflicts": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Sumário de conflitos de veredicto p/ o juiz-chefe (admin/chief); n alimenta o alerta global",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{conflicts:[{id,login,problem_id,lang,sub_epoch,computed_verdict,votes}], n, options}"
          }
        }
      }
    },
    "/contest/review/stats": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Estatística por .judge da avaliação manual (admin/chief): votos, tempo médio, concordâncias, conflitos",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{judges:[{judge,votes,avg_response_s,timed,agreements,conflicts}], total:{votes,avg_response_s}}"
          }
        }
      }
    },
    "/contest/set-verdict": {
      "post": {
        "tags": [
          "contest"
        ],
        "summary": "Definir veredicto final (judge)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contest": {
                    "type": "string"
                  },
                  "problem_id": {
                    "type": "string"
                  },
                  "verdict": {
                    "type": "string"
                  },
                  "username": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok"
          }
        }
      }
    },
    "/contest/rejudge": {
      "post": {
        "tags": [
          "contest"
        ],
        "summary": "Rejulgar (admin): reconstrói a fonte arquivada + reinjeta como submit",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contest": {
                    "type": "string"
                  },
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{action:rejudge, queued[], count, skipped[], skipped_count} (skipped = sem fonte/linha no history)"
          }
        }
      }
    },
    "/admin/adduser": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Adicionar usuário",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contest": {
                    "type": "string"
                  },
                  "login": {
                    "type": "string"
                  },
                  "fullname": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "password": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{login,password}"
          }
        }
      }
    },
    "/admin/passwd": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Trocar senha",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contest": {
                    "type": "string"
                  },
                  "login": {
                    "type": "string"
                  },
                  "newpass": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok"
          }
        }
      }
    },
    "/admin/contest/extend": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Estender fim do contest",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contest": {
                    "type": "string"
                  },
                  "end_epoch": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok"
          }
        }
      }
    },
    "/admin/synctreino": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Sincronizar treino",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          }
        }
      }
    },
    "/admin/rejudge": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Rejulgar (alias do bot)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contest": {
                    "type": "string"
                  },
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "problem": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok"
          }
        }
      }
    },
    "/ops/queue": {
      "get": {
        "tags": [
          "ops"
        ],
        "summary": "Tamanho da fila por contest",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          }
        }
      }
    },
    "/ops/problemtl": {
      "get": {
        "tags": [
          "ops"
        ],
        "summary": "Time limits de um problema",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "problem",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          }
        }
      }
    },
    "/ops/updateproblemset": {
      "post": {
        "tags": [
          "ops"
        ],
        "summary": "Atualizar problemset nos juízes",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "repo": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok"
          }
        }
      }
    },
    "/index/status": {
      "get": {
        "tags": [
          "index"
        ],
        "summary": "Health público (fila, máquinas, daemons)",
        "responses": {
          "200": {
            "description": "{queue,judge:{online,total,busy,healthy,cpus_online,gpus_online},alert,daemons} — cache 20s; gpus_online = só GPU de compute comprovada (nvidia-smi/rocm-smi); daemons.judged = processo local ou heartbeat run/judged.alive (deploy em container)"
          }
        },
        "description": "judge.slots = capacidade total em SLOTS (multi-slot); judge.busy = slots ocupados."
      }
    },
    "/treino/contest-create/permission": {
      "get": {
        "tags": [
          "contest-create"
        ],
        "summary": "O usuário pode criar contest?",
        "responses": {
          "200": {
            "description": "{can_create,is_admin,reason,solved_count,threshold,in_allow,in_deny,allowed_modes}"
          }
        }
      }
    },
    "/treino/contest-create/problems": {
      "get": {
        "tags": [
          "contest-create"
        ],
        "summary": "Busca no banco público de problemas",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{problems:[{id,title,tags}],total,bank_total}"
          }
        }
      }
    },
    "/treino/contest-create/create": {
      "post": {
        "tags": [
          "contest-create"
        ],
        "summary": "Cria e publica o contest (no ar na hora)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "icpc",
                      "obi",
                      "treino",
                      "heuristic",
                      "outro"
                    ]
                  },
                  "start": {
                    "type": "integer"
                  },
                  "end": {
                    "type": "integer"
                  },
                  "priority": {
                    "type": "string",
                    "enum": [
                      "prova",
                      "lista-privada",
                      "lista-publica",
                      "super"
                    ],
                    "description": "prioridade no escalonador ('super' só admin do treino)"
                  },
                  "languages": {
                    "description": "array de ids canônicos (normalizado como o settings) ou string legada",
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "judges": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "pool de juízes do contest (hostnames do registro; vazio = qualquer juiz online) — vira CONTEST_JUDGES; escalonador ESTRITO"
                  },
                  "showcode": {
                    "type": "boolean"
                  },
                  "show_log": {
                    "type": "boolean"
                  },
                  "show_editor": {
                    "type": "boolean"
                  },
                  "show_tl": {
                    "type": "boolean"
                  },
                  "allow_backup": {
                    "type": "boolean"
                  },
                  "allow_print": {
                    "type": "boolean"
                  },
                  "score_anon": {
                    "type": "boolean"
                  },
                  "manual_verdict": {
                    "type": "boolean"
                  },
                  "allow_late": {
                    "type": "boolean"
                  },
                  "secret": {
                    "type": "boolean",
                    "description": "SUPER SECRETO: fora das listagens públicas; placar/visual exigem login no contest"
                  },
                  "login_ua_substring": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "score_full_users": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "penalty_minutes": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "placar ICPC: minutos por tentativa que conta antes do AC (default 20)"
                  },
                  "penalty_verdicts": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "wa",
                        "tle",
                        "mle",
                        "rte",
                        "ce"
                      ]
                    },
                    "description": "placar ICPC: verdicts que contam penalidade (default wa/tle/mle/rte; Judge Error e pendentes nunca contam; [] = nenhum penaliza)"
                  },
                  "locale": {
                    "type": "string",
                    "enum": [
                      "pt",
                      "en"
                    ]
                  },
                  "login_start": {
                    "type": "integer"
                  },
                  "login_enabled": {
                    "type": "boolean"
                  },
                  "freeze": {
                    "type": "integer"
                  },
                  "problems": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "description": "bank_id OU source+problem_id; name/letter; statement_b64/statement_file, statement_pdf_b64/statement_pdf_file; languages[] por problema (vira problem-langs.json)"
                    }
                  },
                  "allow_empty": {
                    "type": "boolean"
                  },
                  "admin": {
                    "type": "object",
                    "properties": {
                      "login": {
                        "type": "string"
                      },
                      "password": {
                        "type": "string"
                      },
                      "fullname": {
                        "type": "string"
                      }
                    }
                  },
                  "users_from": {
                    "type": "string",
                    "description": "id de contest cujos usuários são compartilhados (ex.: treino)"
                  },
                  "users": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "login": {
                          "type": "string"
                        },
                        "password": {
                          "type": "string"
                        },
                        "fullname": {
                          "type": "string"
                        },
                        "email": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "colors": {
                    "type": "object",
                    "description": "mapa letra->hex (sem #) + enableSonic?"
                  },
                  "regions": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "teams_meta": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "regex": {
                          "type": "string"
                        },
                        "country": {
                          "type": "string"
                        },
                        "school": {
                          "type": "string"
                        },
                        "school_full": {
                          "type": "string"
                        }
                      }
                    }
                  }
                },
                "required": [
                  "name",
                  "end",
                  "problems"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{contest_id,admin_login,admin_password,url,scoreboard_url}"
          },
          "403": {
            "description": "sem permissão"
          },
          "409": {
            "description": "id em uso/reservado"
          },
          "422": {
            "description": "validação"
          }
        }
      }
    },
    "/treino/contest-create/template": {
      "get": {
        "tags": [
          "contest-create"
        ],
        "summary": "Baixa template JSON do contest",
        "responses": {
          "200": {
            "description": "arquivo JSON (attachment)"
          }
        }
      }
    },
    "/treino/contest-create/import": {
      "post": {
        "tags": [
          "contest-create"
        ],
        "summary": "Importa contest de um .tar.gz",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tar_b64": {
                    "type": "string"
                  }
                },
                "required": [
                  "tar_b64"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "criado"
          },
          "400": {
            "description": "tar inválido"
          }
        }
      }
    },
    "/treino/admin/contest-perms": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "Lê permissões de criação de contest",
        "responses": {
          "200": {
            "description": "{perms:{threshold,allow[],deny[]}}"
          }
        }
      },
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Define permissões de criação de contest",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "threshold": {
                    "type": "integer"
                  },
                  "allow": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "deny": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{saved,perms}"
          }
        }
      }
    },
    "/treino/admin/contests": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "Lista contests criados pela interface",
        "responses": {
          "200": {
            "description": "{contests:[{id,name,owner,created_at,mode}],count}"
          }
        }
      }
    },
    "/treino/admin/contest-remove": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Move um contest criado pela interface p/ lixeira",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contest": {
                    "type": "string"
                  }
                },
                "required": [
                  "contest"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{removed,contest}"
          },
          "403": {
            "description": "não criado pela interface"
          }
        }
      }
    },
    "/treino/contest-create/tags": {
      "get": {
        "tags": [
          "contest-create"
        ],
        "summary": "Tags do banco com contagem",
        "responses": {
          "200": {
            "description": "{tags:[{tag,count}],total}"
          }
        }
      }
    },
    "/treino/contest-create/templates": {
      "get": {
        "tags": [
          "contest-create"
        ],
        "summary": "Templates nomeados do criador (?name= => 1; senão lista)",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{templates:[{name,created_at,updated_at,mode,duration,has_problems}]} ou {template:{name,spec}}"
          },
          "404": {
            "description": "template não encontrado"
          }
        }
      },
      "post": {
        "tags": [
          "contest-create"
        ],
        "summary": "save|delete|rename template (save: template{} do formulário OU from_contest; spec relativizado + whitelist — nunca guarda usuários/senhas/id)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "op": {
                    "type": "string",
                    "enum": [
                      "save",
                      "delete",
                      "rename"
                    ]
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "template": {
                    "type": "object"
                  },
                  "from_contest": {
                    "type": "string",
                    "description": "salvar a partir de contest existente (só dono/admin; senão 404)"
                  },
                  "include_problems": {
                    "type": "boolean"
                  },
                  "new_name": {
                    "type": "string"
                  }
                },
                "required": [
                  "op",
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{saved|deleted|renamed:true}"
          },
          "404": {
            "description": "template/from_contest não encontrado (não vaza existência)"
          }
        }
      }
    },
    "/treino/contest-create/export": {
      "get": {
        "tags": [
          "contest-create"
        ],
        "summary": "Exporta o spec JSON de um contest (round-trip com o create; nunca exporta passwd/users/senhas)",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "full_statements",
            "in": "query",
            "description": "1 = embute todos os enunciados; default embute só o material exclusivo do contest",
            "schema": {
              "type": "integer",
              "enum": [
                0,
                1
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "attachment <id>-spec.json"
          },
          "404": {
            "description": "contest inexistente, sem created-by, ou o login não é dono/admin"
          }
        }
      }
    },
    "/treino/contest-create/duplicate": {
      "post": {
        "tags": [
          "contest-create"
        ],
        "summary": "Cria contest novo a partir de existente (conf+problemas+visual; usuários/submissões nunca; datas relativas preservadas)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string"
                  },
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "start": {
                    "type": "integer"
                  },
                  "end": {
                    "type": "integer"
                  },
                  "admin": {
                    "type": "object"
                  },
                  "users": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "users_from": {
                    "type": "string"
                  }
                },
                "required": [
                  "from"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "mesmo retorno do create"
          },
          "404": {
            "description": "from inexistente/sem acesso (dono ou admin)"
          }
        }
      }
    },
    "/treino/contest-create/mine": {
      "get": {
        "tags": [
          "contest-create"
        ],
        "summary": "Contests criados pelo login (owner + created-by)",
        "responses": {
          "200": {
            "description": "{contests:[{id,name,mode,created_at,start,end,problems_count}],total}"
          }
        }
      }
    },
    "/treino/contest-create/collections": {
      "get": {
        "tags": [
          "contest-create"
        ],
        "summary": "Coleções do banco público com contagem (escopo do wizard; ≠ /problems/collections)",
        "responses": {
          "200": {
            "description": "{collections:[{collection,count}],total}"
          }
        }
      }
    },
    "/treino/contest-create/genpass": {
      "get": {
        "tags": [
          "contest-create"
        ],
        "summary": "N senhas legíveis",
        "parameters": [
          {
            "name": "n",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{passwords[]}"
          }
        }
      }
    },
    "/treino/contest-create/draw": {
      "get": {
        "tags": [
          "contest-create"
        ],
        "summary": "Sorteia problemas por tag, coleção e dificuldade (filtros em AND; reproduzível por seed)",
        "parameters": [
          {
            "name": "tags",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "collections",
            "in": "query",
            "description": "array JSON url-encoded de nomes de coleção (casa exato; inválido/ausente = sem filtro)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "count",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "match",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "any",
                "all"
              ]
            }
          },
          {
            "name": "difficulty",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "any",
                "easy",
                "medium",
                "hard",
                "known"
              ]
            }
          },
          {
            "name": "seed",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{problems[],candidates,drawn,seed}"
          }
        }
      }
    },
    "/contest/teams-meta": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Regras regex país/escola p/ o placar (bandeiras locais; fallback — o por-usuário /contest/teams vence)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{rules:[{regex,country,school,school_full}]}"
          }
        }
      }
    },
    "/contest/teams": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Diretório de TIMES por-usuário p/ o placar (do .team do account.json + assets; secreto exige sessão)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{teams:{<login>:{univ_short?,univ_full?,flag?,region?,has_logo,has_photo}}} — o NOME vem do TXT do placar (fullname); precedência: isto > teams-meta > vazio"
          }
        }
      }
    },
    "/contest/team-photo": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Foto do time (PNG, lado máx 1000; 📷 clicável no placar; secreto exige sessão)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "user",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "image/png (404 sem foto)"
          }
        }
      }
    },
    "/contest/team-logo": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Brasão do time (PNG, máx 128; célula do time no placar — vence o logo do teams-meta; secreto exige sessão)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "user",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "image/png (404 sem brasão)"
          }
        }
      }
    },
    "/contest/admin/teams": {
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Times por-usuário: set{login:{fullname?,univ_short?,univ_full?,country?,region?}} (fullname = nome do time, campo único; '' apaga os de .team) ou action:materialize (aplica teams-meta/regions aos campos VAZIOS); USERS_FROM → 409",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{saved,skipped[]} | {materialized,filled}"
          },
          "409": {
            "description": "shared_users (contest com usuários compartilhados)"
          }
        }
      }
    },
    "/contest/admin/team-assets": {
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Foto/brasão do time: {kind:photo|logo, filename:'<login>.<ext>', file_b64} (1 por POST; login case-insensitive) ou {action:delete,kind,login}; máx 8MB; USERS_FROM → 409",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{saved:true,login,kind} | {deleted:true}"
          },
          "404": {
            "description": "nenhum usuário casa com o nome do arquivo"
          }
        }
      }
    },
    "/contest/admin/config": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Lê config do contest (cores/regiões/teams-meta/basic)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{name,mode,letters,colors,regions,teams_meta,basic}"
          },
          "403": {
            "description": "não-admin do contest"
          }
        }
      },
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Grava config do contest",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "colors": {
                    "type": "object"
                  },
                  "regions": {
                    "type": "array"
                  },
                  "teams_meta": {
                    "type": "array"
                  },
                  "basic": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{saved:true}"
          }
        }
      }
    },
    "/contest/admin/users": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Usuários do contest (sem senha)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{users:[{login,fullname,email,admin}],shared}"
          }
        }
      }
    },
    "/contest/admin/user-add": {
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Adiciona/reseta usuário do contest (fullname = nome do time; univ_short/univ_full/country/region opcionais mesclam no .team)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "login": {
                    "type": "string"
                  },
                  "password": {
                    "type": "string"
                  },
                  "fullname": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "univ_short": {
                    "type": "string"
                  },
                  "univ_full": {
                    "type": "string"
                  },
                  "country": {
                    "type": "string"
                  },
                  "region": {
                    "type": "string"
                  }
                },
                "required": [
                  "login"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{user:{login,password,fullname,email}}"
          }
        }
      }
    },
    "/contest/admin/users-bulk": {
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Carga de usuários em lote (skip|update; conta privilegiada existente nunca é resetada; fullname = nome do time; univ_short/univ_full/country/region opcionais gravam o .team — carga única de credenciais+time)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "users": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "login": {
                          "type": "string"
                        },
                        "password": {
                          "type": "string"
                        },
                        "fullname": {
                          "type": "string"
                        },
                        "email": {
                          "type": "string"
                        },
                        "univ_short": {
                          "type": "string"
                        },
                        "univ_full": {
                          "type": "string"
                        },
                        "country": {
                          "type": "string"
                        },
                        "region": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "login"
                      ]
                    }
                  },
                  "on_existing": {
                    "type": "string",
                    "enum": [
                      "skip",
                      "update"
                    ]
                  }
                },
                "required": [
                  "users"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{created[],updated[],skipped:[{login,reason}],counts}"
          }
        }
      }
    },
    "/contest/admin/user-remove": {
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Remove usuário do contest",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "login": {
                    "type": "string"
                  }
                },
                "required": [
                  "login"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{removed:true}"
          },
          "409": {
            "description": "não pode remover a si mesmo"
          }
        }
      }
    },
    "/contest/admin/sessions": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Sessões ativas + alerta UA/IP",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{sessions,alerts}"
          }
        }
      }
    },
    "/contest/admin/audit-log": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Feed unificado de auditoria (admin + logins + submissões + veredictos)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{events:[{time,who,kind,action,details}],count} — filtros: since,action,user,limit"
          }
        }
      }
    },
    "/contest/admin/dashboard": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Situação ao vivo: juízes, fila e métricas de submissão/espera",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{judges:{online,busy,total,queue_depth,assigned,pool,list}, submissions:{total,pending,pending_list,max_wait_s,response:{avg_s,max_s,p50_s,p95_s},timeline}} (pool = hostnames de CONTEST_JUDGES; [] = sem pool)"
          }
        }
      }
    },
    "/contest/admin/access-log": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Log de acessos do contest",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{entries,alerts}"
          }
        }
      }
    },
    "/contest/admin/settings": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Configurações do contest (inclui mode read-only, judges[] = pool de juízes do contest, e a penalidade ICPC: penalty_minutes, penalty_verdicts; show_log é o valor EFETIVO — em modo icpc, SHOWLOG ausente = false)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "settings"
          }
        }
      },
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Atualiza configurações (auditado; judges[] = pool de juízes — hostnames do registro, vazio remove: escalonador ESTRITO, pool offline segura a fila; penalty_minutes int default 20, penalty_verdicts array de wa/tle/mle/rte/ce — editar em prova recomputa o placar; show_log:true grava SHOWLOG=1 explícito — religa o log mesmo em modo icpc)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{saved,changed}"
          }
        }
      }
    },
    "/contest/admin/bank": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Busca p/ adicionar problemas: banco público + privados do DONO do contest (mesmo sujeito do gate de add; privados primeiro). ?meta=1 devolve tags+coleções do banco público",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "collection",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "meta",
            "in": "query",
            "schema": {
              "type": "integer",
              "enum": [
                1
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{problems:[{id,title,tags,collections,access,private,has_statement}],total,mine,shared} ou {tags,collections}"
          }
        }
      }
    },
    "/contest/admin/draw": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Sorteio no banco público (contrato do /treino/contest-create/draw: tag+coleção+dificuldade em AND, seed reproduzível)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "tags",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "collections",
            "in": "query",
            "description": "array JSON url-encoded de nomes de coleção",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "count",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "match",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "any",
                "all"
              ]
            }
          },
          {
            "name": "difficulty",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "any",
                "easy",
                "medium",
                "hard",
                "known"
              ]
            }
          },
          {
            "name": "seed",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{problems[],candidates,drawn,seed,collections}"
          }
        }
      }
    },
    "/contest/admin/problems": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Lista problemas (inclui languages e judges por problema)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{problems}"
          }
        }
      },
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "add|remove|reorder|rename|langs|judges|statement (langs -> problem-langs.json; judges -> problem-judges.json, pool por problema, vazio herda o do contest; add de privado: só se o dono do contest é dono/colaborador — senão 404)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{problems}"
          },
          "404": {
            "description": "problema privado sem acesso (não vaza existência)"
          }
        }
      }
    },
    "/contest/statistics": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Estatísticas agregadas (admin/judge/mon)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{totals,problems,languages,verdicts,timeline}"
          }
        }
      }
    },
    "/contest/clarifications": {
      "get": {
        "tags": [
          "contest"
        ],
        "summary": "Clarifications (role-aware)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{clarifications,can_answer}"
          }
        }
      }
    },
    "/contest/clarification-ask": {
      "post": {
        "tags": [
          "contest"
        ],
        "summary": "Perguntar",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{asked,id}"
          }
        }
      }
    },
    "/contest/clarification-claim": {
      "post": {
        "tags": [
          "contest"
        ],
        "summary": "Reservar uma clarification p/ responder (admin/judge/mon; TTL 5min)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{claimed_by,expires_at} — body {id,action:claim|release}; 409 clar_claimed/already_answered"
          }
        }
      }
    },
    "/contest/clarification-broadcast": {
      "post": {
        "tags": [
          "contest"
        ],
        "summary": "Aviso oficial: Q+A público com autor oculto (admin/judge/mon)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{broadcast,id} — body {problem?,question,answer}"
          }
        }
      }
    },
    "/contest/clarification-answer": {
      "post": {
        "tags": [
          "contest"
        ],
        "summary": "Responder (admin/judge/mon; já respondida só chief/admin edita)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{answered}"
          }
        }
      }
    },
    "/contest/admin/news": {
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Notícias do contest (admin/judge/mon)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{items}"
          }
        }
      }
    },
    "/contest/admin/jplag-run": {
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Dispara jplag (background)",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{started}"
          }
        }
      }
    },
    "/contest/admin/jplag-results": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Status + pares de similaridade",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{status,results}"
          }
        }
      }
    },
    "/contest/admin/jplag-match": {
      "get": {
        "tags": [
          "contest-admin"
        ],
        "summary": "HTML lado-a-lado",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "text/html"
          }
        }
      }
    },
    "/contest/admin/logout-user": {
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Encerra sessões de um usuário",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "login": {
                    "type": "string"
                  }
                },
                "required": [
                  "login"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{logged_out,sessions_removed}"
          }
        }
      }
    },
    "/contest/admin/user-disable": {
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Desabilita (bloqueia) um usuário",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "login": {
                    "type": "string"
                  }
                },
                "required": [
                  "login"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{disabled}"
          }
        }
      }
    },
    "/contest/admin/users-set-password": {
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Senha única para todos os não-privilegiados",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "password": {
                    "type": "string"
                  },
                  "include_disabled": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "password"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{updated,count}"
          }
        }
      }
    },
    "/contest/admin/logout-mismatch": {
      "post": {
        "tags": [
          "contest-admin"
        ],
        "summary": "Desloga sessões com UA divergente",
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{logged_out,sessions_removed}"
          }
        }
      }
    },
    "/problems/mine": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Meus problemas (dono/autor)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{problems:[{id,title,author,owner,collections,public,html,claimed}]}"
          }
        }
      }
    },
    "/problems/shared": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Problemas onde sou colaborador (não dono)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{problems:[…]}"
          }
        }
      }
    },
    "/problems/public": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Problemas públicos (visão de gestão dono/autor)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{problems:[…]}"
          }
        }
      }
    },
    "/problems/collection": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Problemas de uma coleção (curso/diretório)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "name",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{problems:[…]}"
          }
        }
      }
    },
    "/problems/collections": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Coleções com contagem total/pública",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{collections:[{name,count,public}]}"
          }
        }
      }
    },
    "/problems/get": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Detalhe do problema (índice + validação)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "índice + validation (relatório do portão) + statement_html_b64/tags/time_limits"
          },
          "404": {
            "description": "não encontrado"
          }
        }
      }
    },
    "/problems/validation": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Último relatório de validação (portão de qualidade)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{checks:[{name,ok,detail}],html_built,render_warnings,ok} — checks incluem secao_entrada/secao_saida; render_warnings pode trazer 'exemplo-no-texto?'"
          }
        }
      }
    },
    "/problems/status": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Painel de status dos problemas do login (dono + colaborador)",
        "description": "Agrega validação, calibração, time_limits e 'precisa recalibrar' dos problemas de que o login é dono OU colaborador. FRONTEIRA de acesso: problema privado de terceiro NÃO aparece (owners_visible). Sem hash de pacote por request — stale vem do checksum carimbado no índice (≤30 min de atraso); use /problems/tl p/ o valor exato ao vivo.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{total,counts:{validated,validation_error,unvalidated,calibrated,uncalibrated,being_calibrated,needs_recalibration,good_sol_no_tl,public_unvalidated,needs_review,errors},calibrating_ids,attention_ids,problems:[{id,title,owner,author,public,collaborators,validated,calibrated,being_calibrated,stale,needs_recalibration,good_sol_no_tl,good_sol_missing_langs,public_unvalidated,error,needs_review,review_reasons,time_limits,updated_at,validated_at}]} — good_sol_no_tl = solução good sem TL (linguagem que falhou em todos os juízes); needs_review = precisa revisão"
          }
        }
      }
    },
    "/problems/tl": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Time limits AO VIVO + staleness exata de 1 problema",
        "description": "Recomputa o checksum do pacote agora (1 problema) e devolve o TL servível + stale/needs_recalibration exatos. Acesso: dono/colaborador OU público (require_problem_view; 404 senão). Versão não-admin do /ops/problemtl.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{problem,checksum,time_limits,calibrated_checksum,hosts,updated_at,calibrated,stale,needs_recalibration}"
          },
          "404": {
            "description": "privado e o login não é dono/colaborador (not_found)"
          }
        }
      }
    },
    "/problems/my-stats": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Análise dos problemas do login, agregada em toda a plataforma",
        "description": "Panorama (cache precomputado) dos problemas que o login é dono/colaborador, somando submissões de TODOS os contests (treino + turmas). Reconcilia o namespace do history (problemas-apc# etc.) com o índice de donos via `collections`. FRONTEIRA: só os problemas do login. PRIVACIDADE: só agregados — sem logins, sem nomes de contests (só contests_count).",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{totals:{owned,with_activity,attempts,accepts,solvers}, overall_verdicts:[{verdict,count}], overall_languages:[{lang,submissions,accepted}], most_popular:{id,title,attempts}, problems:[{id,title,attempts,accepts,wrong,acceptance_rate,distinct_users,solvers,contests_count,verdicts,languages,first,last}]}"
          }
        }
      }
    },
    "/problems/calib": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Resumo de calibração por juiz (TL, log, reports)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{id,checksum,good_langs,missing_langs,hosts:[{host,tl,missing,at,log,reports}]} — missing_langs = linguagem good sem TL em NENHUM host (solução good falhou em todas as máquinas); hosts[].missing = faltantes naquele juiz"
          }
        }
      }
    },
    "/problems/calib-report": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "report.html de uma solução naquele juiz (calibração)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "host",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "text/html do report da solução",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/problems/judges": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Juízes do registro (p/ calibração direcionada)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{judges:[{host,cpu,langs,online}]}"
          }
        }
      }
    },
    "/problems/validate": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "Portão de qualidade: valida + indexa + pede calibração (NÃO publica)",
        "description": "Roda o validador estático (HTML compila + seções `## Entrada`/`## Saída` + exemplos pareados) + gera o índice, e pede calibração a um juiz (roda as `good`, reporta o TL). **NÃO altera o flag `public`**: problema privado continua privado — publicar é `/problems/set-public`, que é quem checa a trava `public_allowed` da ORG. Relatório em `/problems/validation`.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  }
                },
                "required": [
                  "id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{reqid,...}"
          }
        }
      }
    },
    "/problems/publish": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "DEPRECADO: alias de /problems/validate",
        "description": "Alias deprecado de `/problems/validate` (o nome fazia parecer que validar publicava).",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  }
                },
                "required": [
                  "id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{reqid,...}"
          }
        },
        "deprecated": true
      }
    },
    "/problems/request-calibration": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "Enfileira calibração (juiz roda calibreitor.sh, reporta TL)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "hosts": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Calibração direcionada a juízes específicos (opcional)."
                  }
                },
                "required": [
                  "id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{reqid,...}"
          }
        }
      }
    },
    "/problems/repos": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Diretórios do autor (dono/colaborador)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "responses": {
          "200": {
            "description": "{repos:[{repo,owner,collaborators,collections,mine}]}"
          }
        }
      }
    },
    "/problems/repo-create": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "Cria diretório (org no namespace do login)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "repo": {
                    "type": "string"
                  },
                  "collections": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "repo"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{repo,owner}"
          }
        }
      }
    },
    "/problems/source": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Source do pacote inteiro (só dono/colaborador)",
        "description": "Source completo: enunciado + testes OCULTOS + SOLUÇÕES + editorial. Acesso garantido NA API: só dono/colaborador; não-autorizado recebe 404 (sem read-only, sem atalho de .admin). Inclui title, collections, languages (ids de submissão permitidos, [] = todas), examples[].explanation, sols por categoria, score, score_text (tests/score cru), editorial_md, scripts (caminhos, árvore do editor) e scripts_files (round-trip da correção especial: [{path,content_b64,exec}|{path,symlink}]).",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "source + {editable}",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ProblemFields"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "editable": {
                          "type": "boolean"
                        },
                        "scripts": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "readOnly": true,
                          "description": "Caminhos relativos de scripts/ (correção especial) presentes no pacote — SÓ leitura/exibição (não escrito por create/edit). Ex.: [\"compare.sh\",\"c/compile.sh\"]."
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/problems/preview": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "Pré-visualização HTML do enunciado",
        "description": "Usa o renderizador único (render-statement.sh) — idêntico ao HTML servido ao aluno. Injeta o título (h1) e os exemplos (cada um com explanation opcional).",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enunciado_md": {
                    "type": "string"
                  },
                  "enunciado_format": {
                    "type": "string",
                    "enum": [
                      "md",
                      "org",
                      "tex"
                    ]
                  },
                  "title": {
                    "type": "string"
                  },
                  "examples": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/ProblemExample"
                    }
                  }
                },
                "required": [
                  "enunciado_md"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{success:true, html_b64}"
          }
        }
      }
    },
    "/problems/download": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Baixa o pacote .tar.gz (inclui soluções → exige escrita/admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "stream binário .tar.gz",
            "content": {
              "application/gzip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    },
    "/problems/upload": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "Sobe um pacote (.tar/.gz/.bz2/.zst/.zip) e substitui tudo",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "repo": {
                    "type": "string"
                  },
                  "prob": {
                    "type": "string"
                  },
                  "tar_b64": {
                    "type": "string"
                  }
                },
                "required": [
                  "tar_b64"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{id,sha}"
          }
        }
      }
    },
    "/problems/export": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Exporta como pacote ICPC/Kattis (.tar.gz)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "stream binário .tar.gz (problem.yaml+statement+data+submissions)",
            "content": {
              "application/gzip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    },
    "/problems/import": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "Importa pacote ICPC/Kattis → problema MOJ julgável",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "repo": {
                    "type": "string"
                  },
                  "prob": {
                    "type": "string"
                  },
                  "tar_b64": {
                    "type": "string"
                  }
                },
                "required": [
                  "repo",
                  "tar_b64"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{id,sha}"
          }
        }
      }
    },
    "/problems/create": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "Cria problema novo (commit+push)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ProblemFields"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "repo": {
                        "type": "string"
                      },
                      "prob": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "repo",
                      "prob"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{id,sha}"
          }
        }
      }
    },
    "/problems/edit": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "Edita problema (só os campos presentes; commit+push autorado). Aceita languages (ids de submissão permitidos; ausente = não toca, [] = todas), scripts_files (SUBSTITUI scripts/ inteiro: paths validados sem '..', exec vira +x, symlink confinado a scripts/; ausente = não toca) e score_text (tests/score verbatim; '' remove)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ProblemFields"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "id"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{id,sha}"
          }
        }
      }
    },
    "/problems/delete": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "REMOVE um problema (git rm + push) e do treino — destrutivo; confirm tem de repetir o id",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "confirm": {
                    "type": "string",
                    "description": "Confirmação por escrito: tem de ser EXATAMENTE igual a id."
                  }
                },
                "required": [
                  "id",
                  "confirm"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{action:'delete', id}"
          },
          "400": {
            "description": "confirm_mismatch (confirm != id) ou id inválido"
          },
          "403": {
            "description": "sem permissão de escrita"
          }
        }
      }
    },
    "/problems/set-public": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "Público on => valida + calibra (index_problem_bg + cal_request) e grava o flag; off => sai do treino",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "public": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "id",
                  "public"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{id,public,reqid?}"
          }
        }
      }
    },
    "/problems/set-collections": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "Define coleções no .moj-meta.json",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "collections": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "id",
                  "collections"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{id,collections}"
          }
        }
      }
    },
    "/problems/repo-collaborators": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Lista colaboradores do diretório",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          },
          {
            "name": "repo",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{repo,collaborators}"
          }
        }
      },
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "Compartilha o diretório (só o dono gerencia)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/contest"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "repo": {
                    "type": "string"
                  },
                  "add": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "remove": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "repo"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{repo,collaborators}"
          }
        }
      }
    },
    "/problems/move": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "Move um problema de rascunho p/ outra org (muda o id; bloqueia se público/em uso)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id",
                  "to_org"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "to_org": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{id, from}"
          },
          "403": {
            "description": "não é membro das orgs"
          },
          "409": {
            "description": "público/em uso, ou nome colide no destino"
          }
        }
      }
    },
    "/orgs/list": {
      "get": {
        "tags": [
          "orgs"
        ],
        "summary": "Orgs de que o login é membro (inclui a implícita)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{orgs:[{name,title,members,admins,public_allowed,implicit,count,public,mine,can_manage}]}"
          }
        }
      }
    },
    "/orgs/get": {
      "get": {
        "tags": [
          "orgs"
        ],
        "summary": "Detalhe de 1 org (404 se não-membro)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{name,title,members,admins,public_allowed,implicit,mine,can_manage}"
          },
          "404": {
            "description": "não-membro ou inexistente"
          }
        }
      }
    },
    "/orgs/create": {
      "post": {
        "tags": [
          "orgs"
        ],
        "summary": "Cria org (criador vira membro+admin; exige cc_can_create)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "members": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "admins": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "title": {
                    "type": "string"
                  },
                  "public_allowed": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "org criada"
          },
          "403": {
            "description": "sem permissão de criação"
          }
        }
      }
    },
    "/orgs/members": {
      "get": {
        "tags": [
          "orgs"
        ],
        "summary": "Membros/admins da org",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{name,members,admins,mine,can_manage,implicit}"
          }
        }
      },
      "post": {
        "tags": [
          "orgs"
        ],
        "summary": "Gerencia membros/admins (só admin da org)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "add": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "remove": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "admins_add": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "admins_remove": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "idem"
          },
          "403": {
            "description": "não é admin da org"
          }
        }
      }
    },
    "/orgs/set-public-allowed": {
      "post": {
        "tags": [
          "orgs"
        ],
        "summary": "Liga/desliga a trava de público (só admin; implícita 409)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "public_allowed"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "public_allowed": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "flag alterada"
          },
          "403": {
            "description": "não é admin da org"
          },
          "409": {
            "description": "org implícita é sempre privada"
          }
        }
      }
    },
    "/orgs/delete": {
      "post": {
        "tags": [
          "orgs"
        ],
        "summary": "Remove uma org VAZIA (só admin; implícita/não-vazia 409)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{action:org-delete, name}"
          },
          "403": {
            "description": "não é admin da org"
          },
          "404": {
            "description": "org não encontrada"
          },
          "409": {
            "description": "org implícita (implicit_org) ou com problemas (org_not_empty)"
          }
        }
      }
    },
    "/problems/collection-create": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "Cria coleção (TAG curada; nome é texto livre, pode ter espaços)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{name,owner}"
          },
          "403": {
            "description": "sem permissão de criação"
          }
        }
      }
    },
    "/problems/collection-rename": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "Renomeia a coleção (registro + a tag em todos os problemas; só dono/admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "to": {
                    "type": "string"
                  }
                },
                "required": [
                  "name",
                  "to"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{name,from,retagged}"
          },
          "403": {
            "description": "não é dono da coleção"
          }
        }
      }
    },
    "/problems/collection-delete": {
      "post": {
        "tags": [
          "problems"
        ],
        "summary": "Exclui a coleção (tira a tag de todos + remove do registro; só dono/admin)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{name,untagged}"
          },
          "403": {
            "description": "não é dono da coleção"
          }
        }
      }
    },
    "/treino/signup/start": {
      "post": {
        "tags": [
          "treino"
        ],
        "summary": "Inicia cadastro web-first (gera nonce+deep-link)",
        "responses": {
          "200": {
            "description": "{nonce,deep_link,expires_at}"
          },
          "409": {
            "description": "login_taken"
          }
        }
      }
    },
    "/treino/signup/status": {
      "get": {
        "tags": [
          "treino"
        ],
        "summary": "Polling do cadastro (pending|created|already_linked|linked|expired)",
        "parameters": [
          {
            "name": "nonce",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{status,login?} (sem senha)"
          }
        }
      }
    },
    "/treino/signup/verify": {
      "post": {
        "tags": [
          "treino"
        ],
        "summary": "Bot: consome nonce, cria+vincula/linka (bot-token)",
        "responses": {
          "200": {
            "description": "{status,login,password?}"
          },
          "401": {
            "description": "bot_unauth"
          },
          "410": {
            "description": "nonce_expired"
          }
        }
      }
    },
    "/treino/signup/telegram": {
      "post": {
        "tags": [
          "treino"
        ],
        "summary": "Bot: /participar — cria+vincula por telegram_id (bot-token)",
        "responses": {
          "200": {
            "description": "{status,login,password?}"
          }
        }
      }
    },
    "/treino/recover-password": {
      "post": {
        "tags": [
          "treino"
        ],
        "summary": "Bot: recupera senha pelo vínculo Telegram (bot-token)",
        "responses": {
          "200": {
            "description": "{status:ok|not_linked,login?,password?}"
          }
        }
      }
    },
    "/treino/telegram/link-start": {
      "post": {
        "tags": [
          "treino"
        ],
        "summary": "Conta logada gera nonce p/ vincular o próprio Telegram",
        "responses": {
          "200": {
            "description": "{nonce,deep_link,expires_at}"
          }
        }
      }
    },
    "/ops/alerts": {
      "get": {
        "tags": [
          "ops"
        ],
        "summary": "Bot: avalia incidentes + drena outbox de alertas (bot-token)",
        "responses": {
          "200": {
            "description": "{items:[{id,text,chats:[chat_id]}]}"
          },
          "401": {
            "description": "bot_unauth"
          }
        }
      }
    },
    "/problems/script-templates": {
      "get": {
        "tags": [
          "problems"
        ],
        "summary": "Templates de corretor especial (scripts/) p/ o editor web",
        "description": "Lê mojtools/script-templates/<key>/ (criar template = criar uma pasta lá). files[] tem o MESMO shape do campo scripts_files de source/edit: {path,content_b64,exec} ou {path,symlink}. Symlink externo (canônicos do mojtools) vem resolvido como conteúdo; interno (ex.: cpp -> c) vem como symlink. Iniciais: checker-testlib, interativo, interativo-rank, compare-float, ban-funcoes-c.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "templates": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          },
                          "conf_hints": {
                            "type": "string"
                          },
                          "files": {
                            "type": "array",
                            "items": {
                              "type": "object"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/ops/judge-config": {
      "get": {
        "tags": [
          "ops"
        ],
        "summary": "Config fina por juiz (multi-slot): particionamento/reserve/disabled",
        "description": "Admin. Config DESEJADA por host (contests/treino/var/judges-config.json); o heartbeat entrega ao agente quando muda e ele aplica após drenar os jobs. ?host= filtra.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "host",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{success, configs:{<host>:{partition,reserve,disabled,updated_at,by}}}"
          }
        }
      },
      "post": {
        "tags": [
          "ops"
        ],
        "summary": "Define a config de um juiz",
        "description": "Body {host, partition?: off|numa|cpus:<X>, reserve?: int, disabled?: bool}. partition particiona a máquina em SLOTS de cpus disjuntas (o agente corrige N problemas ao mesmo tempo, cada job pinado no seu cpuset); reserve tira as N primeiras cpus dos slots; disabled drena e para de receber trabalho.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{success, action, host, config}"
          }
        }
      }
    },
    "/ops/judge-results": {
      "get": {
        "tags": [
          "ops"
        ],
        "summary": "Relatório de correções por juiz (últimas N + agregado por host)",
        "description": "Admin. Varre run/results (cada correção carrega o host que julgou). ?host= filtra; ?limit= (<=500, default 50).",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "host",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{success, results:[{id,host,verdict,login,problem_id,lang,duration_s,finalized_at,contest,score}], by_host:{<h>:{total,accepted,judge_errors,avg_duration,last_at}}}"
          }
        }
      }
    }
  }
}