uVersion
Português
Baixar →

Wiki

REST API

Referência completa dos endpoints HTTP do servidor uVersion: auth, repos, files, locks, comments, watchlist, modification requests, builds, admin.

O servidor uVersion expõe uma API HTTP JSON autenticada por JWT. Esta página documenta a totalidade dos endpoints usados pelo cliente desktop, pelos plugins de editor e pela CLI uversion. Você pode chamá-los diretamente para integrar o uVersion ao seu tooling interno (dashboard personalizado, scripts de auditoria, webhooks, etc.).

Convenções

URL base

Todos os caminhos documentados são relativos à URL da sua instância. Os exemplos usam https://uversion.mygamestudio.com. Substitua pela sua.

Cabeçalhos obrigatórios

CabeçalhoValor
AuthorizationBearer <jwt> em todas as rotas /api/* exceto /api/auth/* e /api/server-info (e /health)
Content-Typeapplication/json para os POST/PUT com body JSON. application/octet-stream para os uploads binários (build files).
Acceptapplication/json recomendado (o servidor retorna JSON por padrão)

Formato de resposta uniforme

Todas as rotas respondem com o seguinte envelope:

{
  "success": true,
  "data": { /* payload */ }
}

Em caso de erro:

{
  "success": false,
  "error": "Permission denied: capability 'manage_users' required"
}

O campo success está sempre presente e indica se a requisição teve êxito. O campo data está presente em caso de sucesso. O campo error está presente em caso de falha. Nunca ambos juntos.

Paginação

As rotas que retornam listas potencialmente longas aceitam limit e offset como query params (?limit=20&offset=40). A resposta inclui total e has_more para facilitar a iteração. Limit máximo: 100.

Rate limiting

O rate limiting global foi removido (ver CLAUDE.md, seção Security). Apenas /api/auth/login tem rate limiting: 5 tentativas malsucedidas por username a cada 15 minutos. As tentativas válidas não contam.

Versionamento de tokens

Cada JWT contém um campo tv (token version) que corresponde a users.token_version no DB. O middleware de auth verifica esse valor a cada requisição. Quando o usuário faz POST /api/auth/logout, quando um admin reseta a senha dele, ou quando um admin desativa a conta dele, o token_version é incrementado, invalidando instantaneamente todos os tokens existentes desse usuário em todas as máquinas dele.

Curl

Para chamar a API a partir de um terminal:

TOKEN=$(curl -s -X POST https://uversion.mygamestudio.com/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"secret"}' | jq -r '.data.token')

curl -H "Authorization: Bearer $TOKEN" \
  https://uversion.mygamestudio.com/api/repositories

Autenticação

POST /api/auth/login

Autentica um usuário e retorna um JWT de 30 dias.

Body:

{
  "username": "alice",
  "password": "secret"
}

Response 200:

{
  "success": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "expires_at": "2026-06-13T14:30:00Z",
    "user": {
      "id": 12,
      "username": "alice",
      "email": "alice@mygamestudio.com",
      "role": "lead",
      "is_active": true
    }
  }
}

Erros:

  • 401: credenciais inválidas ou usuário inativo
  • 429: 5 tentativas malsucedidas atingidas para este username na janela de 15 min

O servidor executa sempre argon2id::verify_password (contra um hash fictício se o usuário não existir) para neutralizar os ataques de temporização. A resposta leva o mesmo tempo existindo ou não o usuário.

POST /api/auth/refresh

Renova o JWT sem passar novamente pela senha.

Cabeçalhos: Authorization: Bearer <current_token>. Sem body.

Response 200: idêntica a /login com uma expiração adiada em 30 dias e um novo token.

Erros:

  • 401: token atual inválido, expirado, ou token_version incompatível

POST /api/auth/logout

Invalida todos os tokens do usuário atual (em todas as máquinas dele) incrementando users.token_version. O usuário terá que fazer login novamente em todos os lugares.

Response 200: { "success": true }.

POST /api/auth/validate

Verifica se o token ainda é válido. Para os tokens a menos de 7 dias de expirar, o servidor inclui um refreshed_token na resposta (auto-extensão). O cliente deve persisti-lo no lugar do antigo.

Response 200:

{
  "success": true,
  "data": {
    "valid": true,
    "user_id": 12,
    "expires_at": "2026-06-13T14:30:00Z",
    "refreshed_token": "eyJhbGciOiJIUzI1NiIs..."
  }
}

POST /api/auth/register

Cria uma conta de usuário (rota pública, sem autenticação).

POST /api/auth/change-password

Altera a senha do usuário atual. Incrementa token_version, o que invalida todos os tokens antigos.

Repositórios

GET /api/repositories

Lista os repositórios acessíveis pelo usuário atual (filtrados via a tabela de permissões). Um usuário que não tem nenhum padrão de permissão sobre um repo não o vê.

Query params: limit, offset.

Response 200:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "name": "hero-rpg",
      "owner": "acme",
      "description": "Main RPG project",
      "current_revision": "7f3a9b1c2d...",
      "file_count": 8432,
      "size_bytes": 14211938405,
      "created_at": "2026-01-15T10:00:00Z"
    },
    {
      "id": 2,
      "name": "shared-assets",
      "owner": "acme",
      "description": "Shared asset library",
      "current_revision": "3a4b5c6d...",
      "file_count": 412,
      "size_bytes": 980000000,
      "created_at": "2026-02-01T09:00:00Z"
    }
  ]
}

GET /api/repositories/{repo_id}

Detalhe de um repositório, incluindo as estatísticas agregadas.

Response 200:

{
  "success": true,
  "data": {
    "id": 1,
    "name": "hero-rpg",
    "owner": "acme",
    "description": "Main RPG project",
    "current_revision": "7f3a9b1c2d...",
    "file_count": 8432,
    "size_bytes": 14211938405,
    "dedup_ratio": 4.2,
    "created_at": "2026-01-15T10:00:00Z",
    "last_commit_at": "2026-05-15T08:30:00Z"
  }
}

Erros:

  • 403: sem acesso a este repo
  • 404: repo inexistente

POST /api/repositories

Cria um novo repositório. Capability create_repos obrigatória (admin por padrão).

Body:

{
  "name": "new-project",
  "description": "Optional description"
}

Validação:

  • name: 1 a 64 caracteres, alfanumérico + hífens + underscores. Único por owner.
  • description: 0 a 1024 caracteres. Opcional.

Erros:

  • 400: nome inválido ou longo demais
  • 403: capability create_repos ausente
  • 409: já existe um repo com este nome para este owner

Arquivos

POST /api/files/{repo_id}/upload-chunks

Faz upload de um batch de chunks binários. Os chunks idênticos (por SHA-256) são deduplicados automaticamente. O servidor não re-armazena um chunk já presente. A resposta retorna para cada chunk seu hash + tamanho + tamanho comprimido, para usar no POST /commit seguinte.

Body:

{
  "chunks": [
    { "data": "<base64-encoded chunk bytes>" },
    { "data": "<base64-encoded chunk bytes>" }
  ]
}

Response 200:

{
  "success": true,
  "data": {
    "chunks": [
      { "hash": "abc123...", "size": 1048576, "compressed_size": 423152 },
      { "hash": "def456...", "size": 2097152, "compressed_size": 891204 }
    ]
  }
}

Limites: body máx. 1 GB (configurável via security.max_body_size_files). Para arquivos maiores, dividir em vários batches.

POST /api/files/{repo_id}/commit

Cria um commit atômico com uma lista de arquivos e seus chunks. Ou todos os arquivos passam, ou nenhum.

Body:

{
  "message": "Updated main level + hero pose pass",
  "files": [
    {
      "path": "Content/Maps/MainLevel.umap",
      "action": "modified",
      "chunks": ["abc123...", "def456..."]
    },
    {
      "path": "Content/Characters/NewVillain.uasset",
      "action": "added",
      "chunks": ["fed789..."]
    },
    {
      "path": "Content/OldAsset.uasset",
      "action": "deleted"
    }
  ]
}

Ações possíveis: added, modified, deleted. Para added e modified, o array chunks contém os hashes retornados por /upload-chunks. Para deleted, omitir chunks.

Response 200:

{
  "success": true,
  "data": {
    "commit_hash": "7f3a9b1c2d3e4f...",
    "files_changed": 3,
    "bytes_uploaded": 88080384,
    "bytes_deduped": 4194304,
    "revision": 47
  }
}

Erros:

  • 400: mensagem vazia, arquivo sem ação, chunks referenciados inexistentes
  • 403: sem a capability checkin, ou sem permissão write sobre um dos arquivos
  • 409: bloqueio já mantido por outro usuário sobre um arquivo modificado, ou commit concorrente (race condition sobre o mesmo arquivo)

GET /api/files/{repo_id}/snapshot

Retorna o estado completo do repositório na revisão atual: todos os arquivos, suas revisões e seus chunks. Usado pelo cliente para as operações de clone e sync forçado.

Query params:

  • revision (opcional): snapshot em uma revisão passada. Padrão: HEAD.

Response 200:

{
  "success": true,
  "data": {
    "revision": 47,
    "files": [
      {
        "path": "Content/Maps/MainLevel.umap",
        "revision": 12,
        "size_bytes": 84934656,
        "chunks": ["abc123...", "def456..."]
      }
    ]
  }
}

GET /api/files/{repo_id}/content

Faz download do conteúdo de um arquivo em uma revisão dada (o servidor remonta os chunks). Usado pelo clone e pelo sync.

Query params:

  • path: caminho do arquivo (relativo à raiz do repo)
  • revision (opcional): número de revisão. Padrão: a última.

Response 200: o conteúdo binário do arquivo.

GET /api/files/{repo_id}/history

Histórico paginado dos commits do repositório.

Query params:

  • limit (1 a 100, padrão 20)
  • offset (padrão 0)
  • path (opcional): filtra por caminho de arquivo (commits que modificaram este arquivo)
  • author (opcional): filtra por username
  • since (opcional): ISO 8601 timestamp

Response 200:

{
  "success": true,
  "data": {
    "commits": [
      {
        "hash": "7f3a9b1c...",
        "author": "alice",
        "author_id": 12,
        "date": "2026-05-15T08:30:00Z",
        "message": "Fixed lighting in main level",
        "files_changed": 1,
        "bytes_uploaded": 84934656
      }
    ],
    "total": 423,
    "limit": 20,
    "offset": 0,
    "has_more": true
  }
}

Sync incremental

Endpoints usados pelo cliente desktop para sincronizar de forma eficiente:

  • GET /api/files/{repo_id}/sync: arquivos alterados desde uma revisão, para um sync delta.
  • GET /api/files/{repo_id}/deletions: arquivos removidos no lado do servidor, para propagar as remoções localmente.
  • GET /api/files/{repo_id}/list: lista dos arquivos do repo.
  • POST /api/files/{repo_id}/checkout: adquire os bloqueios e prepara a edição.

Bloqueios

POST /api/locks/{repo_id}/acquire

Adquire bloqueios sobre uma lista de caminhos. As aquisições são independentes: a lista acquired contém os sucessos, a lista failed contém as falhas com o motivo.

Body:

{
  "paths": [
    "Content/Maps/MainLevel.umap",
    "Content/Characters/Hero.uasset"
  ]
}

Response 200:

{
  "success": true,
  "data": {
    "acquired": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "file_id": 12345,
        "file_path": "Content/Maps/MainLevel.umap",
        "user_id": 12,
        "username": "alice",
        "acquired_at": "2026-05-15T14:30:00Z",
        "last_heartbeat_at": "2026-05-15T14:30:00Z",
        "expires_at": "2026-05-15T15:30:00Z"
      }
    ],
    "failed": [
      {
        "path": "Content/Characters/Hero.uasset",
        "reason": "ALREADY_LOCKED",
        "lock_holder": "bob",
        "lock_acquired_at": "2026-05-15T13:00:00Z"
      }
    ]
  }
}

Os bloqueios expiram após 60 minutos sem heartbeat. O campo expires_at reflete esse prazo. Chame /heartbeat periodicamente para renovar o bloqueio, ou /release para liberá-lo.

Motivos de failed:

  • ALREADY_LOCKED: outro usuário mantém o bloqueio (os campos lock_holder e lock_acquired_at são preenchidos)
  • PERMISSION_DENIED: sem permissão write sobre este caminho
  • INVALID_PATH: caminho malformado (absoluto, contém .., etc.)

POST /api/locks/{repo_id}/release

Libera bloqueios que você possui. Body:

{
  "paths": ["Content/Maps/MainLevel.umap"],
  "force": false
}

force: true requer a capability force_unlock. A ação é auditada.

POST /api/locks/{repo_id}/heartbeat

Atualiza last_heartbeat_at nos seus bloqueios. Recomendado a cada 5 minutos para os jobs de CI de longa duração que querem que os admins vejam a atividade.

GET /api/locks/{repo_id}/status

Lista todos os bloqueios ativos do repositório, com joins sobre users e files para retornar diretamente os nomes.

Query params: user (filtra por username), limit, offset.

Response 200:

{
  "success": true,
  "data": {
    "locks": [
      {
        "file_path": "Content/Maps/MainLevel.umap",
        "user_id": 12,
        "username": "alice",
        "acquired_at": "2026-05-15T14:30:00Z",
        "last_heartbeat_at": "2026-05-15T16:00:00Z"
      }
    ],
    "total": 1
  }
}

Comentários e revisões

GET /api/comments/{repo_id}/comments?commit_hash=<hash>

Lista os comentários de um commit, em threads (pai → filhos).

Response 200:

{
  "success": true,
  "data": [
    {
      "id": 42,
      "commit_hash": "7f3a9b1c...",
      "file_path": "Content/Maps/MainLevel.umap",
      "author_id": 12,
      "author_username": "alice",
      "body": "LGTM, ship it",
      "parent_id": null,
      "created_at": "2026-05-15T15:00:00Z",
      "replies": [
        {
          "id": 43,
          "author_username": "bob",
          "body": "Thanks!",
          "parent_id": 42,
          "created_at": "2026-05-15T15:05:00Z"
        }
      ]
    }
  ]
}

POST /api/comments/{repo_id}/comments

Cria um comentário. file_path é opcional (comentário de commit vs. de arquivo). parent_id para responder a uma thread existente.

Body:

{
  "commit_hash": "7f3a9b1c...",
  "file_path": "Content/Maps/MainLevel.umap",
  "body": "LGTM, ship it",
  "parent_id": null
}

POST /api/comments/{repo_id}/reviews

Envia uma revisão sobre um commit. Capability approve_changes obrigatória.

Body:

{
  "commit_hash": "7f3a9b1c...",
  "status": "approved",
  "comment": "Lighting looks great, approving"
}

Status aceito: approved, changes_requested, pending.

Lista de observação

GET /api/watchlist/{repo_id}/watchlist

Lista os padrões de watch do usuário atual neste repositório.

Response 200:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "user_id": 12,
      "pattern": "Content/Characters/Hero/**",
      "notify_on": ["commit", "lock"],
      "created_at": "2026-04-10T10:00:00Z"
    }
  ]
}

POST /api/watchlist/{repo_id}/watchlist

Adiciona um padrão de watch. Body:

{
  "pattern": "Content/Characters/Hero/**",
  "notify_on": ["commit", "lock"]
}

Os eventos possíveis: commit (um commit modificou um arquivo correspondente), lock (um bloqueio foi adquirido), review (uma revisão foi postada).

DELETE /api/watchlist/{repo_id}/watchlist/{watch_id}

Remove um padrão de watch. Retorna { "success": true }.

Quadro de produção (tasks)

A antiga API modification-requests foi removida: o quadro de produção a absorve (as solicitações se tornam cartões, origin='request'). Os endpoints estão aninhados sob /api/tasks/{repo_id}.

RotaDescrição
GET /api/tasks/{repo_id}/boardQuadro completo: colunas + cartões
POST /api/tasks/{repo_id}/tasksCria um cartão
PUT/DELETE /api/tasks/{repo_id}/tasks/{task_id}Atualiza / remove um cartão
PUT /api/tasks/{repo_id}/tasks/{task_id}/assigneesAtribui usuários a um cartão
GET /api/tasks/{repo_id}/assignable-usersLista de usuários atribuíveis
POST /api/tasks/{repo_id}/columnsConfigura as colunas do quadro

Sub-rotas também disponíveis: comentários de cartão, links para assets e commits, e anexos (com imagem de capa). A configuração das colunas requer a capability manage_board (admin / lead).

Builds

POST /api/builds/{repo_id}/upload?hash=<sha256>

Faz upload de um arquivo de build para <storage_path>/builds/<hash>. O servidor verifica o SHA-256 fornecido no query param contra o conteúdo recebido. Se o hash já existir no lado do servidor, retorna 200 imediatamente sem recopiar (dedup do lado do build storage).

Cabeçalhos: Content-Type: application/octet-stream.

Body: binário bruto (sem wrapping JSON).

Limites: 8 GB por arquivo.

Erros:

  • 400: query param hash ausente ou malformado, ou SHA-256 calculado != hash fornecido
  • 413: arquivo > 8 GB

POST /api/builds/{repo_id}/publish

Registra o manifest de um build após o upload de todos os seus arquivos. Capability publish_builds obrigatória.

Body:

{
  "version": "0.1.5-nightly",
  "config": "Development",
  "platform": "Win64",
  "executable_path": "HeroRPG/Binaries/Win64/HeroRPG.exe",
  "release_notes": "Nightly build of main branch, 2026-05-15",
  "files": [
    { "path": "HeroRPG.exe",                "hash": "abc123...", "size_bytes": 12345678 },
    { "path": "HeroRPG/Content/Paks/pak0.pak", "hash": "def456...", "size_bytes": 2147483648 }
  ]
}

GET /api/builds/{repo_id}

Lista os builds publicados. Capability download_builds obrigatória para ver os links de download.

Response 200:

{
  "success": true,
  "data": [
    {
      "id": 5,
      "version": "0.1.5-nightly",
      "config": "Development",
      "platform": "Win64",
      "published_by": "ci-nightly",
      "published_at": "2026-05-15T03:00:00Z",
      "size_bytes": 2159829326,
      "file_count": 142,
      "release_notes": "Nightly build of main branch, 2026-05-15"
    }
  ]
}

GET /api/builds/{repo_id}/{build_id}/file

Faz download de um arquivo de um build. Capability download_builds obrigatória. O manifest de um build está disponível via GET /api/builds/{repo_id}/{build_id}/manifest.

Administração

Todas as rotas /api/admin/* exigem que o usuário tenha pelo menos uma capability de admin (conforme o endpoint: manage_users, manage_permissions, manage_rules, etc.). Documentação sucinta abaixo. A maioria segue o padrão REST padrão (GET/POST/PUT/DELETE).

RotaCapabilityDescrição
GET/POST /api/admin/usersmanage_usersLista / cria usuários
PUT/DELETE /api/admin/users/{id}manage_usersAtualiza / remove
POST /api/admin/users/{id}/reset-passwordmanage_usersReseta a senha, incrementa token_version
GET/POST /api/admin/groupsmanage_usersGestão de grupos
GET/POST /api/admin/permissions/{repo_id}manage_permissionsRegras glob de permissões por caminho
GET/POST /api/admin/repositories/{repo_id}/rulesmanage_rulesRegras de validação pré-checkin
GET/POST /api/admin/repositories/{repo_id}/webhooksmanage_rulesDiscord / Slack / Teams / custom
GET /api/admin/auditview_all_activityAudit log filtrado e paginado
GET /api/admin/statsview_all_activityStorage metrics, dedup ratio, crescimento
GET /api/admin/repositories/{repo_id}/gc/previewadminPreview do GC sem executar
POST /api/admin/repositories/{repo_id}/gcadminInicia o garbage collection
POST /api/admin/locks/{id}/force-releaseforce_unlockForça o desbloqueio de um bloqueio de terceiros, auditado

Saúde

GET /health

Endpoint não autenticado que retorna 200 OK se o servidor conseguir acessar o banco de dados. Usado para os health checks de load balancer ou de monitoramento (Prometheus blackbox, Datadog synthetic, etc.).

Response 200: OK (text/plain).

Response 503: se o banco de dados estiver inacessível.

GET /api/server-info

Endpoint público não autenticado que retorna os metadados do servidor (versão, etc.). Como /health, ele não é protegido pelo middleware de auth.

Formato de erro

Em caso de erro, a resposta é { "success": false, "error": "..." } com um status HTTP apropriado. O campo error é uma mensagem legível por humanos. Para os clientes que precisam de códigos estáveis legíveis por máquina, faça o parse do prefixo (ex.: "Permission denied: ..." começa sempre por "Permission denied").

HTTPSignificadoQuando
400Bad requestParâmetro inválido, JSON malformado, body grande demais (antes do limite rígido 413)
401Not authenticatedToken ausente, expirado, assinatura inválida, ou token_version incompatível
403Permission deniedCapability ausente, ou sem permissão sobre o caminho / repo
404Resource not foundRepo / arquivo / commit / usuário inexistente ou inacessível
409ConflictBloqueio já mantido, commit concorrente, restrição unique violada
413Payload too largeBody além do limite (1 GB em /files, 8 GB em /builds/upload)
429Too many requestsApenas em /auth/login (rate limiting por usuário)
500Internal server errorErro de DB, erro de IO. Registrado no servidor; anexe o timestamp se você reportar o bug.
503Service unavailableBanco de dados inacessível (health check) ou migração em andamento