# EzServer — Documentação da API REST v1

> Versão da API: **v1** · Atualizado em: **03/10/2026**
> Página online: <https://ezserver.app/api>

A API do EzServer permite integrar o gerenciamento dos seus servidores a scripts, pipelines de deploy e sistemas internos. Todas as respostas são em **JSON** e a autenticação é feita por **token Bearer** (Laravel Sanctum).

---

## Sumário

1. [Visão geral](#1-visão-geral)
2. [Autenticação](#2-autenticação)
3. [Conta e tokens](#3-conta-e-tokens)
4. [Servidores](#4-servidores)
5. [Terminal SSH em tempo real (WebSocket)](#5-terminal-ssh-em-tempo-real-websocket)
6. [Monitoramento e painel](#6-monitoramento-e-painel)
7. [Domínios e DNS](#7-domínios-e-dns)
8. [Backups](#8-backups)
9. [Banco de dados — upload em partes, importação e exportação](#9-banco-de-dados--upload-em-partes-importação-e-exportação)
10. [Arquivos — download em partes](#10-arquivos--download-em-partes)
11. [Webhooks Git (deploy automático)](#11-webhooks-git-deploy-automático)
12. [API MCP (Claude / Model Context Protocol)](#12-api-mcp-claude--model-context-protocol)
13. [Erros e códigos HTTP](#13-erros-e-códigos-http)
14. [Limites e boas práticas](#14-limites-e-boas-práticas)
15. [Exemplos completos](#15-exemplos-completos)

---

## 1. Visão geral

| Item | Valor |
|------|-------|
| URL base | `https://ezserver.app/api/v1` |
| Formato | JSON (`Content-Type: application/json`) |
| Autenticação | `Authorization: Bearer SEU_TOKEN` |
| Cabeçalho obrigatório | `Accept: application/json` |
| Idioma das mensagens | Português (pt-BR) |

Envie **sempre** o cabeçalho `Accept: application/json`. Sem ele, erros de validação e de autenticação podem voltar como redirecionamento HTML em vez de JSON.

### Formato padrão das respostas

Sucesso:

```json
{
  "success": true,
  "message": "Texto opcional",
  "data": { },
  "meta": { }
}
```

Erro:

```json
{
  "success": false,
  "message": "Descrição do erro",
  "errors": { "campo": ["mensagem de validação"] }
}
```

> Os endpoints de **banco de dados** e **arquivos** (seções 9 e 10) devolvem o erro no campo `error` em vez de `message`.

### Identificadores

- `{id}` / `{server}` — ID numérico do servidor, obtido em `GET /servers`.
- Todos os recursos são **isolados por conta**: um ID que não pertence a você responde `404` (ou `403` nas seções 9 e 10).

### Verificar se a API está no ar

```
GET /health
```

Não exige token.

```json
{
  "success": true,
  "message": "EasyServer API is running",
  "version": "1.0.0",
  "timestamp": "2026-10-02T12:00:00.000000Z"
}
```

---

## 2. Autenticação

A API usa login em **duas etapas**: senha → código do app autenticador (2FA). O token só é emitido para contas com:

- **e-mail confirmado**; e
- **autenticação em dois fatores ativa** (ative no painel, no primeiro login).

### 2.1 Criar conta

```
POST /auth/register
```

| Campo | Tipo | Obrigatório | Regras |
|-------|------|:-----------:|--------|
| `name` | string | sim | máx. 255 |
| `email` | string | sim | e-mail válido, único |
| `password` | string | sim | mín. 8 caracteres |
| `password_confirmation` | string | sim | igual a `password` |

Resposta `201`:

```json
{
  "success": true,
  "message": "Conta criada. Confirme seu e-mail e ative a autenticação em dois fatores no painel para usar a API.",
  "data": { "user": { "id": 123, "name": "Maria", "email": "maria@empresa.com" } }
}
```

Depois do cadastro, confirme o e-mail e ative o 2FA pelo painel antes de fazer login pela API.

### 2.2 Login (etapa 1 — senha)

```
POST /auth/login
```

| Campo | Tipo | Obrigatório |
|-------|------|:-----------:|
| `email` | string | sim |
| `password` | string | sim |

```bash
curl -X POST https://ezserver.app/api/v1/auth/login \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"email":"voce@empresa.com","password":"SUA_SENHA"}'
```

Resposta `200` (o `success: false` é intencional — o login ainda não terminou):

```json
{
  "success": false,
  "message": "2FA requerido",
  "requires_2fa": true,
  "challenge_token": "hK3v…(64 caracteres)…Qz",
  "expires_in": 300
}
```

| HTTP | Situação |
|------|----------|
| `401` | E-mail ou senha incorretos (`Credenciais inválidas`) |
| `403` | E-mail não confirmado, ou 2FA não ativado na conta |
| `422` | Campos ausentes/inválidos |
| `429` | Mais de 10 tentativas por minuto a partir do mesmo IP |

### 2.3 Verificar 2FA (etapa 2 — código)

```
POST /auth/verify-2fa
```

| Campo | Tipo | Obrigatório | Descrição |
|-------|------|:-----------:|-----------|
| `challenge_token` | string | sim | Exatamente 64 caracteres, recebido no login |
| `code` | string | sim | Código de 6 dígitos do app autenticador **ou** um código de backup |

```bash
curl -X POST https://ezserver.app/api/v1/auth/verify-2fa \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"challenge_token":"hK3v…Qz","code":"482913"}'
```

Resposta `200`:

```json
{
  "success": true,
  "message": "2FA verificado com sucesso",
  "data": {
    "user": { "id": 123, "name": "Maria", "email": "maria@empresa.com", "is_admin": false, "plan": "pro" },
    "token": "45|o8Xk...Zq1"
  }
}
```

Regras do `challenge_token`:

- vale por **5 minutos**;
- é de **uso único** (descartado após o sucesso);
- é descartado após **5 códigos errados** — faça login de novo.

| HTTP | Situação |
|------|----------|
| `401` | `Código 2FA inválido`, ou `Login expirado ou inválido. Faça login novamente.` |
| `422` | Campos ausentes/inválidos |
| `429` | Limite de tentativas por IP |

### 2.4 Usando o token

Envie o token em todas as chamadas protegidas:

```bash
curl https://ezserver.app/api/v1/servers \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Accept: application/json"
```

O token **não expira**. Revogue-o quando não precisar mais (seção 3).

---

## 3. Conta e tokens

Todos exigem token.

| Método | Caminho | Descrição |
|--------|---------|-----------|
| `GET` | `/auth/profile` | Dados do usuário e estatísticas |
| `PUT` | `/auth/profile` | Atualiza nome, e-mail ou senha |
| `GET` | `/auth/tokens` | Lista seus tokens |
| `DELETE` | `/auth/tokens/{tokenId}` | Revoga um token |
| `POST` | `/auth/logout` | Revoga o token usado na requisição |
| `POST` | `/auth/logout-all` | Revoga **todos** os seus tokens |

### 3.1 `GET /auth/profile`

```json
{
  "success": true,
  "data": {
    "user": {
      "id": 123,
      "name": "Maria",
      "email": "maria@empresa.com",
      "is_admin": false,
      "plan": "pro",
      "two_factor_enabled": true,
      "created_at": "2026-01-10T14:00:00.000000Z",
      "updated_at": "2026-09-01T10:00:00.000000Z"
    },
    "statistics": {
      "total_servers": 4,
      "active_servers": 3,
      "total_domains": 7,
      "total_applications": 9
    }
  }
}
```

### 3.2 `PUT /auth/profile`

Envie apenas os campos que deseja alterar.

| Campo | Tipo | Regras |
|-------|------|--------|
| `name` | string | máx. 255 |
| `email` | string | e-mail válido, único |
| `password` | string | mín. 8; exige `password_confirmation` e `current_password` |
| `password_confirmation` | string | igual a `password` |
| `current_password` | string | obrigatório quando `password` é enviado |

```bash
curl -X PUT https://ezserver.app/api/v1/auth/profile \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"current_password":"SENHA_ATUAL","password":"NovaSenha123","password_confirmation":"NovaSenha123"}'
```

Senha atual errada → `422` com `"message": "Senha atual incorreta"`.

### 3.3 `GET /auth/tokens`

```json
{
  "success": true,
  "data": [
    { "id": 45, "name": "api", "last_used_at": "2026-10-02T11:58:00.000000Z", "created_at": "2026-09-20T09:00:00.000000Z", "is_current": true },
    { "id": 31, "name": "api", "last_used_at": null, "created_at": "2026-06-01T09:00:00.000000Z", "is_current": false }
  ]
}
```

### 3.4 `DELETE /auth/tokens/{tokenId}`

```json
{ "success": true, "message": "Token removido com sucesso" }
```

### 3.5 `POST /auth/logout` e `POST /auth/logout-all`

```json
{ "success": true, "message": "Logout realizado com sucesso" }
```

```json
{ "success": true, "message": "Logout realizado de todos os dispositivos" }
```

---

## 4. Servidores

| Método | Caminho | Descrição |
|--------|---------|-----------|
| `GET` | `/servers` | Lista seus servidores |
| `POST` | `/servers` | Cadastra um servidor |
| `GET` | `/servers/{id}` | Detalhes, configuração detectada e métricas |
| `PUT` | `/servers/{id}` | Atualiza dados do servidor |
| `DELETE` | `/servers/{id}` | Remove o servidor do EzServer |
| `POST` | `/servers/{id}/test-connection` | Testa a conexão SSH (e ativa o servidor) |
| `POST` | `/servers/{id}/execute-command` | Executa um comando via SSH |
| `GET` | `/servers/{id}/applications` | Aplicações detectadas no servidor |
| `POST` | `/servers/{id}/terminal` | Ticket para o terminal SSH em tempo real ([seção 5](#5-terminal-ssh-em-tempo-real-websocket)) |

### Objeto Servidor

```json
{
  "id": 42,
  "name": "producao-loja",
  "ip_address": "203.0.113.10",
  "type": "vps",
  "status": "active",
  "ssh_port": 22,
  "ssh_user": "root",
  "created_at": "2026-09-01T10:00:00.000000Z",
  "updated_at": "2026-09-01T10:05:00.000000Z",
  "applications_count": 3,
  "domains_count": 2,
  "backups_count": 5
}
```

- `type`: `vps`, `dedicated`, `cloud` ou `shared`.
- `status`: `active`, `inactive` ou `error`.
- Senhas e chaves **nunca** são devolvidas pela API.

### 4.1 `GET /servers`

```json
{
  "success": true,
  "data": [ { "...": "Objeto Servidor" } ],
  "meta": { "total": 4, "active": 3, "inactive": 1 }
}
```

### 4.2 `POST /servers`

| Campo | Tipo | Obrigatório | Regras |
|-------|------|:-----------:|--------|
| `name` | string | sim | máx. 255 |
| `ip_address` | string | sim | somente endereço IP (IPv4/IPv6), não hostname |
| `ssh_user` | string | sim | máx. 255 |
| `ssh_password` | string | sim | mín. 6 caracteres |
| `ssh_port` | integer | não | 1–65535 (padrão `22`) |
| `type` | string | não | `vps` (padrão), `dedicated`, `cloud`, `shared` |

```bash
curl -X POST https://ezserver.app/api/v1/servers \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "producao-loja",
    "ip_address": "203.0.113.10",
    "ssh_port": 22,
    "ssh_user": "root",
    "ssh_password": "SENHA_DO_SERVIDOR",
    "type": "vps"
  }'
```

Resposta `201` com o Objeto Servidor. Observações:

- O servidor nasce com `status: "inactive"`. Chame `test-connection` para validar e ativar.
- A senha é armazenada criptografada.
- Para autenticação por **chave SSH**, cadastre o servidor pelo painel.
- O **limite de servidores do plano** vale para a API: ao atingi-lo, a resposta é `422` com `"Limite de servidores atingido para seu plano"`.

### 4.3 `GET /servers/{id}`

Devolve o Objeto Servidor com os campos extras:

| Campo | Descrição |
|-------|-----------|
| `configuration` | Configuração do servidor |
| `detected_config` | Configuração detectada automaticamente (SO, serviços, linguagens…) |
| `system_metrics` | Últimas métricas coletadas |
| `last_metrics_update` | Data da última coleta de métricas |
| `last_config_detection` | Data da última detecção de configuração |

### 4.4 `PUT /servers/{id}`

Envie apenas os campos que deseja alterar.

| Campo | Tipo | Regras |
|-------|------|--------|
| `name` | string | máx. 255 |
| `ip_address` | string | endereço IP |
| `ssh_port` | integer | 1–65535 |
| `ssh_user` | string | máx. 255 |
| `ssh_password` | string | troca a senha armazenada (string vazia é ignorada) |
| `type` | string | `vps`, `dedicated`, `cloud`, `shared` |
| `status` | string | `active`, `inactive`, `error` |

Resposta `200` com o Objeto Servidor atualizado.

### 4.5 `DELETE /servers/{id}`

Remove o servidor **apenas do EzServer** — nada é alterado na máquina.

```json
{ "success": true, "message": "Servidor removido com sucesso" }
```

### 4.6 `POST /servers/{id}/test-connection`

Conecta via SSH e executa um comando de teste. Se a conexão funcionar, o servidor passa para `status: "active"`.

```json
{
  "success": true,
  "message": "Conexão estabelecida com sucesso",
  "data": { "success": true, "message": "Conexão estabelecida com sucesso" }
}
```

Falha de conexão: `200` com `"success": false` e `"message": "Falha na conexão: …"`. Erro inesperado: `500`.

### 4.7 `POST /servers/{id}/execute-command`

| Campo | Tipo | Obrigatório |
|-------|------|:-----------:|
| `command` | string | sim |

```bash
curl -X POST https://ezserver.app/api/v1/servers/42/execute-command \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"command":"df -h /"}'
```

```json
{
  "success": true,
  "data": {
    "command": "df -h /",
    "output": "Filesystem  Size  Used Avail Use% Mounted on\n/dev/sda1  80G  27G  53G  34% /\n",
    "error": "",
    "exit_code": 0
  }
}
```

- `success: true` indica que o comando **foi executado**; verifique `exit_code` e `error` para saber se ele teve êxito.
- A requisição espera o comando terminar. Para tarefas longas, rode em segundo plano (`nohup … &`) ou use o terminal do painel com tmux.
- O comando roda com o usuário SSH cadastrado — trate o token com o mesmo cuidado que uma senha de root.

### 4.8 `GET /servers/{id}/applications`

```json
{
  "success": true,
  "data": [
    {
      "id": 7,
      "name": "loja",
      "type": "php",
      "framework": "laravel",
      "path": "/var/www/loja",
      "domain": "loja.com.br",
      "status": "running",
      "environment": "production",
      "version": "11.x",
      "created_at": "2026-08-01T10:00:00.000000Z",
      "updated_at": "2026-09-01T10:00:00.000000Z"
    }
  ]
}
```

---

## 5. Terminal SSH em tempo real (WebSocket)

Abre um **terminal interativo** (PTY) num servidor, com streaming bidirecional — o mesmo terminal do painel, para você embutir no seu app (xterm.js, WebView, terminal nativo etc.).

A senha e a chave SSH **nunca chegam ao app**: o app pede um *ticket* de uso único com o token da API e se autentica no WebSocket só com esse ticket. O EzServer resolve as credenciais internamente.

### 5.1 Fluxo

```
App                                   EzServer
 │  POST /servers/{id}/terminal  ───────▶  (token Bearer)
 │  ◀─────────── { ws_url, ticket, expires_in: 60 }
 │
 │  WebSocket ws_url (wss://ezserver.app/ssh-ws)
 │  ──▶ {"type":"auth","ticket":"…","cols":80,"rows":24}
 │  ◀── {"type":"connected","tmux":true,"session":"ezserver"}
 │  ──▶ {"type":"input","data":"ls -la\r"}
 │  ◀── {"type":"output","data":"<base64>"}   (contínuo)
 │  ──▶ {"type":"resize","cols":120,"rows":40}
```

### 5.2 `POST /servers/{id}/terminal` — obter ticket

Exige token. Sem corpo.

```bash
curl -X POST https://ezserver.app/api/v1/servers/42/terminal \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Accept: application/json"
```

```json
{
  "success": true,
  "data": {
    "ws_url": "wss://ezserver.app/ssh-ws",
    "ticket": "Zk81…(64 caracteres)…pQ",
    "expires_in": 60,
    "server": { "id": 42, "name": "producao-loja" }
  }
}
```

- O ticket vale por **60 segundos** e é de **uso único**: peça um novo a cada conexão (inclusive reconexões).
- Limite: **30 tickets por minuto**.

| HTTP | Situação |
|------|----------|
| `401` | Token ausente ou inválido |
| `404` | Servidor não existe ou não é seu |
| `422` | Servidor sem senha nem chave SSH cadastrada |
| `429` | Limite de tickets atingido |

### 5.3 Conexão WebSocket

URL: o `ws_url` devolvido (hoje `wss://ezserver.app/ssh-ws`). Todas as mensagens são **texto JSON**, nos dois sentidos.

Assim que a conexão abrir, envie o `auth` — conexões que não se autenticam em **30 segundos** são encerradas. Comandos enviados antes do `connected` são descartados.

A **única** forma de autenticação é o ticket. Enviar host, usuário ou senha no `auth` não é aceito (a conexão é fechada com o código `4003`).

Cada mensagem pode ter no máximo **1 MB**. Para colar textos maiores, divida em várias mensagens `input`.

#### Mensagens do app → EzServer

| `type` | Campos | Descrição |
|--------|--------|-----------|
| `auth` | `ticket` (obrigatório), `cols`, `rows`, `tmux`, `tmuxSession` | Autentica e abre o terminal. Envie **uma vez** por conexão. |
| `input` | `data`, `encoding` (opcional) | Teclas/texto digitado. Com `"encoding":"base64utf8"`, `data` é o texto UTF-8 em base64 (recomendado). |
| `resize` | `cols`, `rows` | Informa o novo tamanho do terminal (colunas × linhas). |
| `ping` | — | Mantém a conexão ativa; responde `pong`. |

Campos do `auth`:

| Campo | Tipo | Padrão | Descrição |
|-------|------|:------:|-----------|
| `ticket` | string | — | Ticket obtido em `POST /servers/{id}/terminal` |
| `cols` | integer | `80` | Colunas iniciais |
| `rows` | integer | `24` | Linhas iniciais |
| `tmux` | boolean | `true` | Usa sessão persistente tmux (se o servidor tiver tmux). `false` abre um shell simples. |
| `tmuxSession` | string | `ezserver` | Nome da sessão tmux (letras, números, `_` e `-`; máx. 50) |

Com `tmux` ativo, cair a conexão **não** encerra o que está rodando: ao reconectar com a mesma `tmuxSession`, o terminal volta exatamente de onde parou. É a mesma sessão usada pelo terminal do painel.

#### Mensagens do EzServer → app

| `type` | Campos | Descrição |
|--------|--------|-----------|
| `connected` | `message`, `tmux`, `session` | Terminal pronto. `tmux` indica se a sessão é persistente. |
| `output` | `data` | Saída do terminal, **UTF-8 codificado em base64**. Decodifique e escreva no terminal como está (inclui sequências ANSI de cor/cursor). |
| `error` | `message`, `code` (opcional) | Erro de autenticação SSH, ticket inválido etc. |
| `disconnected` | `message` | A sessão SSH terminou ou o servidor de terminais está reiniciando. Reconecte com um novo ticket. |
| `pong` | — | Resposta ao `ping`. |

Códigos de fechamento do WebSocket:

| Código | Motivo |
|--------|--------|
| `4001` | `invalid_ticket` — ticket inválido, expirado, já usado ou acesso ao servidor revogado. Peça outro. |
| `4002` | `auth_timeout` — o `auth` não chegou em 30 segundos. Reconecte com um novo ticket. |
| `4003` | `ticket_required` — `auth` sem ticket (login por credenciais não é aceito). |
| `1009` | Mensagem maior que 1 MB. |

Os mesmos motivos chegam antes numa mensagem `error`, no campo `code`.

### 5.4 Teclas especiais

Envie os bytes que um terminal enviaria:

| Tecla | `data` |
|-------|--------|
| Enter | `\r` |
| Backspace | `\x7f` |
| Tab | `\t` |
| Ctrl+C | `\x03` |
| Ctrl+D | `\x04` |
| Esc | `\x1b` |
| Setas ↑ ↓ → ← | `\x1b[A` `\x1b[B` `\x1b[C` `\x1b[D` |

Com xterm.js, basta repassar o que chega em `term.onData(...)`.

### 5.5 Conexão e reconexão

- O EzServer envia *ping* de protocolo WebSocket a cada 60 s; bibliotecas WebSocket respondem sozinhas. Conexões sem resposta são encerradas.
- Envie `{"type":"ping"}` a cada ~25 s para atravessar proxies e redes móveis com folga.
- Ao receber `disconnected` ou ao fechar o socket, **peça um novo ticket** e reconecte (sugestão: 1 s, 2 s, 4 s… até 30 s). Com tmux, a sessão continua de onde parou.
- Em apps móveis, ao voltar do segundo plano, verifique se o socket ainda está aberto e reconecte se preciso.

### 5.6 Exemplo — JavaScript com xterm.js (web, Electron ou WebView no React Native/Flutter)

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.min.css">
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/lib/addon-fit.min.js"></script>
<div id="terminal" style="height:100vh"></div>
<script>
const API = "https://ezserver.app/api/v1";
const TOKEN = "SEU_TOKEN";      // no app, guarde no armazenamento seguro do sistema
const SERVER_ID = 42;

const term = new Terminal({ cursorBlink: true, fontSize: 14 });
const fit = new FitAddon.FitAddon();
term.loadAddon(fit);
term.open(document.getElementById("terminal"));
fit.fit();

const b64 = (s) => btoa(String.fromCharCode(...new TextEncoder().encode(s)));
const unb64 = (s) => new TextDecoder().decode(Uint8Array.from(atob(s), (c) => c.charCodeAt(0)));

let ws, retry = 0, keepalive;

async function connect() {
  const res = await fetch(`${API}/servers/${SERVER_ID}/terminal`, {
    method: "POST",
    headers: { Authorization: `Bearer ${TOKEN}`, Accept: "application/json" },
  });
  const { data } = await res.json();

  ws = new WebSocket(data.ws_url);
  ws.onopen = () => ws.send(JSON.stringify({ type: "auth", ticket: data.ticket, cols: term.cols, rows: term.rows }));
  ws.onmessage = (ev) => {
    const msg = JSON.parse(ev.data);
    if (msg.type === "connected") { retry = 0; keepalive = setInterval(() => ws.send('{"type":"ping"}'), 25000); }
    if (msg.type === "output") term.write(unb64(msg.data));
    if (msg.type === "error") term.writeln(`\r\n\x1b[31m${msg.message}\x1b[0m`);
    if (msg.type === "disconnected") ws.close();
  };
  ws.onclose = () => {
    clearInterval(keepalive);
    setTimeout(connect, Math.min(1000 * 2 ** retry++, 30000));
  };
}

term.onData((d) => ws?.readyState === 1 && ws.send(JSON.stringify({ type: "input", data: b64(d), encoding: "base64utf8" })));
window.addEventListener("resize", () => {
  fit.fit();
  ws?.readyState === 1 && ws.send(JSON.stringify({ type: "resize", cols: term.cols, rows: term.rows }));
});

connect();
</script>
```

> **React Native / Flutter:** a forma mais simples é renderizar a página acima num `WebView` e passar o token por `postMessage`/`injectedJavaScript`. Se preferir terminal nativo, o protocolo é o mesmo: qualquer cliente WebSocket + um widget que interprete sequências ANSI/VT100.

### 5.7 Exemplo — Python (sem interface, para automações)

```python
import asyncio, base64, json, os, requests, websockets

API = "https://ezserver.app/api/v1"
H = {"Authorization": f"Bearer {os.environ['EZSERVER_TOKEN']}", "Accept": "application/json"}

async def main(server_id: int):
    t = requests.post(f"{API}/servers/{server_id}/terminal", headers=H, timeout=30).json()["data"]
    async with websockets.connect(t["ws_url"]) as ws:
        await ws.send(json.dumps({"type": "auth", "ticket": t["ticket"], "cols": 120, "rows": 40, "tmux": False}))
        async for raw in ws:
            msg = json.loads(raw)
            if msg["type"] == "connected":
                await ws.send(json.dumps({"type": "input", "data": "uptime; exit\r"}))
            elif msg["type"] == "output":
                print(base64.b64decode(msg["data"]).decode("utf-8", "replace"), end="")
            elif msg["type"] in ("error", "disconnected"):
                print(f"\n[{msg['type']}] {msg.get('message')}")
                break

asyncio.run(main(42))
```

### 5.8 Segurança

- A senha e a chave SSH **nunca** são enviadas ao app nem ao navegador — o EzServer as resolve internamente a partir do ticket.
- O ticket só abre o terminal do servidor para o qual foi emitido, uma única vez, por 60 s. A permissão é conferida de novo no momento da conexão: se o acesso foi revogado depois da emissão, a conexão é recusada.
- Quando o servidor tem chave **e** senha cadastradas, o EzServer tenta a chave primeiro e usa a senha como alternativa.
- Toda emissão e uso de ticket fica registrada (usuário, servidor, IP).
- O terminal roda com o usuário SSH cadastrado no servidor — quem tem o token da API tem acesso de shell. Proteja o token no app (Keychain/Keystore) e revogue-o se o aparelho for perdido (`DELETE /auth/tokens/{id}`).

---

## 6. Monitoramento e painel

| Método | Caminho | Descrição |
|--------|---------|-----------|
| `GET` | `/metrics` | Últimas métricas de todos os seus servidores |
| `GET` | `/dashboard` | Resumo da conta e atividade recente |
| `GET` | `/health` | Status da API (sem token) |

### 6.1 `GET /metrics`

```json
{
  "success": true,
  "data": [
    {
      "id": 42,
      "name": "producao-loja",
      "status": "active",
      "cpu_usage": 12.5,
      "memory_usage": 63.1,
      "disk_usage": 34,
      "load_average": 0.42,
      "network_in": 1048576,
      "network_out": 524288
    }
  ]
}
```

Valores em porcentagem para CPU, memória e disco. Servidores sem métricas coletadas devolvem `0`.

### 6.2 `GET /dashboard`

```json
{
  "success": true,
  "data": {
    "user": { "id": 123, "name": "Maria", "email": "maria@empresa.com", "plan": "pro" },
    "statistics": {
      "servers": { "total": 4, "active": 3, "inactive": 1 },
      "applications": 9,
      "domains": 7,
      "backups": 12
    },
    "recent_activity": {
      "servers": [ { "id": 42, "name": "producao-loja", "status": "active", "created_at": "…" } ],
      "backups": [ { "id": 88, "name": "Backup diário", "status": "completed", "created_at": "…" } ]
    }
  }
}
```

---

## 7. Domínios e DNS

| Método | Caminho | Descrição |
|--------|---------|-----------|
| `GET` | `/domains` | Lista seus domínios |
| `POST` | `/domains` | Cadastra um domínio |
| `GET` | `/domains/{id}` | Detalhes do domínio, com registros DNS |
| `PUT` | `/domains/{id}` | Atualiza o domínio |
| `DELETE` | `/domains/{id}` | Remove o domínio do EzServer |
| `GET` | `/domains/{id}/dns-records` | Registros da zona DNS do domínio |

### Objeto Domínio

```json
{
  "id": 15,
  "name": "loja.com.br",
  "status": "active",
  "is_external": false,
  "external_dns": null,
  "server": { "id": 42, "name": "producao-loja", "ip_address": "203.0.113.10" },
  "created_at": "2026-08-01T10:00:00.000000Z",
  "updated_at": "2026-08-01T10:00:00.000000Z"
}
```

Em `GET /domains/{id}`, se o domínio tiver zona DNS, vem também `dns_records`.

### 7.1 `GET /domains`

```json
{
  "success": true,
  "data": [ { "...": "Objeto Domínio" } ],
  "meta": { "total": 7, "active": 6, "inactive": 1 }
}
```

### 7.2 `POST /domains`

| Campo | Tipo | Obrigatório | Regras |
|-------|------|:-----------:|--------|
| `name` | string | sim | apenas letras, números, `.` e `-` |
| `server_id` | integer | sim | ID de um servidor seu |
| `is_external` | boolean | não | DNS gerenciado fora do EzServer |
| `external_dns` | string | não | Provedor/servidor de DNS externo |

Resposta `201` com o Objeto Domínio (criado com `status: "active"`).

### 7.3 `PUT /domains/{id}`

Aceita `name`, `server_id` (servidor seu), `status` (`active`/`inactive`), `is_external` e `external_dns`.

### 7.4 `DELETE /domains/{id}`

```json
{ "success": true, "message": "Domínio removido com sucesso" }
```

### 7.5 `GET /domains/{id}/dns-records`

```json
{
  "success": true,
  "data": [
    { "id": 301, "type": "A", "name": "@", "content": "203.0.113.10", "ttl": 3600, "priority": null, "created_at": "…", "updated_at": "…" },
    { "id": 302, "type": "MX", "name": "@", "content": "mail.loja.com.br", "ttl": 3600, "priority": 10, "created_at": "…", "updated_at": "…" }
  ]
}
```

---

## 8. Backups

| Método | Caminho | Descrição |
|--------|---------|-----------|
| `GET` | `/backups` | Lista os backups de todos os seus servidores |
| `GET` | `/backups/{id}` | Detalhes de um backup |
| `GET` | `/servers/{id}/backups` | Backups de um servidor |
| `GET` | `/servers/{id}/backup-configurations` | Rotinas de backup agendado de um servidor |
| `PUT` | `/servers/backup-configurations/{configId}` | Ativa/desativa ou ajusta uma rotina |
| `DELETE` | `/servers/backup-configurations/{configId}` | Remove uma rotina de backup |

> Criar backups e rotinas de backup é feito pelo painel (**Servidor → Backups**). A API serve para consultar o histórico e gerenciar as rotinas existentes.

### Objeto Backup

```json
{
  "id": 88,
  "name": "Backup diário",
  "type": "full",
  "status": "completed",
  "size": 734003200,
  "progress": 100,
  "server": { "id": 42, "name": "producao-loja" },
  "started_at": "2026-10-02T03:00:00.000000Z",
  "completed_at": "2026-10-02T03:12:41.000000Z",
  "created_at": "2026-10-02T03:00:00.000000Z"
}
```

- `size` em bytes.
- `status`: `pending`, `running`, `completed`, `failed`, `cancelled`, `verifying` ou `verified`.
- Em `GET /backups/{id}` vêm também `file_path`, `log` e `configuration`.

### 8.1 `GET /backups`

```json
{
  "success": true,
  "data": [ { "...": "Objeto Backup" } ],
  "meta": { "total": 12, "completed": 10, "running": 1, "failed": 1 }
}
```

### 8.2 `GET /servers/{id}/backup-configurations`

```json
{
  "success": true,
  "data": [
    {
      "id": 5,
      "name": "Backup diário",
      "type": "full",
      "schedule": "0 3 * * *",
      "retention_days": 30,
      "is_active": true,
      "last_run": null,
      "next_run": null,
      "created_at": "…",
      "updated_at": "…"
    }
  ]
}
```

### 8.3 `PUT /servers/backup-configurations/{configId}`

| Campo | Tipo | Regras |
|-------|------|--------|
| `name` | string | máx. 255 |
| `is_active` | boolean | pausa (`false`) ou retoma (`true`) a rotina |
| `retention_days` | integer | 1–365 |

```bash
curl -X PUT https://ezserver.app/api/v1/servers/backup-configurations/5 \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"is_active": false}'
```

### 8.4 `DELETE /servers/backup-configurations/{configId}`

Remove a rotina (os backups já gerados não são apagados).

```json
{ "success": true, "message": "Configuração de backup removida com sucesso" }
```

---

## 9. Banco de dados — upload em partes, importação e exportação

Para enviar dumps grandes (`.sql`, `.sql.gz`) e importá-los num banco do servidor. O arquivo é enviado em **partes de 10 MB**, com retomada em caso de queda. A sessão de upload vale por **24 horas**.

Prefixo: `/servers/{server}/database`

| Método | Caminho | Descrição |
|--------|---------|-----------|
| `POST` | `/upload/initialize` | Abre uma sessão de upload |
| `POST` | `/upload/chunk` | Envia uma parte (multipart) |
| `POST` | `/upload/finalize` | Junta as partes e valida o arquivo |
| `GET` | `/upload/{uploadId}/progress` | Progresso do upload |
| `POST` | `/upload/{uploadId}/resume` | Lista as partes que faltam |
| `DELETE` | `/upload/{uploadId}/cancel` | Cancela e limpa o upload |
| `POST` | `/import` | Importa o arquivo enviado (em segundo plano) |
| `POST` | `/export` | Exporta um banco (em segundo plano) |

### 9.1 Fluxo de upload

**1. Iniciar**

| Campo | Tipo | Obrigatório | Regras |
|-------|------|:-----------:|--------|
| `filename` | string | sim | máx. 255 |
| `total_size` | integer | sim | tamanho em bytes |
| `checksum_md5` | string | não | 32 caracteres |
| `checksum_sha256` | string | não | 64 caracteres |

```json
{ "success": true, "upload_id": "9b2f6d1e-…-uuid", "chunk_size": 10485760, "total_chunks": 37 }
```

**2. Enviar cada parte** (`multipart/form-data`)

| Campo | Tipo | Obrigatório |
|-------|------|:-----------:|
| `upload_id` | string (UUID) | sim |
| `chunk_index` | integer | sim — começa em `0` |
| `chunk_data` | arquivo | sim — os bytes da parte |

```bash
curl -X POST https://ezserver.app/api/v1/servers/42/database/upload/chunk \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Accept: application/json" \
  -F upload_id=9b2f6d1e-… \
  -F chunk_index=0 \
  -F chunk_data=@parte_000
```

```json
{
  "success": true,
  "chunk_index": 0,
  "progress": { "uploaded_chunks": 1, "total_chunks": 37, "uploaded_bytes": 10485760, "total_bytes": 381681664, "percent": 2.7 },
  "completed": false
}
```

Reenviar uma parte já recebida é seguro (`"message": "Chunk already uploaded"`).

**3. Finalizar**

```json
{ "upload_id": "9b2f6d1e-…" }
```

```json
{
  "success": true,
  "file_path": "/…/caminho/do/arquivo.sql.gz",
  "manifest_path": "/…/caminho/do/manifesto.json",
  "size": 381681664,
  "size_human": "364.01 MB"
}
```

Se faltar alguma parte: `"success": false`, `"error": "Not all chunks uploaded"` com o `progress`. Use `resume` para descobrir quais faltam:

```json
{ "success": true, "upload_id": "…", "total_chunks": 37, "uploaded_chunks": 35, "missing_chunks": [12, 30], "progress": { } }
```

### 9.2 `POST /import`

Use o `file_path` devolvido pelo `finalize`.

| Campo | Tipo | Obrigatório | Padrão | Descrição |
|-------|------|:-----------:|:------:|-----------|
| `database_name` | string | sim | — | Banco de destino no servidor |
| `file_path` | string | sim | — | Valor retornado em `upload/finalize` |
| `create_backup` | boolean | não | `true` | Faz backup do banco antes de importar |
| `drop_existing` | boolean | não | `false` | Apaga as tabelas existentes antes |
| `force` | boolean | não | `false` | Continua mesmo com avisos |
| `force_without_backup` | boolean | não | `false` | Prossegue mesmo se o backup prévio falhar |

```json
{ "success": true, "message": "Importação segura iniciada em background. Acompanhe o progresso em tempo real." }
```

A importação roda em segundo plano; acompanhe pelo painel (**Servidor → Bancos de dados**).

### 9.3 `POST /export`

| Campo | Tipo | Obrigatório | Padrão |
|-------|------|:-----------:|:------:|
| `database_name` | string | sim | — |
| `compress` | boolean | não | `true` |
| `include_data` | boolean | não | `true` |
| `include_structure` | boolean | não | `true` |

```json
{ "success": true, "message": "Exportação segura iniciada em background. Acompanhe o progresso em tempo real." }
```

O arquivo exportado fica disponível no painel ao terminar.

---

## 10. Arquivos — download em partes

Baixa arquivos grandes do servidor em **partes de 10 MB** (codificadas em base64), com checksum por parte e retomada. Sessão válida por **24 horas**.

Prefixo: `/servers/{server}/files`

| Método | Caminho | Descrição |
|--------|---------|-----------|
| `POST` | `/download/initialize` | Abre uma sessão de download |
| `GET` | `/download/{downloadId}/chunk/{chunkIndex}` | Baixa uma parte |
| `POST` | `/download/{downloadId}/finalize` | Junta e valida as partes |
| `GET` | `/download/{downloadId}/progress` | Progresso |
| `POST` | `/download/{downloadId}/resume` | Lista as partes que faltam |
| `DELETE` | `/download/{downloadId}/cancel` | Cancela e limpa |
| `POST` | `/download` | Faz o download inteiro em segundo plano |

### 10.1 Iniciar

```json
{ "remote_path": "/var/www/loja/storage/logs/laravel.log" }
```

```json
{
  "success": true,
  "download_id": "c41a…",
  "chunk_size": 10485760,
  "total_chunks": 3,
  "total_size": 26214400,
  "filename": "laravel.log"
}
```

### 10.2 Baixar uma parte

```
GET /servers/42/files/download/c41a…/chunk/0
```

```json
{
  "success": true,
  "chunk_data": "BASE64…",
  "chunk_index": 0,
  "chunk_size": 10485760,
  "checksum_md5": "…",
  "checksum_sha256": "…",
  "progress": {
    "downloaded_chunks": 1, "total_chunks": 3,
    "downloaded_bytes": 10485760, "total_bytes": 26214400,
    "percent": 40, "downloaded_human": "10 MB", "total_human": "25 MB"
  },
  "completed": false
}
```

Decodifique `chunk_data` (base64), confira o `checksum_sha256` e grave as partes em ordem.

### 10.3 Download em segundo plano

`POST /download` com `{ "remote_path": "…" }` inicia a sessão e processa todas as partes em segundo plano:

```json
{ "success": true, "message": "Download seguro iniciado em background. Acompanhe o progresso em tempo real.", "download_id": "c41a…" }
```

Acompanhe com `GET /download/{downloadId}/progress`.

---

## 11. Webhooks Git (deploy automático)

URL base: `https://ezserver.app/api/webhooks` — **não usa token Bearer**; a autenticação é o par `{repository}/{token}` da URL do webhook. Copie a URL pronta no painel, na tela do repositório Git.

| Método | Caminho | Descrição |
|--------|---------|-----------|
| `POST` | `/git/{repository}/{token}` | Recebe o push e dispara o deploy |
| `GET` | `/git/{repository}/{token}/test` | Testa se a URL do webhook é válida |
| `GET` | `/git/{repository}/{token}/status` | Repositório, últimos 10 deploys e estatísticas |

### 11.1 Configurar no GitHub / GitLab / Bitbucket

1. No painel do EzServer, abra o repositório e copie a **URL do webhook**.
2. No provedor Git, crie um webhook com essa URL, `Content-Type: application/json` e o evento **push**.
3. Ative o **deploy automático** do repositório no EzServer.

Para escolher o domínio de destino quando o repositório está ligado a mais de um, adicione `?domain={id_do_dominio}` à URL. Sem o parâmetro, o primeiro domínio vinculado é usado.

### 11.2 Respostas do `POST`

| HTTP | Corpo | Situação |
|------|-------|----------|
| `200` | `{"message": "…", "repository": "…", "domain": "…", "status": "success"}` | Deploy executado |
| `200` | `{"message": "Auto-deploy is disabled for this repository"}` | Deploy automático desligado |
| `200` | `{"message": "No domain configured for this repository"}` | Repositório sem domínio |
| `404` | `{"error": "Invalid repository or token"}` | URL inválida |
| `500` | `{"message": "…", "status": "error"}` | Falha no deploy |

### 11.3 `GET …/test`

```json
{
  "message": "Webhook endpoint is working",
  "repository": "loja",
  "timestamp": "2026-10-02T12:00:00.000000Z",
  "webhook_url": "https://ezserver.app/api/webhooks/git/…"
}
```

### 11.4 `GET …/status`

```json
{
  "repository": { "id": 3, "name": "loja", "url": "git@github.com:empresa/loja.git", "branch": "main", "auto_deploy": true, "framework": "laravel" },
  "webhook": { "url": "https://ezserver.app/api/webhooks/git/…", "auto_deploy_enabled": true },
  "recent_deployments": [
    { "id": 91, "domain": "loja.com.br", "server": "producao-loja", "branch": "main", "commit_hash": "a1b2c3d", "status": "success", "deployed_at": "…", "duration": "42s" }
  ],
  "stats": { }
}
```

---

## 12. API MCP (Claude / Model Context Protocol)

Endpoints usados pela integração do EzServer com o **Claude** (e outros clientes MCP). Usam um **token MCP próprio**, diferente do token da API REST.

URL base: `https://ezserver.app/api/mcp` · Limite: **120 requisições por minuto**.

### 12.1 Credenciais

Gere em **Perfil → Integração MCP** no painel. Envie os dois cabeçalhos:

| Cabeçalho | Valor |
|-----------|-------|
| `Authorization` | `Bearer SEU_TOKEN_MCP` |
| `X-User-ID` | Seu ID de usuário (exibido na mesma tela) |

O token MCP expira (30 dias por padrão). Token expirado → `401` com `"error": "Token expired"`; gere outro no painel.

### 12.2 `POST /tool`

```json
{ "tool": "<nome_da_ferramenta>", "params": { } }
```

| Ferramenta | Parâmetros | Retorno |
|------------|------------|---------|
| `listServers` | — | `{ "success": true, "servers": [ … ] }` |
| `getServerDetails` | `server_id` (ID ou UUID) | `{ "success": true, "server": { … } }` |
| `executeCommand` | `server_id`, `command` | `{ "success": true, "output": "…", "exit_code": 0, "error": null }` |
| `getServerMetrics` | `server_id` | `{ "success": true, "metrics": { … } }` |

```bash
curl -X POST https://ezserver.app/api/mcp/tool \
  -H "Authorization: Bearer SEU_TOKEN_MCP" \
  -H "X-User-ID: SEU_ID" \
  -H "Content-Type: application/json" \
  -d '{"tool":"executeCommand","params":{"server_id":42,"command":"uptime"}}'
```

Comandos potencialmente destrutivos (ex.: `rm -rf /`, `mkfs`, `dd` para dispositivos, fork bombs) são **bloqueados** no MCP — rode-os manualmente pelo terminal do painel, se tiver certeza.

Erros: `400` (parâmetros ausentes), `401` (credenciais inválidas/expiradas), `404` (servidor não encontrado ou ferramenta desconhecida).

### 12.3 `GET /sse`

Stream **Server-Sent Events** do protocolo MCP (mesmos cabeçalhos de autenticação). Envia o evento `initialize` com as capacidades e um `ping` a cada 30 segundos.

Para configurar o Claude Desktop/Claude Code, siga o guia em <https://ezserver.app/documentation#mcp>.

---

## 13. Erros e códigos HTTP

| HTTP | Significado |
|------|-------------|
| `200` / `201` | Sucesso / recurso criado |
| `400` | Requisição incompleta (MCP) |
| `401` | Token ausente/inválido, credenciais erradas ou 2FA inválido |
| `403` | E-mail não confirmado, 2FA desativado na conta, ou recurso de outro usuário (seções 9 e 10) |
| `404` | Recurso não existe ou não pertence à sua conta; endpoint inexistente |
| `422` | Dados inválidos ou limite do plano atingido |
| `429` | Muitas requisições — aguarde e tente de novo |
| `500` | Erro interno ou falha na conexão SSH (veja `message`/`error`) |

Erro de validação:

```json
{
  "success": false,
  "message": "Dados de validação inválidos",
  "errors": { "ip_address": ["O campo ip address deve ser um endereço IP válido."] }
}
```

Endpoint inexistente:

```json
{ "success": false, "message": "Endpoint não encontrado", "error": "NOT_FOUND" }
```

---

## 14. Limites e boas práticas

- **Login, 2FA e cadastro**: até **10 tentativas por minuto** por IP. Faça login uma vez e reutilize o token.
- **MCP**: até **120 requisições por minuto**.
- **Terminal**: até **30 tickets por minuto**; cada ticket vale 60 s e abre uma única conexão.
- O token tem **os mesmos poderes da sua conta**, inclusive executar comandos nos servidores. Guarde-o em variável de ambiente ou cofre de segredos; nunca em código versionado ou no front-end.
- Revogue tokens que não usa mais (`GET /auth/tokens` + `DELETE /auth/tokens/{id}`). Se suspeitar de vazamento, use `POST /auth/logout-all`.
- `execute-command` é síncrono: para tarefas longas use `nohup … &` e consulte o resultado depois.
- Cada conta só enxerga os próprios recursos; IDs de outras contas respondem `404`/`403`.

---

## 15. Exemplos completos

### 15.1 Bash — login com 2FA e listagem de servidores

```bash
#!/usr/bin/env bash
set -euo pipefail
BASE="https://ezserver.app/api/v1"

CHALLENGE=$(curl -s -X POST "$BASE/auth/login" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d "{\"email\":\"$EZ_EMAIL\",\"password\":\"$EZ_PASSWORD\"}" | jq -r .challenge_token)

read -rp "Código 2FA: " CODE

TOKEN=$(curl -s -X POST "$BASE/auth/verify-2fa" \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d "{\"challenge_token\":\"$CHALLENGE\",\"code\":\"$CODE\"}" | jq -r .data.token)

curl -s "$BASE/servers" -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
  | jq -r '.data[] | "\(.id)\t\(.status)\t\(.name)\t\(.ip_address)"'
```

### 15.2 Python — alerta de disco cheio

```python
import os, requests

BASE = "https://ezserver.app/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['EZSERVER_TOKEN']}", "Accept": "application/json"}

for s in requests.get(f"{BASE}/metrics", headers=HEADERS, timeout=30).json()["data"]:
    if (s.get("disk_usage") or 0) > 85:
        print(f"⚠️  {s['name']}: disco em {s['disk_usage']}%")
```

### 15.3 Node.js — executar comando em todos os servidores ativos

```javascript
const BASE = "https://ezserver.app/api/v1";
const headers = {
  Authorization: `Bearer ${process.env.EZSERVER_TOKEN}`,
  Accept: "application/json",
  "Content-Type": "application/json",
};

const { data: servers } = await (await fetch(`${BASE}/servers`, { headers })).json();

for (const s of servers.filter((s) => s.status === "active")) {
  const res = await fetch(`${BASE}/servers/${s.id}/execute-command`, {
    method: "POST",
    headers,
    body: JSON.stringify({ command: "uptime" }),
  });
  const { data } = await res.json();
  console.log(`${s.name}: ${data.output.trim()} (exit ${data.exit_code})`);
}
```

### 15.4 Python — upload de dump SQL em partes e importação

```python
import os, math, hashlib, requests

BASE = "https://ezserver.app/api/v1"
H = {"Authorization": f"Bearer {os.environ['EZSERVER_TOKEN']}", "Accept": "application/json"}
SERVER, DB, PATH = 42, "loja", "backup.sql.gz"

size = os.path.getsize(PATH)
sha256 = hashlib.sha256(open(PATH, "rb").read()).hexdigest()

init = requests.post(f"{BASE}/servers/{SERVER}/database/upload/initialize", headers=H,
                     json={"filename": os.path.basename(PATH), "total_size": size, "checksum_sha256": sha256}).json()
upload_id, chunk = init["upload_id"], init["chunk_size"]

with open(PATH, "rb") as f:
    for i in range(math.ceil(size / chunk)):
        r = requests.post(f"{BASE}/servers/{SERVER}/database/upload/chunk", headers=H,
                          data={"upload_id": upload_id, "chunk_index": i},
                          files={"chunk_data": f.read(chunk)}).json()
        print(f"parte {i}: {r['progress']['percent']}%")

final = requests.post(f"{BASE}/servers/{SERVER}/database/upload/finalize", headers=H,
                      json={"upload_id": upload_id}).json()

print(requests.post(f"{BASE}/servers/{SERVER}/database/import", headers=H,
                    json={"database_name": DB, "file_path": final["file_path"], "create_backup": True}).json())
```

---

Dúvidas ou sugestões de novos endpoints: <https://ezserver.app/contact>
