# Backup e restore

Escopo: `databases:write`. Substitua `$P` (projeto) e `$DB` (serviço-banco) pelos ids.

## Política

- Backup **diário** automático: retenção de 7 diários + 4 semanais.
- Backup **sob demanda** quando quiser (antes de uma migration arriscada, por exemplo).
- Apagar o banco faz um backup final por padrão.

## Criar um backup

```bash
SUF=$(date +%s)-$RANDOM
H="Authorization: Bearer $NEBULAR_TOKEN"
P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" -H "$H" -H "Content-Type: application/json" \
  -d "{\"name\": \"bk-$SUF\"}" | jq -r .id)
DB=$(curl -sS -X POST "$NEBULAR_API/v1/projects/$P/databases" -H "$H" -H "Content-Type: application/json" \
  -d '{"name": "db", "engine": "postgres", "quota_mb": 1024}' | jq -r .id)
# o banco nasce em provisioning: espere ficar ready antes de pedir backup
for i in $(seq 1 40); do
  [ "$(curl -sS "$NEBULAR_API/v1/projects/$P/databases/$DB" -H "$H" | jq -r .status)" = ready ] && break
  sleep 2
done

curl -sS -X POST "$NEBULAR_API/v1/projects/$P/databases/$DB/backups" \
  -H "$H" \
  -H "Idempotency-Key: backup-$DB-$(date +%Y%m%d%H%M)-$RANDOM"
```
```json
{"backup_id": "b_01J9ZK5Q", "job_id": "j_01J9ZK5R"}
```

A resposta é `202`: o backup é assíncrono. Guarde `backup_id`.

## Listar backups

Paginação por cursor (`next_cursor` / `has_more`). Cada item tem `id`, `at`, `size`, `status`.

```bash
SUF=$(date +%s)-$RANDOM
H="Authorization: Bearer $NEBULAR_TOKEN"
P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" -H "$H" -H "Content-Type: application/json" \
  -d "{\"name\": \"bk-$SUF\"}" | jq -r .id)
DB=$(curl -sS -X POST "$NEBULAR_API/v1/projects/$P/databases" -H "$H" -H "Content-Type: application/json" \
  -d '{"name": "db", "engine": "postgres", "quota_mb": 1024}' | jq -r .id)
# o banco nasce em provisioning: espere ficar ready antes de pedir backup
for i in $(seq 1 40); do
  [ "$(curl -sS "$NEBULAR_API/v1/projects/$P/databases/$DB" -H "$H" | jq -r .status)" = ready ] && break
  sleep 2
done
curl -sS -X POST "$NEBULAR_API/v1/projects/$P/databases/$DB/backups" -H "$H" >/dev/null

curl -sS "$NEBULAR_API/v1/projects/$P/databases/$DB/backups" \
  -H "$H"
```
```json
{"items": [{"id": "b_01J9ZK5Q", "at": "2026-10-10T03:00:00Z", "size": 1048576, "status": "running"}], "next_cursor": null, "has_more": false}
```

O item nasce `running` e vira `done`. Espere o backup concluir antes de usá-lo (restaurar um backup que ainda roda responde `409 backup_not_ready`): consulte o job com `GET /v1/projects/{p}/databases/{s}/jobs/{job_id}` (`status`: `queued`, `running`, `done`, `failed`; no `failed` vem `error` com `code` e `message`), com backoff de 2 s dobrando até 30 s, teto de uns 10 min.

## Restore

`target`:

- `"new"` (padrão): restaura para um banco novo; informe `new_db_name`. Seguro, não toca nos dados atuais.
- `"same"`: sobrescreve o banco atual. Destrutivo para os dados atuais: no painel exige step-up (confirmação recente de identidade); pela API de token vale o escopo `databases:write`, então só use com autorização explícita de uma pessoa.

```bash
SUF=$(date +%s)-$RANDOM
H="Authorization: Bearer $NEBULAR_TOKEN"
P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" -H "$H" -H "Content-Type: application/json" \
  -d "{\"name\": \"bk-$SUF\"}" | jq -r .id)
DB=$(curl -sS -X POST "$NEBULAR_API/v1/projects/$P/databases" -H "$H" -H "Content-Type: application/json" \
  -d '{"name": "db", "engine": "postgres", "quota_mb": 1024}' | jq -r .id)
# o banco nasce em provisioning: espere ficar ready antes de pedir backup
for i in $(seq 1 40); do
  [ "$(curl -sS "$NEBULAR_API/v1/projects/$P/databases/$DB" -H "$H" | jq -r .status)" = ready ] && break
  sleep 2
done
R=$(curl -sS -X POST "$NEBULAR_API/v1/projects/$P/databases/$DB/backups" -H "$H")
B=$(echo "$R" | jq -r .backup_id); J=$(echo "$R" | jq -r .job_id)

# espera o job do backup concluir (backoff 2 s, dobrando até 30 s)
delay=2
for i in $(seq 1 40); do
  st=$(curl -sS "$NEBULAR_API/v1/projects/$P/databases/$DB/jobs/$J" -H "$H" | jq -r .status)
  echo "tentativa $i: $st"
  [ "$st" = done ] && break
  case "$st" in failed) exit 1 ;; esac
  sleep "$delay"; delay=$(( delay * 2 > 30 ? 30 : delay * 2 ))
done
[ "$st" = done ]

curl -sS -X POST "$NEBULAR_API/v1/projects/$P/databases/$DB/restore" \
  -H "$H" -H "Content-Type: application/json" \
  -H "Idempotency-Key: restore-$DB-$B" \
  -d "{\"backup_id\": \"$B\", \"target\": \"new\", \"new_db_name\": \"n_restaurado\"}"
```
```json
{"job_id": "j_01J9ZK5S"}
```

Resposta `202`. O restore é assíncrono: consulte o resultado com backoff até concluir.

## O que muda em cada motor

| Motor | Efeito do restore |
|---|---|
| PostgreSQL / MariaDB, `new` | cria um banco extra na mesma instância; a plataforma monta a `DATABASE_URL` do banco novo (mesmo papel e senha, outro nome de banco). Aponte o app para ele quando validar. |
| PostgreSQL / MariaDB, `same` | sobrescreve o banco atual; as variáveis não mudam. |
| SQLite, `new` ou `same` | **a URL e o token MUDAM em todo restore** (o namespace antigo é removido no `same`). A plataforma sela o token novo, atualiza `LIBSQL_URL`/`LIBSQL_AUTH_TOKEN` e marca os dependentes para redeploy: faça deploy deles. |

## Se algo falhar

| Resposta | O que fazer |
|---|---|
| `401` / `403` | Falta `databases:write`. Pare e peça a uma pessoa. |
| `409 backup_not_ready` | O backup ainda não terminou (job `running`): espere o job e repita. |
| `404 not_found` | Banco ou `backup_id` inexistente ou de outro time. Liste os backups de novo. |
| `409` | Há outra operação em andamento no banco, ou `new_db_name` já existe. Espere ou troque o nome. |
| `422 quota_exceeded` | O restore não cabe na cota. Aumente a cota ou apague dados. |
| `503 unavailable` | Repita com backoff e a mesma `Idempotency-Key`; persistindo, pare e avise. |
| `429 rate_limited` | Espere `Retry-After`. |

Veja [Erros](/erros/tabela/) e [Rate limit e retry](/erros/rate-limit-e-retry/).
