# Introdução
URL: https://0.0.0.0:3002/docs
O que é o Hydro, os conceitos principais e por onde começar.
O **Hydro** é o painel e a API de hospedagem da **AcquaCloud**. Nele você publica **aplicações** (sites, APIs, bots e workers) e cria **bancos de dados** gerenciados, tudo dentro de um **workspace** com rede privada, membros e uma cobrança só.
## Conceitos [#conceitos]
| Conceito | O que é |
| ------------------ | ----------------------------------------------------------------------------------------------- |
| **Workspace** | Onde ficam aplicações, bancos e membros. Cada workspace tem um plano e uma rede privada própria |
| **Aplicação** | Seu código rodando: **web** (com endereço HTTPS) ou **worker** (sem porta, como um bot) |
| **Deploy** | Uma versão da aplicação: do GitHub, de um .zip ou de uma imagem Docker pública |
| **Banco de dados** | PostgreSQL, MySQL, Redis ou MongoDB gerenciado, com backup diário |
| **Plano** | Define a memória, a vCPU e os membros do workspace |
## Por onde começar [#por-onde-começar]
- [Primeiros passos](/docs/primeiros-passos): Da conta criada à primeira aplicação no ar.
- [Aplicações](/docs/aplicacoes): Tipos, origens do código, endereço, variáveis e deploy.
- [Bancos de dados](/docs/bancos-de-dados): Motores, conexão, acesso externo e backups.
- [API](/docs/api): Automatize com um token: convenções e referência.
---
# Primeiros passos
URL: https://0.0.0.0:3002/docs/primeiros-passos
Crie a conta, o workspace e coloque a primeira aplicação no ar.
## 1. Crie a conta [#1-crie-a-conta]
Cadastre-se no painel e confirme o e-mail. O **primeiro workspace** que você criar entra num [teste grátis](/docs/planos-e-quotas#período-de-teste) de 14 dias, sem cartão.
## 2. Crie a aplicação [#2-crie-a-aplicação]
Em **Aplicações → Criar aplicação**, escolha de onde vem o código:
* **GitHub:** conecte a conta e escolha o repositório e a branch. Cada push pode virar um deploy ([Deploy pelo GitHub](/docs/deploy-github)).
* **Upload de .zip:** envie o código (sem `node_modules`). O Hydro detecta a linguagem e faz o build.
* **Imagem Docker:** uma imagem pública, como `ghcr.io/empresa/app:1.0`, sem build.
Depois, o nome, o tipo (**Web** ou **Worker**), a porta e, se quiser, o **endereço** em `acq.lat`. Por fim, a memória: digite quantos MB a app precisa (qualquer valor a partir de 100 MB, como 231).
## 3. Configure as variáveis [#3-configure-as-variáveis]
Na aba **Variáveis**, cadastre o que a aplicação precisa (ou importe um `.env`). Marque como **segredo** o que não deve aparecer de volta.
## 4. Faça o deploy [#4-faça-o-deploy]
O deploy mostra o log do build ao vivo. Aplicações web entram no ar quando respondem na porta, com HTTPS no endereço escolhido.
## 5. Adicione um banco (opcional) [#5-adicione-um-banco-opcional]
Em **Bancos de dados → Criar banco**, escolha o motor e digite a memória em MB (a partir do mínimo do motor). Na aba **Conexão**, revele a senha, copie a URL e cadastre-a como variável secreta da app (em **Variáveis**). Pela API, `POST …/connect` faz isso de uma vez.
## Próximos passos [#próximos-passos]
* [Domínios próprios](/docs/aplicacoes#domínios-personalizados)
* [Logs, métricas, terminal e arquivos](/docs/aplicacoes-runtime)
* [Automatizar pela API](/docs/api)
---
# Usar com IA
URL: https://0.0.0.0:3002/docs/ia
Leve a documentação do Hydro para o ChatGPT, o Claude, o Cursor ou o seu agente, em Markdown.
As docs do Hydro foram feitas para serem lidas também por agentes de IA.
## Arquivos para agentes [#arquivos-para-agentes]
| Endereço | O que tem |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------ |
| [`/llms.txt`](/llms.txt) | Índice da documentação ([llmstxt.org](https://llmstxt.org)), com o link para o Markdown de cada página |
| [`/llms-full.txt`](/llms-full.txt) | Todas as páginas em Markdown, num arquivo só |
| `/docs/
.mdx` | Uma página em Markdown, como [`/docs/aplicacoes.mdx`](/docs/aplicacoes.mdx) |
| [`/openapi.json`](/openapi.json) | A especificação OpenAPI da API pública |
## Em cada página [#em-cada-página]
No topo de cada página:
* **Copiar como Markdown** copia o texto da página para colar no chat.
* **Abrir em…** abre o ChatGPT ou o Claude já com a página indicada.
## Dicas [#dicas]
* **Dê o token por variável de ambiente.** Crie um [token de API](/docs/api/autenticacao) só para o agente, com o escopo `read` se ele só for consultar, e defina uma validade. Revogue quando terminar.
* **Comece pelo `llms.txt`.** Ele é pequeno e aponta para o resto; o `llms-full.txt` é melhor quando o agente precisa de tudo de uma vez.
* **Use o OpenAPI para gerar código.** Clientes e tipos gerados do `openapi.json` erram menos que rotas escritas à mão.
* **Peça confirmação para o que apaga.** Excluir apps e bancos e restaurar backups não tem volta: peça ao agente que confirme com você antes.
---
# Workspaces e permissões
URL: https://0.0.0.0:3002/docs/workspaces
Papéis, convites, transferência de propriedade e limites.
Todo recurso pertence a um **workspace**. Uma pessoa pode participar de vários workspaces, com um papel em cada.
**Atalho:** **Ctrl+K** (ou **⌘K** no Mac), ou o campo **Buscar** da barra lateral, abre a busca do painel. Ela leva direto a uma aplicação, a um banco, a qualquer página do workspace ou a outro workspace.
## Papéis [#papéis]
| Papel | Resumo |
|---|---|
| Proprietário (`owner`) | Controle total, incluindo excluir o workspace e gerenciar proprietários. |
| Administrador (`admin`) | Gerencia membros, convites e configurações. Não exclui o workspace. |
| Desenvolvedor (`developer`) | Opera recursos e tokens de API. Não gerencia membros. |
| Leitor (`viewer`) | Somente leitura. |
| Permissão | Proprietário | Administrador | Desenvolvedor | Leitor |
|---|---|---|---|---|
| `workspace:read` | ✓ | ✓ | ✓ | ✓ |
| `workspace:update` | ✓ | ✓ | — | — |
| `workspace:delete` | ✓ | — | — | — |
| `members:read` | ✓ | ✓ | ✓ | ✓ |
| `members:invite` | ✓ | ✓ | — | — |
| `members:update` | ✓ | ✓ | — | — |
| `members:remove` | ✓ | ✓ | — | — |
| `api_tokens:read` | ✓ | ✓ | ✓ | — |
| `api_tokens:manage` | ✓ | ✓ | ✓ | — |
| `audit:read` | ✓ | ✓ | — | — |
| `apps:read` | ✓ | ✓ | ✓ | ✓ |
| `apps:manage` | ✓ | ✓ | ✓ | — |
| `integrations:manage` | ✓ | ✓ | — | — |
| `billing:read` | ✓ | ✓ | — | — |
| `billing:manage` | ✓ | — | — | — |
Regras de atribuição:
* **Proprietários** atribuem qualquer papel.
* Os demais só atribuem ou alteram papéis **abaixo** do seu. Um administrador gerencia desenvolvedores e leitores, mas não outros administradores nem proprietários.
* Ninguém altera o próprio papel. Um proprietário que quer sair da função usa **Transferir propriedade**.
* O workspace sempre tem **pelo menos um proprietário**.
## Convites [#convites]
1. Um administrador ou proprietário convida por e-mail, com um papel (exceto proprietário).
2. A pessoa recebe um link válido por 7 dias.
3. Para aceitar, ela entra ou cria uma conta **com o mesmo e-mail** do convite. Criando a conta pelo link do convite, com esse e-mail, ela já entra sem confirmar o e-mail (o convite chegou nele) e volta para o convite para aceitar. Com outro e-mail, o link de confirmação traz a pessoa de volta ao convite, já logada.
Membros e convites pendentes contam para o limite de membros do [plano](/docs/planos-e-quotas). Convites podem ser revogados enquanto estão pendentes.
## Remoção e saída [#remoção-e-saída]
Remover um membro ou sair do workspace também **revoga os tokens de API** da pessoa naquele workspace.
## Workspace suspenso [#workspace-suspenso]
Um workspace suspenso pela equipe da AcquaCloud fica **somente leitura**: alterações respondem `403 workspace_suspended`. Fale com o suporte para entender o motivo.
---
# Planos e quotas
URL: https://0.0.0.0:3002/docs/planos-e-quotas
Como funcionam o período de teste, o pool de recursos do workspace e os limites.
Cada workspace tem um **plano**. O plano define a **memória** que você distribui entre aplicações e bancos, a **vCPU** do workspace e o limite de membros. **Armazenamento e domínios são ilimitados em todo plano**, inclusive no teste. Não há limite de quantidade de recursos, só um tamanho mínimo por tipo.
## CPU: garantida pela memória, dividida nos picos [#cpu-garantida-pela-memória-dividida-nos-picos]
Você não escolhe a vCPU de cada aplicação ou banco:
* **Garantida:** cada recurso tem **1 vCPU por GB de memória**. Assim, 256 MB valem 0,25 vCPU e 2 GB valem 2 vCPU. Aumentar a memória aumenta a CPU garantida.
* **Picos:** quando sobra CPU no workspace, qualquer recurso usa mais que a garantia, até a **vCPU do plano**. Uma app sozinha pode usar toda a vCPU do plano enquanto as outras estão quietas.
* **Disputa:** quando todos precisam ao mesmo tempo, a vCPU do plano é dividida na proporção das garantias. O total do workspace nunca passa da vCPU do plano.
Veja o plano, o uso e os limites em **Workspace → Plano e uso**, ou pela API em `GET /v1/workspaces/{id}/plan`.
## Período de teste [#período-de-teste]
* O **primeiro** workspace que você cria entra em teste. Hoje o teste dura 14 dias, com 1 vCPU, 1 GB de memória e 3 membros.
* Cada pessoa tem um teste só. Os outros workspaces que você criar começam **sem plano** até contratar um.
* Alguns dias antes do fim, os proprietários e administradores do workspace recebem um aviso por e-mail.
* Quando o teste termina, começa uma **carência** de 7 dias: o que já existe continua rodando, mas não é possível criar nem aumentar recursos. A data do fim da carência aparece na página do plano e no e-mail. Quando a carência acaba, as [aplicações](/docs/aplicacoes) são paradas, sem apagar nada, até o workspace assinar um plano.
## Planos pagos [#planos-pagos]
O catálogo está em **Plano e uso**, no site da AcquaCloud e em `GET /v1/plans` (público, sem login; preço mensal em centavos de real e `featured` no plano em destaque). A assinatura é feita em **Plano e uso**; veja [Cobrança](/docs/cobranca).
Quando um plano muda de preço ou de limites, quem já assinou continua com as condições em que entrou.
## Status do plano [#status-do-plano]
| Status | O que significa |
| --------------- | ----------------------------------------------------------------------------- |
| Em teste | Pode criar e aumentar recursos dentro dos limites do teste, até a data de fim |
| Ativo | Plano pago em vigor |
| Teste encerrado | Carência: recursos existentes continuam, nada novo é criado |
| Sem plano | Nenhum recurso novo; só o proprietário como membro |
## Limites [#limites]
A memória do plano vale para o workspace inteiro. Um pedido que não cabe responde `409 quota_exceeded`, dizendo quanto foi pedido e quanto está disponível. A vCPU não é reservada: ela é o teto dos picos, dividido entre todos.
Tamanho mínimo por tipo de recurso (a vCPU é a garantida pela memória; o disco mínimo é técnico e o disco não tem limite no plano):
| Tipo | Memória | vCPU garantida | Disco mínimo |
| ---------- | ------- | -------------- | ------------ |
| Aplicação | 100 MB | 0,1 | 0 |
| PostgreSQL | 256 MB | 0,25 | 1 GB |
| MySQL | 512 MB | 0,25 | 1 GB |
| MariaDB | 256 MB | 0,25 | 1 GB |
| MongoDB | 512 MB | 0,25 | 1 GB |
| Redis | 64 MB | 0,1 | 256 MB |
Abaixo do mínimo, a resposta é `422 resource_below_minimum`. Sem plano em vigor, `403 plan_required`.
**Membros:** membros + convites pendentes contam para o limite. Ao atingir o limite, novos convites respondem `409 member_limit_reached`.
## Acima da quota [#acima-da-quota]
Se o plano mudar para um menor que o uso atual, o workspace fica **acima da quota**. Nada é apagado nem parado: você pode reduzir e remover recursos normalmente, mas não pode criar nem aumentar até voltar a caber no plano.
---
# Aplicações
URL: https://0.0.0.0:3002/docs/aplicacoes
Tipos, origens do código, endereço em acq.lat, deploy, variáveis de ambiente, domínios e backups.
Uma **aplicação** roda dentro de um workspace e usa a memória do [plano](/docs/planos-e-quotas). A CPU vem da memória: 1 vCPU garantida por GB e, nos picos, a CPU livre do workspace até a vCPU do plano.
| Tipo | Para quê | Endereço |
| ---------- | --------------------------------------------------------------------------- | ---------------------------------------------------- |
| **Web** | Sites e APIs: a app escuta numa porta (padrão `3000`, disponível em `PORT`) | `https://.acq.lat`, com HTTPS automático |
| **Worker** | Processos sem porta: bots, filas, tarefas | Nenhum |
O código vem de uma de três **origens**, escolhidas na criação:
* **Upload de .zip:** você envia o código e o Hydro faz o build, detectando a linguagem.
* **Imagem Docker:** uma imagem pública (ex.: `ghcr.io/empresa/bot:1.2.0`), sem build.
* **GitHub:** um repositório conectado, com build no Hydro. Veja [Deploy pelo GitHub](/docs/deploy-github).
## Criar [#criar]
`POST /v1/workspaces/{id}/apps` com nome, tipo, origem e memória (`memoryMb`, qualquer inteiro a partir de 100 MB). A vCPU (`cpuMillis` na resposta) é calculada pela memória.
* O identificador sai do nome (`Minha API` → `minha-api`) e pode ser informado em `slug`. Ele é único no workspace.
* O tamanho é **reservado no plano na hora**: se não couber, a resposta é `409 quota_exceeded`. Mínimo de 100 MB por aplicação.
* Sem plano em vigor, a resposta é `403 plan_required`.
* Criar não sobe nada: o código chega no primeiro deploy.
## Endereço em acq.lat [#endereço-em-acqlat]
Toda aplicação web ganha um endereço `https://.acq.lat` com HTTPS.
* **Escolha o subdomínio** na criação, em `subdomain` (ex.: `minha-loja` → `minha-loja.acq.lat`). Sem ele, o Hydro gera um a partir do identificador, com um sufixo.
* Regras: 3 a 40 caracteres, letras minúsculas, números e hífens, sem hífen no começo, no fim ou repetido. Alguns nomes são reservados.
* O subdomínio é **único em toda a plataforma**: se já estiver em uso, a resposta é `409 subdomain_taken`.
* **Trocar depois:** em **Configurações → Endereço**, ou `PATCH .../apps/{appId}` com `subdomain`. O endereço novo vale na hora e o antigo **para de responder**.
## Deploy [#deploy]
| Origem | Como |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Upload de .zip | `POST .../apps/{appId}/deployments/upload` com o arquivo no corpo (`Content-Type: application/zip`, até 200 MB) |
| Imagem Docker | `POST .../apps/{appId}/deployments` com `{ "image": "..." }` (ou vazio para usar a imagem da app). Só imagens públicas |
| Refazer o deploy | `POST .../apps/{appId}/deployments` vazio numa app por upload: reaproveita o build atual com variáveis e tamanho novos |
* **O que vai no .zip:** só o código. A pasta `node_modules` não é aceita (`400 artifact_has_node_modules`): as dependências são instaladas no build. O .zip também é recusado (`422 artifact_invalid`) com caminhos absolutos ou com `..`, mais de 50 mil entradas, mais de 2 GB descompactados ou compressão suspeita.
* Só **um deploy em andamento** por aplicação (`409 deployment_in_progress`). Um deploy na fila pode ser cancelado (`POST .../deployments/{id}/cancel`).
* Estados: na fila → fazendo build → subindo → no ar. Se falhar, o deploy anterior continua no ar e o motivo aparece em `failureReason`.
* **Logs de build:** `GET .../deployments/{id}/logs?after=0`; leia de novo com `after = nextAfter` até `done`. Cada deploy guarda até 5 MB de log.
* **Rollback:** `POST .../deployments/{id}/rollback` sobe de novo a imagem de um deploy anterior, sem build, enquanto ela estiver disponível (`imageAvailable`). As 3 últimas imagens de cada app ficam guardadas.
## O que a aplicação encontra [#o-que-a-aplicação-encontra]
* **Web:** escute em `0.0.0.0` na porta da variável `PORT`. O deploy só entra no ar quando a porta responde (até 60 s); se não responder, o deploy falha e a versão anterior continua.
* **Pasta do projeto:** gravável e persistente. O que a aplicação grava ali, como um banco SQLite ou uploads, continua entre deploys e reinícios e entra nos backups. `/tmp` serve para temporários (64 MB).
* **Usuário:** a aplicação roda como usuário sem privilégios, dono da pasta do projeto.
* **Variáveis do Hydro:** `PORT` (web), `HYDRO_APP_ID` e `HYDRO_DEPLOYMENT_ID`.
* **Troca sem queda (web):** a versão nova sobe ao lado da antiga e o endereço só muda quando ela responde. Workers param a versão antiga antes de subir a nova, para nunca rodarem dois ao mesmo tempo.
* **Rede:** aplicações e bancos do mesmo workspace ficam numa rede privada; os de outros workspaces não os alcançam. Cada um tem um nome interno, como `minha-api.internal`. A saída para a internet é liberada.
## Variáveis de ambiente [#variáveis-de-ambiente]
`PUT /v1/workspaces/{id}/apps/{appId}/env` substitui o conjunto inteiro (até 100 variáveis, 32 KB por valor).
* Marque como **segredo** o que não deve aparecer de volta: a API e o painel nunca mostram o valor de um segredo (`value: null`). Para manter um segredo sem reenviar o valor, mande-o com valor vazio.
* Tudo é guardado **cifrado** (AES-256-GCM) e nunca entra no build.
* `PORT` e nomes começando com `HYDRO_` são reservados.
* Mudanças valem a partir do próximo deploy (refazer o deploy aplica sem novo build).
## Parar, iniciar e reiniciar [#parar-iniciar-e-reiniciar]
`POST .../apps/{appId}/stop`, `.../start` e `.../restart` (exigem um deploy no ar: `409 app_not_deployed`). Parar tira a app e o endereço do ar, mas mantém o tamanho reservado e a pasta do projeto.
## Estado [#estado]
O `status` vem do servidor em que a app roda: `idle` (sem deploy), `deploying`, `starting`, `running`, `restarting`, `stopped` ou `crashed`. Em `statusDetail` ficam o código de saída, o número de reinícios e as últimas linhas quando o processo cai.
* Se o processo cai, ele é reiniciado automaticamente, com espera crescente.
* Depois de 5 saídas em 10 minutos, a app fica `crashed` e é parada, para não ficar em loop.
## Domínios personalizados [#domínios-personalizados]
Apps web podem responder também num domínio seu, com HTTPS automático.
1. Em **Configurações → Endereço**, adicione o domínio (ex.: `loja.exemplo.com.br`). Até 20 por aplicação.
2. No DNS do domínio, crie um registro **A** para o IP mostrado no painel (e **AAAA**, se houver IPv6) e um **TXT** `_hydro.` com o valor indicado.
3. Clique em **Verificar**. Com A e TXT certos, o domínio fica **Ativo** e o certificado é emitido em seguida.
* O registro A precisa apontar direto para o IP indicado. Um proxy na frente (como o da Cloudflare com a nuvem laranja) impede a emissão do certificado.
* O IP é o mesmo para todos os domínios e não muda se a aplicação trocar de servidor.
* Enquanto o domínio aponta para o Hydro sem estar ligado a uma aplicação, ele mostra a página "Domínio ainda não conectado".
* O Hydro rechecha sozinho domínios **Aguardando DNS** por até 7 dias e os ativos a cada 6 horas.
* Um domínio só pode estar em uma aplicação da plataforma (`409 domain_taken`).
| Método | Caminho |
| ------------- | ------------------------------------------ |
| `GET`, `POST` | `/v1/workspaces/{id}/apps/{appId}/domains` |
| `POST` | `…/domains/{domainId}/verify` |
| `DELETE` | `…/domains/{domainId}` |
## Backups dos arquivos [#backups-dos-arquivos]
A pasta do projeto tem backup diário automático, cifrado e fora do servidor. Em **Configurações → Backups**: fazer backup agora (no máximo um por hora) e restaurar, o que substitui a pasta do projeto pela do backup. Antes de restaurar, o Hydro guarda um backup do estado atual.
## Excluir [#excluir]
`DELETE /v1/workspaces/{id}/apps/{appId}` libera o tamanho no plano e remove a aplicação, as imagens e a pasta do projeto.
## Permissões [#permissões]
| Ação | Papéis |
| --------------------------------------------------------- | ------------------------------------------ |
| Ver aplicações, deploys e variáveis (segredos mascarados) | Todos, inclusive leitor |
| Criar, alterar, fazer deploy, excluir | Desenvolvedor, administrador, proprietário |
---
# Deploy pelo GitHub
URL: https://0.0.0.0:3002/docs/deploy-github
Conectar uma conta ou organização do GitHub, criar apps a partir de um repositório e usar monorepos.
Aplicações com origem **GitHub** pegam o código direto de um repositório, sem upload. O Hydro usa um GitHub App da AcquaCloud: você escolhe em quais repositórios ele entra, e ele só tem **leitura** do código e permissão para marcar o **status** dos commits.
## Conectar e criar [#conectar-e-criar]
1. **Configurações do workspace → GitHub → Conectar conta do GitHub.** No GitHub, escolha a conta ou organização e os repositórios. É preciso ser proprietário ou administrador do workspace.
2. **Aplicações → Criar aplicação → GitHub.** Escolha o repositório, a branch, a pasta (num monorepo) e se cada push faz deploy. O primeiro deploy sai na criação, com o último commit da branch.
3. **Na aplicação:** "Deploy do último commit", "Deploy de outro commit" e "Refazer deploy sem build". Cada deploy mostra o commit, com link para o GitHub. Branch, pasta e deploy automático mudam em **Configurações → Repositório**.
## Opções do repositório [#opções-do-repositório]
| Campo | Padrão | O que faz |
| --------------- | ----------- | ------------------------------------------------------------------------------- |
| `branch` | — | Branch acompanhada. Precisa existir no repositório |
| `rootDir` | `""` (raiz) | Pasta da app num **monorepo**, como `apps/loja` |
| `autoDeploy` | `true` | Faz deploy a cada push na branch |
| `watchRootOnly` | `false` | Com `rootDir`, só faz deploy automático quando o push muda arquivos dessa pasta |
O repositório é guardado pelo **ID**: renomeá-lo no GitHub não quebra a app. Branch, pasta e as duas opções mudam com `PATCH .../apps/{appId}` em `github`.
## Deploy [#deploy]
| Gatilho | Como |
| ---------- | ------------------------------------------------------------------------------------------------------- |
| **Manual** | `POST .../apps/{appId}/deployments` com `{}` (último commit da branch) ou `{ "commitSha": "<40 hex>" }` |
| **Push** | Automático com `autoDeploy`: cada push na branch faz deploy do commit novo |
Durante o deploy, o commit ganha o status **pending** no GitHub, com o contexto `Hydro / `, e termina como **success** ou **failure**, com o motivo.
### Pushes seguidos [#pushes-seguidos]
* Deploy ainda na fila: o push novo **substitui** o anterior, que fica cancelado.
* Deploy já em build: o push novo **espera** o build terminar e faz deploy em seguida. Só o push mais recente vale.
### Quando o push não faz deploy [#quando-o-push-não-faz-deploy]
* Push de tag, branch apagada ou outra branch.
* `autoDeploy` desligado ou app sem acesso ao repositório.
* `watchRootOnly` ligado e nenhum arquivo alterado dentro de `rootDir`.
## Acesso perdido [#acesso-perdido]
Se a instalação for suspensa ou removida no GitHub, ou o repositório sair dela, as apps ficam com `github.accessLost: true`: continuam no ar, mas pushes são ignorados e o deploy manual responde `404 github_repository_not_found`. Reconectar a conta restaura o acesso.
## Limitações [#limitações]
* Submódulos e arquivos do Git LFS não vêm no código baixado.
* Sem previews de pull request.
---
# Logs, métricas, terminal e arquivos
URL: https://0.0.0.0:3002/docs/aplicacoes-runtime
Acompanhar a aplicação no ar, ver o consumo, abrir um shell no container e ver ou editar o código do projeto.
Com a aplicação no ar, o detalhe dela no painel ganha quatro abas de runtime. Logs, métricas, terminal e arquivos vêm do servidor onde a app roda, ao vivo.
| Aba | Quem vê | O que faz |
| ------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------- |
| **Logs** | Todos do workspace (`apps:read`) | Console ao vivo: as últimas 1000 linhas e o que chegar |
| **Métricas** | Todos (`apps:read`) | Consumo agora e histórico de até 30 dias |
| **Terminal** | Owner, admin e developer (`apps:manage`) | Shell dentro do container |
| **Arquivos** | Owner, admin e developer (`apps:manage`) | A pasta do projeto no servidor, ao vivo: navegar, baixar, editar, enviar, renomear e apagar |
## Logs [#logs]
* `stdout` e `stderr` da aplicação, com as cores ANSI. Linhas de `stderr` têm uma marca à esquerda.
* **Pausar** segura a tela sem perder o que chega; **Seguir** traz o atrasado. O filtro procura texto nas linhas já recebidas.
* **Erros e avisos** ficam destacados (vermelho e amarelo), reconhecidos pelo texto da linha (`error`, `fatal`, `exception`, `warn`, `deprecated`…) ou pelo campo `level` de logs em JSON (como o pino). O filtro **Todos / Avisos / Erros** mostra só um nível, com a contagem de cada um.
* O histórico é o que o servidor guarda da app (cerca de 30 MB por container, rotacionado). Depois de um deploy, o console segue o container novo.
* A conexão volta sozinha se cair.
## Métricas [#métricas]
* **Agora**, a cada 2 s: CPU (em vCPU, comparada ao tamanho da app), memória (com o limite), rede (entrada e saída por segundo), disco e número de processos.
* **Histórico**: 1 hora (pontos de 1 minuto), 24 horas (5 minutos), 7 dias (30 minutos) e 30 dias (2 horas), com média e pico de CPU e memória, a linha do limite do tamanho, rede e disco por segundo e o tamanho da pasta do projeto.
* O agente mede a cada 30 s. Intervalos sem a app rodando aparecem como buracos no gráfico.
* Pela API: `GET /v1/workspaces/{id}/apps/{appId}/metrics?range=1h|24h|7d|30d`.
* **Na lista de aplicações**, cada app no ar mostra a tendência da última hora de CPU e memória, em % do tamanho dela. Pela API: `GET /v1/workspaces/{id}/usage/apps` (12 pontos de 5 minutos por app; `null` onde não houve amostra).
* **Tempo no ar:** o detalhe da app e do banco mostra há quanto tempo estão rodando (`runningSince` na API). A contagem recomeça num deploy novo, num reinício ou quando a app volta depois de parar ou cair.
## Terminal [#terminal]
* **Abrir terminal** inicia um shell (`bash` se a imagem tiver, senão `sh`) dentro do container, com o **mesmo usuário e as mesmas restrições da aplicação**: o que a app acessa, o terminal acessa; nada além disso.
* Imagens sem shell não abrem terminal (o painel avisa).
* Até 2 terminais abertos por aplicação. A sessão fecha depois de 15 minutos sem digitar ou 4 horas no total.
* Abertura e fim ficam na **atividade** do workspace, com duração. O que é digitado e o que aparece na tela **não são gravados**.
* Só funciona pelo painel, com a sessão de uma pessoa (tokens de API não abrem terminal).
## Arquivos [#arquivos]
A aba **Arquivos** mostra a **pasta do projeto no servidor, ao vivo**: o código do último deploy e tudo o que a aplicação grava enquanto roda, como um banco SQLite, uploads ou arquivos de configuração gerados. Vale para apps por upload, do GitHub e por imagem, e funciona com a aplicação parada. A pasta só existe depois do primeiro deploy.
Dá para navegar, baixar, editar texto (até 1 MB), enviar arquivos (até 100 MB cada), criar pastas, renomear e apagar. Os arquivos abrem num editor de código (o mesmo motor do VS Code), com realce de sintaxe por linguagem e **Ctrl/Cmd+S** para salvar. Tudo é servido pelo próprio painel, sem CDN de terceiros, e cada arquivo mostra um ícone pelo tipo, quando ele é conhecido.
O que muda aqui **vale na hora, direto no servidor**:
* **Código editado só roda depois de reiniciar.** O processo que já está no ar não relê o arquivo.
* **Arquivo alterado aqui é mantido nos próximos deploys**, como o que a própria aplicação altera ([pasta do projeto](/docs/aplicacoes)). O log do deploy lista os mantidos.
* **Para voltar a seguir o código enviado,** apague o arquivo antes do próximo deploy: ele volta na sincronização.
* **Apagar um arquivo do código** só vale até o próximo deploy, que o traz de volta.
* **Mudar de vez o código de todos os deploys:** envie um .zip novo ou faça push no GitHub.
Envios, edições, renomeações, novas pastas e exclusões ficam na atividade do workspace.
### Pela API [#pela-api]
| Rota | O que faz |
| --------------------------------------- | ----------------------------------------------------------------------------------- |
| `GET .../apps/{appId}/files?path=` | Lista uma pasta |
| `GET .../files/content?path=` | Baixa um arquivo |
| `PUT .../files/content?path=` | Envia o corpo cru (`application/octet-stream`, com `Content-Length`) |
| `POST .../files/directories` | Cria pasta (`{ "path": "uploads" }`) |
| `POST .../files/rename` | Renomeia ou move (`{ "from": "a.txt", "to": "b.txt" }`); o destino não pode existir |
| `DELETE .../files?path=&recursive=true` | Apaga (pasta com conteúdo exige `recursive=true`) |
Os caminhos são relativos à raiz do projeto e não aceitam `..`. A pasta `.hydro/` guarda o controle da sincronização: não aparece na lista e responde `404`.
| Erro | Quando |
| ---------------------------------------------- | ------------------------------------------------ |
| `404 file_not_found` | O caminho não existe (ou é `.hydro/`) |
| `409 file_exists` | O destino de uma criação ou renomeação já existe |
| `409 directory_not_empty` | Pasta com conteúdo, sem `recursive=true` |
| `413 file_too_large` | Arquivo maior que o limite |
| `409 app_not_deployed` / `409 app_not_running` | A pasta ainda não existe (sem deploy) |
| `503 node_offline` | Servidor fora do ar |
## Como a conexão funciona [#como-a-conexão-funciona]
Logs, consumo e terminal usam um WebSocket direto com a API. O painel pede um endereço de uso único, válido por 30 segundos (`POST .../apps/{appId}/streams`), e abre a conexão nele. O endereço não carrega cookies; firewalls corporativos precisam permitir WebSocket para o endereço da API.
---
# Bancos de dados
URL: https://0.0.0.0:3002/docs/bancos-de-dados
PostgreSQL, MySQL, Redis e MongoDB gerenciados, na rede privada do workspace e com acesso externo opcional.
O Hydro cria e mantém bancos de dados ao lado das suas aplicações. Cada banco roda isolado, com volume próprio, e só é acessível pela **rede privada do workspace**, a menos que você ligue o acesso externo.
| Motor | Versões | Porta | Variável ao conectar |
| ---------- | ---------- | ----- | -------------------- |
| PostgreSQL | 18, 17, 16 | 5432 | `DATABASE_URL` |
| MySQL | 8.4 | 3306 | `DATABASE_URL` |
| Redis | 8, 7.4 | 6379 | `REDIS_URL` |
| MongoDB | 8, 7 | 27017 | `MONGODB_URL` |
## Criar [#criar]
`POST /v1/workspaces/{id}/databases` com nome, motor, versão e memória (`memoryMb`). No painel: **Bancos de dados → Criar banco**.
* A memória sai da quota do plano. A vCPU vem da memória (1 vCPU garantida por GB) e, nos picos, o banco divide a vCPU do plano com as apps. O disco não conta na quota.
* A memória é qualquer número inteiro de MB a partir do mínimo do motor: PostgreSQL 256 MB, MySQL e MongoDB 512 MB, Redis 64 MB.
* Mudar o tamanho (`PATCH`) reinicia o banco; os dados ficam no volume.
## Rede privada [#rede-privada]
* Cada banco e cada aplicação têm um nome na rede do workspace: `` e `.internal`. Ex.: `postgres://hydro:…@meu-banco.internal:5432/hydro`.
* Os nomes são únicos no workspace entre apps e bancos (`409 name_taken`).
* Bancos e apps de outros workspaces não resolvem nem conectam.
## Credenciais [#credenciais]
* Usuário e banco padrão: `hydro`. A senha é gerada pelo Hydro e guardada cifrada.
* `GET …/credentials` devolve host, porta, usuário, banco e a URL de conexão. Cada consulta fica registrada na atividade do workspace.
* **Trocar a senha** (`POST …/rotate-password`) aplica uma senha nova sem perder dados. Apps conectadas precisam ser conectadas de novo e reiniciadas.
## Conectar a uma aplicação [#conectar-a-uma-aplicação]
`POST …/connect` com `{ appId, variable? }` grava a URL de conexão interna como **variável secreta** da app (a padrão da tabela acima, ou outro nome). Vale no próximo deploy ou reinício da app.
## Acesso externo [#acesso-externo]
Desligado por padrão. Ao ligar (`PUT …/public` com `{ enabled: true, allowedCidrs }`):
* **Qualquer IP:** deixe `allowedCidrs` vazio. Conecta de qualquer lugar, com a senha do banco.
* **Só IPs específicos (mais seguro):** informe as faixas, como `["203.0.113.10/32", "198.51.100.0/24"]`. Só elas conectam.
* O banco ganha uma porta própria no IP público do servidor.
* A conexão é **sempre com TLS**. O certificado é autoassinado: use `sslmode=require` (PostgreSQL), `ssl-mode=REQUIRED` (MySQL), `rediss://` (Redis) ou `tls=true&tlsAllowInvalidCertificates=true` (MongoDB). Conexões sem TLS são recusadas.
A URL externa aparece nas credenciais quando o acesso está ligado.
## Backups [#backups]
Cada banco tem backup **diário automático**, guardado fora do servidor e cifrado. Na aba **Backups**:
* **Fazer backup agora:** um backup manual, no máximo um por hora.
* **Restaurar em um banco novo:** cria outro banco com os dados do backup. O original fica intacto.
* **Restaurar neste banco:** substitui os dados atuais pelos do backup. Antes, o Hydro faz um backup do estado atual; se ele falhar, nada é restaurado.
Guardamos os 7 diários mais recentes, os manuais por 30 dias e os feitos antes de uma restauração por 7 dias. Ao excluir o banco, os backups ainda ficam 7 dias. O espaço dos backups não conta no plano.
## Logs e consumo [#logs-e-consumo]
Logs e consumo ao vivo funcionam como nas aplicações (veja [Logs, métricas, terminal e arquivos](/docs/aplicacoes-runtime)). Bancos não têm terminal nem aba de arquivos.
## API [#api]
| Método | Caminho |
| ------------------------ | --------------------------------------------------------- |
| `GET`, `POST` | `/v1/workspaces/{id}/databases` |
| `GET`, `PATCH`, `DELETE` | `/v1/workspaces/{id}/databases/{databaseId}` |
| `POST` | `…/{databaseId}/start`, `stop`, `restart` |
| `GET` | `…/{databaseId}/credentials` |
| `POST` | `…/{databaseId}/rotate-password` |
| `POST` | `…/{databaseId}/connect` com `{ appId, variable? }` |
| `PUT` | `…/{databaseId}/public` com `{ enabled, allowedCidrs }` |
| `GET`, `POST` | `…/{databaseId}/backups` e `…/backups/{backupId}/restore` |
Erros específicos: `name_taken`, `workspace_node_full` (sem espaço para o banco), `database_not_ready` (banco ainda sendo criado, ou parado ao trocar a senha) e `public_port_exhausted`.
---
# Cobrança
URL: https://0.0.0.0:3002/docs/cobranca
Assinar um plano, pagar com Pix, boleto ou cartão, mudar de plano e o que acontece quando uma fatura atrasa.
O Hydro cobra por assinatura mensal, processada pelo **Asaas**. Cada fatura tem uma página de pagamento onde você escolhe **Pix, boleto ou cartão**.
## Assinar [#assinar]
Em **Plano e uso**, o owner do workspace escolhe um plano e clica em **Assinar**.
1. Na primeira vez, informe os dados de cobrança: nome ou razão social, CPF ou CNPJ e e-mail. Eles vão para o Asaas, que emite as cobranças.
2. A página de pagamento da primeira fatura abre numa aba nova.
3. **O plano passa a valer quando o pagamento é confirmado.** Até lá, o workspace continua como estava (em teste, por exemplo). Cartão e Pix confirmam em instantes; boleto, em até 3 dias úteis.
As próximas faturas chegam todo mês, no mesmo dia, por e-mail do Asaas.
## Mudar de plano [#mudar-de-plano]
* **Para um plano maior:** os limites novos valem na hora. A diferença dos dias que faltam no período vem numa cobrança à parte, com vencimento em 3 dias (abaixo de R$ 5,00 não há cobrança). As próximas faturas já vêm com o valor novo.
* **Para um plano menor:** vale no próximo ciclo. Só é possível se o que está em uso couber no plano novo (`409 plan_usage_exceeds`, com o que reduzir).
## Cancelar [#cancelar]
O cancelamento vale no **fim do período pago**, e até lá dá para desfazer. Depois, o workspace entra na mesma carência do fim do teste: os recursos continuam rodando por alguns dias, sem criar nem aumentar nada, e depois param. Nada é apagado.
## Fatura em atraso [#fatura-em-atraso]
1. No vencimento sem pagamento, o workspace fica **em atraso**, com aviso no painel e por e-mail.
2. Durante a **carência de 7 dias**, tudo continua rodando e os deploys funcionam, mas não dá para criar recursos nem aumentar o tamanho (`403 billing_past_due`).
3. No fim da carência, **apps e bancos param**. Nada é apagado.
4. **Pagou, volta:** o que parou por falta de pagamento religa sozinho assim que o pagamento é confirmado.
O mesmo vale para o fim do período de teste: apps e bancos param no fim da carência e religam quando um plano é assinado.
## Quem pode [#quem-pode]
| Ação | Papéis |
| --------------------------------- | ------------- |
| Ver plano, assinatura e faturas | Owner e admin |
| Assinar, mudar de plano, cancelar | Só o owner |
## API [#api]
| Método | Caminho |
| ------ | ------------------------------------------------ |
| `GET` | `/v1/workspaces/{id}/billing` |
| `POST` | `…/billing/subscribe` com `{ planId, profile? }` |
| `POST` | `…/billing/change-plan` com `{ planId }` |
| `POST` | `…/billing/cancel` e `…/billing/cancel/revert` |
Erros específicos: `billing_not_configured` (cobrança desligada na plataforma), `billing_gateway_error` (o Asaas não respondeu), `billing_active`, `billing_pending_payment`, `billing_profile_required`, `billing_past_due`, `plan_usage_exceeds`, `plan_not_purchasable` e `same_plan`.
---
# Hospedar um bot do Discord
URL: https://0.0.0.0:3002/docs/guias/bot-discord
Coloque um bot feito com discord.js ou discord.py online 24/7 no Hydro, com o token como segredo.
Um bot do Discord abre uma conexão com o gateway do Discord e fica ouvindo eventos. Ele não recebe conexões, então roda como **worker**: um processo contínuo, sem porta pública, que o Hydro reinicia se cair.
## Antes de começar [#antes-de-começar]
* O token do bot, em **Discord Developer Portal → Applications → seu app → Bot → Reset Token**.
* Os **Privileged Gateway Intents** que o bot usa ligados na mesma página (por exemplo, *Message Content Intent* para ler mensagens).
* O código num repositório do GitHub ou num `.zip` (sem `node_modules` nem `.venv`).
## 1. Leia o token da variável de ambiente [#1-leia-o-token-da-variável-de-ambiente]
Nunca deixe o token no código. Leia de `DISCORD_TOKEN`:
```js title="index.js (discord.js)"
import { Client, Events, GatewayIntentBits } from 'discord.js';
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.once(Events.ClientReady, (ready) => console.log(`Online como ${ready.user.tag}`));
client.login(process.env.DISCORD_TOKEN);
```
```python title="main.py (discord.py)"
import os
import discord
client = discord.Client(intents=discord.Intents.default())
@client.event
async def on_ready():
print(f"Online como {client.user}")
client.run(os.environ["DISCORD_TOKEN"])
```
## 2. Garanta o comando de início [#2-garanta-o-comando-de-início]
* **Node.js:** um script `start` no `package.json`, como `"start": "node index.js"`. O gerenciador (npm, pnpm, yarn ou bun) vem do lockfile.
* **Python:** um `main.py` na raiz e as dependências no `requirements.txt` (ou `pyproject.toml` com Poetry ou uv).
## 3. Crie o worker [#3-crie-o-worker]
Em **Aplicações → Criar aplicação**, escolha a origem (GitHub, `.zip` ou imagem Docker), o tipo **Worker** e a memória. Bots pequenos costumam rodar bem com 128 a 256 MB; acompanhe o consumo em **Métricas** e ajuste.
## 4. Cadastre o token e faça o deploy [#4-cadastre-o-token-e-faça-o-deploy]
Na aba **Variáveis**, crie `DISCORD_TOKEN` marcada como **segredo** e faça o deploy. Nos **Logs**, aparece a linha `Online como …` quando o bot conecta.
## Erros comuns [#erros-comuns]
| Sintoma | Causa |
| -------------------------------- | ------------------------------------------------------------------------------ |
| `Used disallowed intents` | O código pede um intent privilegiado que não está ligado no Developer Portal |
| `An invalid token was provided` | Token errado ou com espaço sobrando na variável. Gere outro e atualize |
| O bot conecta e não lê mensagens | Falta o *Message Content Intent*, no código e no portal |
| A app fica `crashed` | O processo caiu 5 vezes em 10 minutos. As últimas linhas do log mostram o erro |
## Dados do bot [#dados-do-bot]
Arquivos que o bot grava, como um banco SQLite, ficam na **pasta do projeto**, que continua entre deploys e entra no backup diário ([detalhes](/docs/aplicacoes)). Para muitos acessos simultâneos, use um [banco gerenciado](/docs/bancos-de-dados).
---
# Hospedar um bot do WhatsApp
URL: https://0.0.0.0:3002/docs/guias/bot-whatsapp
Rode um bot com Baileys 24/7 no Hydro sem perder a sessão a cada deploy.
O ponto delicado de um bot do WhatsApp fora do computador é a **sessão**: se ela se perde, o bot pede o QR code de novo. No Hydro, grave a sessão na **pasta do projeto**, que continua entre deploys e reinícios e entra no backup diário.
## 1. Grave a sessão numa pasta do projeto [#1-grave-a-sessão-numa-pasta-do-projeto]
Com o Baileys, use um caminho relativo, dentro do projeto:
```js title="index.js"
import makeWASocket, { useMultiFileAuthState } from '@whiskeysockets/baileys';
const { state, saveCreds } = await useMultiFileAuthState('./auth');
const sock = makeWASocket({ auth: state, printQRInTerminal: true });
sock.ev.on('creds.update', saveCreds);
```
Não versione a pasta `auth` no Git: ela é criada no servidor no primeiro acesso.
## 2. Crie o worker [#2-crie-o-worker]
Em **Aplicações → Criar aplicação**, escolha a origem, o tipo **Worker** e a memória. O comando de início é o script `start` do `package.json`.
## 3. Faça o deploy e escaneie o QR code [#3-faça-o-deploy-e-escaneie-o-qr-code]
Faça o deploy e abra os **Logs**: o QR code impresso pela biblioteca aparece ali. No celular do número do bot, abra **Aparelhos conectados → Conectar um aparelho** e escaneie.
Nos próximos deploys e reinícios, o bot reaproveita a sessão da pasta `auth`.
## whatsapp-web.js [#whatsapp-webjs]
O whatsapp-web.js controla um Chromium. Publique uma **imagem Docker** com o navegador e as bibliotecas dele (origem *Imagem Docker*) e reserve mais memória. Guarde a pasta `.wwebjs_auth` dentro do projeto do mesmo jeito.
## Boas práticas [#boas-práticas]
* Bibliotecas não oficiais podem levar a bloqueios do número se o uso violar os termos do WhatsApp. Para uso comercial em escala, prefira a API oficial (Cloud API), que recebe webhooks: rode como aplicação **Web**, com HTTPS automático.
* O celular principal precisa entrar na conta de tempos em tempos para manter os aparelhos conectados.
* Chaves de outros serviços (OpenAI, banco) vão como [variáveis secretas](/docs/guias/variaveis-e-tokens).
---
# Hospedar um bot do Telegram
URL: https://0.0.0.0:3002/docs/guias/bot-telegram
Long polling como worker ou webhook com HTTPS automático, com Telegraf, grammY, python-telegram-bot ou aiogram.
O Telegram entrega as mensagens de dois jeitos, e o Hydro tem um tipo de aplicação para cada um:
| Modo | Tipo da aplicação | Quando usar |
| ---------------- | ----------------- | ------------------------------------------------------------------------ |
| **Long polling** | Worker | O mais simples: o bot busca mensagens novas o tempo todo, sem endereço |
| **Webhook** | Web | Bots muito usados: o Telegram chama o seu endereço HTTPS a cada mensagem |
## Long polling (worker) [#long-polling-worker]
```python title="main.py (python-telegram-bot)"
import os
from telegram.ext import Application, CommandHandler
async def start(update, context):
await update.message.reply_text("Olá!")
app = Application.builder().token(os.environ["TELEGRAM_BOT_TOKEN"]).build()
app.add_handler(CommandHandler("start", start))
app.run_polling()
```
Crie uma aplicação **Worker**, cadastre `TELEGRAM_BOT_TOKEN` como segredo em **Variáveis** e faça o deploy.
Workers rodam uma cópia por vez, inclusive no deploy, então não aparece o erro `Conflict: terminated by other getUpdates request`. Se ele aparecer, o bot está rodando também em outro lugar (como o seu computador).
## Webhook (web) [#webhook-web]
1. Crie uma aplicação **Web**. Ela ganha um endereço `https://.acq.lat` com HTTPS automático (ou use um [domínio próprio](/docs/aplicacoes#domínios-personalizados)).
2. A aplicação precisa escutar em `0.0.0.0`, na porta da variável `PORT`.
3. Depois do deploy, registre o endereço no Telegram:
```bash
curl "https://api.telegram.org/bot/setWebhook?url=https://.acq.lat/webhook"
```
Use um caminho difícil de adivinhar ou o parâmetro `secret_token` do `setWebhook` para conferir que a chamada veio do Telegram.
## Bibliotecas [#bibliotecas]
* **Node.js:** Telegraf ou grammY, iniciados pelo script `start` do `package.json`.
* **Python:** python-telegram-bot ou aiogram, com um `main.py` na raiz e as dependências no `requirements.txt`.
---
# Variáveis de ambiente e tokens
URL: https://0.0.0.0:3002/docs/guias/variaveis-e-tokens
Como guardar o token do bot, senhas e chaves de API com segurança no Hydro.
Token de bot, senha de banco e chave de API nunca devem ficar no código nem no repositório. No Hydro, eles vão como **variáveis de ambiente** da aplicação.
## Cadastrar [#cadastrar]
Na aba **Variáveis** da aplicação:
* crie cada variável com nome e valor, ou **importe um `.env`** inteiro;
* marque como **segredo** o que não deve aparecer de novo: o valor fica cifrado com AES-256-GCM e não é mostrado de volta, nem no painel nem na API.
As variáveis chegam à aplicação como variáveis de ambiente comuns. Elas não entram no build, só no processo em execução.
## Ler no código [#ler-no-código]
| Linguagem | Como ler |
| --------- | ----------------------------- |
| Node.js | `process.env.DISCORD_TOKEN` |
| Python | `os.environ["DISCORD_TOKEN"]` |
| Go | `os.Getenv("DISCORD_TOKEN")` |
## Trocar um token vazado [#trocar-um-token-vazado]
1. Gere um token novo no serviço (Discord Developer Portal, BotFather, painel da API).
2. Atualize a variável no Hydro.
3. Reinicie a aplicação ou faça um deploy para o processo ler o valor novo.
## O que não fazer [#o-que-não-fazer]
* Não versione o `.env`: coloque-o no `.gitignore`.
* Não imprima o token nos logs.
* Não compartilhe o mesmo token entre bots diferentes.
---
# Convenções da API
URL: https://0.0.0.0:3002/docs/api
Endereço, versionamento, formato, erros, paginação, idempotência e limites.
Base URL: `https://api.acquacloud.com.br`. Tudo o que está documentado aqui funciona com um [token de API](/docs/api/autenticacao). A lista completa de rotas está na [referência](/docs/api/referencia).
## Versionamento [#versionamento]
Toda rota começa com a versão: `/v1/...`. Mudanças incompatíveis criam `/v2/...`, e a versão anterior continua disponível até ser descontinuada com aviso prévio. Campos novos em respostas não contam como mudança incompatível: ignore campos desconhecidos.
## Formato [#formato]
* JSON em UTF-8. Corpo de até 100 KB (uploads de .zip e de arquivos têm limites próprios).
* Datas em ISO 8601 com fuso (`2026-10-06T22:00:00.000Z`).
* IDs são UUIDv7.
* Toda resposta traz `X-Request-Id`. Você pode enviar o seu (8 a 64 caracteres `[A-Za-z0-9_-]`) para correlacionar com os seus logs.
## Erros [#erros]
Erros seguem a [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457), com `Content-Type: application/problem+json`:
```json
{
"title": "Alguns campos estão inválidos.",
"status": 400,
"code": "validation_failed",
"requestId": "0b9f…",
"errors": [{ "path": "memoryMb", "message": "Mínimo: 100 MB", "code": "custom" }]
}
```
Baseie a lógica no campo **`code`**, que é estável. `title` e `detail` são textos para pessoas e podem mudar. Veja os [códigos de erro](/docs/api/erros).
Um recurso de outro workspace responde **404**, não 403: a API não revela a existência de recursos aos quais você não tem acesso.
## Paginação [#paginação]
Listas grandes usam cursor: `?limit=25&cursor=…`, com a resposta `{ "data": [...], "nextCursor": "…" }`. `limit` vai de 1 a 100 (padrão 25). Quando `nextCursor` vier `null`, não há mais páginas.
## Idempotência [#idempotência]
Requisições `POST` aceitam o header `Idempotency-Key` (8 a 200 caracteres) para retentativas seguras:
| Situação | Resposta |
| -------------------------------------- | ---------------------------------------------------- |
| Mesma chave e mesmo corpo | A resposta original, com `Idempotent-Replayed: true` |
| Mesma chave e corpo diferente | `422 idempotency_conflict` |
| Primeira requisição ainda em andamento | `409 idempotency_in_progress` |
| Erro na primeira tentativa | A chave é liberada para nova tentativa |
As chaves valem por 24 horas, por token.
## Limites de requisição [#limites-de-requisição]
* 300 requisições por minuto por IP, com os headers `X-RateLimit-Limit`, `X-RateLimit-Remaining` e `X-RateLimit-Reset`.
* Ao exceder, a API responde `429 rate_limited` com `Retry-After`.
---
# Autenticação
URL: https://0.0.0.0:3002/docs/api/autenticacao
Tokens de API por workspace, escopos e boas práticas.
A API pública usa **tokens de API**. Crie um em **Workspace → Tokens de API** no painel. O valor completo (`hydro_pat_…`) aparece **uma única vez**. Envie-o no header `Authorization`:
```bash
curl https://api.acquacloud.com.br/v1/workspaces \
-H "Authorization: Bearer hydro_pat_XXXXXXXX"
```
## Escopo e permissões [#escopo-e-permissões]
* **Um workspace:** o token só enxerga o workspace em que foi criado. Os outros respondem 404.
* **Escopos:** `read` (consultas) e `write` (alterações).
* **Permissão efetiva:** o escopo do token limitado ao papel **atual** de quem o criou. Se a pessoa for rebaixada, o token perde o acesso na hora; se ela sair do workspace, o token é revogado.
* **Validade:** opcional, de 1 a 365 dias. O painel mostra o último uso.
## Boas práticas [#boas-práticas]
* Guarde o token num gerenciador de segredos, nunca no código.
* Use `read` para integrações que só consultam.
* Um token vazado deve ser revogado no painel: a revogação vale na hora.
Login, conta, membros, convites, cobrança e a criação de tokens são feitos pelo painel e não fazem parte da API pública.
---
# Códigos de erro
URL: https://0.0.0.0:3002/docs/api/erros
Os códigos estáveis do campo code nos erros da API do Hydro, com a mensagem padrão de cada um e os status HTTP mais comuns.
O campo `code` dos erros é estável e deve guiar a lógica do seu cliente. A tabela é gerada a partir do código-fonte e está sempre atualizada.
| code | Mensagem padrão |
|---|---|
| `validation_failed` | Alguns campos estão inválidos. |
| `bad_request` | Requisição inválida. |
| `unauthenticated` | Faça login para continuar. |
| `forbidden` | Você não tem permissão para esta ação. |
| `not_found` | Recurso não encontrado. |
| `conflict` | O recurso foi alterado por outra operação. Tente novamente. |
| `rate_limited` | Muitas tentativas. Aguarde um pouco e tente novamente. |
| `internal_error` | Erro interno. Tente novamente em instantes. |
| `invalid_token` | Link inválido ou expirado. |
| `workspace_suspended` | Este workspace está suspenso. |
| `workspace_limit_reached` | Você atingiu o limite de workspaces. |
| `slug_taken` | Este identificador já está em uso. |
| `last_owner` | O workspace precisa de pelo menos um proprietário. |
| `role_not_allowed` | Você não pode atribuir este papel. |
| `member_limit_reached` | O workspace atingiu o limite de membros. |
| `ingress_node_exists` | Já existe um ingresso central. Crie o node sem ingresso e faça a troca na página dele, depois que ele estiver no ar. |
| `agent_unavailable` | O binário do agente não está disponível neste servidor. |
| `payload_too_large` | Conteúdo maior que o permitido. |
| `quota_exceeded` | O workspace não tem recursos disponíveis no plano para este pedido. |
| `resource_below_minimum` | O tamanho pedido está abaixo do mínimo para este tipo de recurso. |
| `plan_required` | O workspace precisa de um plano ativo para criar ou aumentar recursos. |
| `plan_archived` | Este plano está arquivado. |
| `plan_order_stale` | O catálogo mudou enquanto você reordenava. Recarregue e tente de novo. |
| `app_slug_taken` | Já existe uma aplicação com este identificador no workspace. |
| `deployment_in_progress` | Já há um deploy em andamento para esta aplicação. |
| `artifact_too_large` | O arquivo passa do tamanho máximo permitido. |
| `artifact_invalid` | Envie um arquivo .zip válido com o código da aplicação. |
| `artifact_has_node_modules` | O .zip tem a pasta node_modules. Remova-a e envie de novo: as dependências são instaladas no build. |
| `image_not_found` | Imagem não encontrada ou não é pública. |
| `rollback_unavailable` | A imagem deste deploy não está mais disponível para rollback. |
| `app_not_deployed` | A aplicação ainda não tem um deploy no ar. |
| `build_failed` | O build falhou. Veja os logs do deploy. |
| `workspace_has_apps` | Exclua as aplicações do workspace antes de excluí-lo. |
| `apps_unavailable` | Aplicações indisponíveis: a plataforma ainda não foi configurada para elas. |
| `github_installation_not_found` | Conta do GitHub não encontrada ou sem acesso neste workspace. |
| `github_repository_not_found` | Repositório não encontrado ou fora do acesso do Hydro. |
| `github_unavailable` | O GitHub não respondeu. Tente de novo em instantes. |
| `cloudflare_token_invalid` | O token da Cloudflare é inválido ou não tem acesso à zona do domínio das aplicações. |
| `cloudflare_unavailable` | A Cloudflare não respondeu. Tente de novo em instantes. |
| `app_not_running` | A aplicação não está rodando. Inicie ou faça um deploy primeiro. |
| `file_not_found` | Arquivo ou pasta não encontrado. |
| `file_too_large` | Arquivo grande demais. |
| `file_exists` | Já existe um arquivo ou pasta com esse nome. |
| `path_invalid` | Caminho inválido. |
| `directory_not_empty` | A pasta não está vazia. |
| `name_taken` | Já existe uma aplicação ou banco com esse nome interno neste workspace. |
| `workspace_node_full` | O servidor deste workspace não tem espaço para esse tamanho. Reduza o tamanho ou fale com o suporte. |
| `workspace_node_unavailable` | O servidor deste workspace está indisponível agora (manutenção ou fora do ar). Tente de novo em instantes ou fale com o suporte. |
| `workspace_node_no_ingress` | O servidor deste workspace não recebe tráfego web, então não roda aplicações web. Use um worker ou fale com o suporte. |
| `public_port_exhausted` | Não há portas livres para acesso externo neste servidor. |
| `database_not_ready` | O banco ainda não está pronto. Tente de novo em instantes. |
| `domain_taken` | Esse domínio já está em uso na plataforma. |
| `subdomain_taken` | Esse endereço em acq.lat já está em uso. Escolha outro. |
| `domain_invalid` | Domínio inválido para uso com o Hydro. |
| `domain_not_verified` | O DNS do domínio ainda não aponta para o Hydro. |
| `billing_gateway_error` | O serviço de pagamentos não respondeu. Tente de novo em instantes. |
| `billing_active` | Este workspace tem uma assinatura em andamento. Altere o plano pela cobrança. |
| `billing_pending_payment` | A assinatura aguarda o primeiro pagamento. |
| `billing_profile_required` | Informe os dados de cobrança (nome, CPF ou CNPJ e e-mail). |
| `billing_no_subscription` | Este workspace não tem assinatura. |
| `backup_in_progress` | Já há um backup ou uma restauração em andamento para este recurso. |
| `backup_not_ready` | Este backup ainda não está pronto para restaurar. |
| `backup_rate_limited` | Faça no máximo um backup manual por hora. |
| `billing_past_due` | Há uma fatura em atraso. Pague para criar recursos ou religar os que pararam. |
| `plan_usage_exceeds` | O uso atual não cabe neste plano. Reduza os recursos antes de mudar. |
| `plan_not_purchasable` | Este plano não está disponível para contratação. |
| `same_plan` | O workspace já está neste plano. |
| `idempotency_conflict` | Esta Idempotency-Key já foi usada com outro conteúdo. |
| `idempotency_in_progress` | Uma requisição com esta Idempotency-Key ainda está em processamento. |
## Status HTTP mais comuns [#status-http-mais-comuns]
| Status | Quando |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 400 | Corpo inválido (`validation_failed`, `domain_invalid`) ou .zip com `node_modules` (`artifact_has_node_modules`) |
| 401 | Sem token válido (`unauthenticated`) |
| 403 | Sem permissão (`forbidden`, `role_not_allowed`), plano exigido (`plan_required`) ou workspace suspenso |
| 404 | Recurso inexistente **ou** de outro workspace |
| 409 | Conflito de estado (`name_taken`, `subdomain_taken`, `domain_taken`, `quota_exceeded`, `deployment_in_progress`, `database_not_ready`, `backup_in_progress`) |
| 422 | Reuso de `Idempotency-Key` com outro corpo; .zip ilegível ou inválido (`artifact_invalid`) |
| 429 | Limite de requisições |