--- name: catcher-data-storage description: Use quando um app ou agente precisa consumir o Storage do Catcher (/v1/cloudstore/*). Buckets S3-compatíveis — upload, pastas, URLs assinadas, preview, chaves HMAC, bucket por tenant. homepage: https://catcher.one/produtos/storage docs: https://catcher.one/docs/storage api_base: https://data-api.catcher.one --- # Catcher — Storage Autenticação: `X-API-Key: ctc_…` (owner/admin). Base: https://data-api.catcher.one. Onboarding de key e conceitos gerais: https://catcher.one/skill. Referência navegável: https://catcher.one/docs/storage. # Storage — objetos gerenciados (`cloudstore`) > Rotas `/v1/cloudstore/*` · owner/admin · gated por `CLOUDSTORE_ENABLED` (off → 404) ## O que é **Buckets de objetos gerenciados** em cima do Google Cloud Storage. Você cria buckets por projeto e faz upload/download/organização de arquivos pela API — sem tocar no console da GCP. Os buckets ficam no **mesmo GCP project** que hospeda as instâncias de [Database](database.md) daquele projeto. ## O que dá pra fazer - Criar/listar/remover buckets; ver o **overview** (contagem de objetos, bytes totais, settings ao vivo do GCS). - Upload (multipart), download (stream autenticado **ou** signed URL keyless), delete. - **Pastas** (prefixos `foo/bar/`): criar, ver stats (count + bytes), apagar recursivamente. - **Power-ops** de objeto: `stat` (metadata completa), `copy`, `move`/rename, `meta` (editar content-type/cache/metadata/storage-class). - **Acesso S3-compatível**: emitir chaves HMAC e apontar qualquer SDK do S3/R2 para `storage.googleapis.com`. ## Como funciona por dentro (na prática) - **Control-plane + GCS real.** A API guarda a control-row do bucket no tenant DB; o objeto vive no GCS. O nome real do bucket é globalmente único (`ctc--`) — você sempre usa o `id` da control-row. - **Signed URLs keyless (V4).** `objects/sign` devolve uma URL que o GCS serve **direto** (`storage.googleapis.com`), sem passar pelo Catcher nem expor credencial — ideal para o navegador baixar/exibir. Default TTL 15 min, máx. 7 dias. - **Pastas são prefixos.** O GCS não tem pastas; uma "pasta" é um placeholder zero-byte com chave terminada em `/`. Os stats e a varredura ignoram o placeholder. - **Content-Type normalizado** no upload: tipos textuais ganham `; charset=utf-8` e tipos vazios/`octet-stream` são inferidos da extensão — a signed URL renderiza UTF-8 no navegador sem mojibake. - **S3-compat isolado por cliente:** cada projeto usa uma SA dedicada (`ctc-store@`) com acesso só ao próprio projeto; o `secret` da chave HMAC só aparece na criação. ## Fluxo típico (end-to-end) ```bash KEY="X-API-Key: ctc_…" ; B=https://data-api.catcher.one ; JS='Content-Type: application/json' PID= # mesmo projeto do Database (ver Getting Started) # 1. Cria o bucket (tenant_id é opcional — dedica o bucket a um cliente final; ver Tenants) BID=$(curl -sX POST $B/v1/cloudstore/projects/$PID/buckets -H "$KEY" -H "$JS" \ -d '{"name":"assets","location":"southamerica-east1","storage_class":"STANDARD"}' | jq -r .id) # 2. Upload (multipart). 'key' opcional aceita prefixo (cria a "pasta") curl -sX POST $B/v1/cloudstore/buckets/$BID/objects -H "$KEY" \ -F "file=@./logo.png" -F "key=brand/logo.png" # 3. Lista sob um prefixo curl -s "$B/v1/cloudstore/buckets/$BID/objects?prefix=brand/" -H "$KEY" # 4. Signed URL para o navegador baixar/exibir (15 min) curl -s "$B/v1/cloudstore/buckets/$BID/objects/sign?key=brand/logo.png&ttl=900" -H "$KEY" # → { "url": "https://storage.googleapis.com/…", "expires_in": 900 } ``` ## Referência de endpoints ### Buckets | Método | Rota | O que faz | |---|---|---| | `POST` | `/v1/cloudstore/projects/{pid}/buckets` | Cria (`name`, `location?`, `storage_class?`, `tenant_id?`) → `201` | | `GET` | `/v1/cloudstore/projects/{pid}/buckets` | Lista | | `GET` | `/v1/cloudstore/buckets/{id}/overview` | `object_count`+`total_bytes` + settings GCS (versioning, public-access-prevention, UBLA) | | `DELETE` | `/v1/cloudstore/buckets/{id}` | Remove (GCS + control-row) → `204` | #### Isolamento por tenant (end-customers) Passe `tenant_id` ao criar o bucket para dedicá-lo a um **cliente final**: o bucket fica isolado por IAM no GCS — um bucket por cliente. Sem `tenant_id`, o bucket é de nível projeto (compartilhado entre seus clientes). É o mesmo `tenant_id` usado no Database, Vector e Cache — ver **[Tenants](tenants.md)**. ### Objetos | Método | Rota | O que faz | |---|---|---| | `GET` | `/v1/cloudstore/buckets/{id}/objects?prefix=&limit=` | Lista (`key,size,content_type,updated,etag`) | | `POST` | `/v1/cloudstore/buckets/{id}/objects` | Upload multipart (`file` + `key?`) | | `DELETE` | `/v1/cloudstore/buckets/{id}/objects?key=` | Remove → `204` | | `GET` | `/v1/cloudstore/buckets/{id}/objects/sign?key=&ttl=` | Signed URL (download direto do GCS) | | `GET` | `/v1/cloudstore/buckets/{id}/objects/download?key=` | Stream pelo control-plane (autenticado) | | `GET` | `/v1/cloudstore/buckets/{id}/objects/stat?key=` | Metadata completa (generation, md5, crc32c, custom `x-goog-meta-*`, …) | | `POST` | `/v1/cloudstore/buckets/{id}/objects/copy` | Duplica (`{src,dst}`, rewrite server-side) → `201` | | `POST` | `/v1/cloudstore/buckets/{id}/objects/move` | Move/rename (`{src,dst}`, copy+delete) → `200` | | `PATCH` | `/v1/cloudstore/buckets/{id}/objects/meta?key=` | Edita content-type/cache/disposition/language/storage-class/metadata | ### Pastas | Método | Rota | O que faz | |---|---|---| | `POST` | `/v1/cloudstore/buckets/{id}/folders` | Cria pasta vazia (`{path}`) → `201 {path:"docs/sub/"}` | | `GET` | `/v1/cloudstore/buckets/{id}/folders/stats?path=` | `{count, total_bytes}` sob o prefixo | | `DELETE` | `/v1/cloudstore/buckets/{id}/folders?path=` | Apaga recursivamente tudo sob o prefixo → `{deleted:N}` | ### Acesso S3-compatível (HMAC) | Método | Rota | O que faz | |---|---|---| | `POST` | `/v1/cloudstore/projects/{pid}/hmac-keys` | Emite chave (`secret` só aqui) | | `GET` | `/v1/cloudstore/projects/{pid}/hmac-keys` | Lista | | `DELETE` | `/v1/cloudstore/projects/{pid}/hmac-keys/{accessId}` | Revoga | Aponte qualquer SDK do S3/R2 para `https://storage.googleapis.com` com a chave/secret. ## Códigos de erro do módulo `INVALID_BUCKET_NAME` (400) · `BUCKET_ALREADY_EXISTS` (409) · `BUCKET_NOT_FOUND` (404) · `BUCKET_NOT_READY` (409) · `OBJECT_NOT_FOUND` (404) · `NO_READY_PROJECT` (409, sem GCP project pronto no projeto) · `S3_ACCESS_DISABLED_BY_POLICY` (409, org policy bloqueia chaves de SA). ## Gotchas - **Use sempre o `id` da control-row**, não o nome real do GCS (`ctc-…`). - **Para o navegador, prefira `sign`** (download direto, sem custo de proxy); `download` é o stream autenticado pelo control-plane (útil server-side). - **Apagar pasta é recursivo e definitivo** — `path` não pode ser vazio/`/`/`.`/`..` (guard contra apagar o bucket inteiro). - **`move` não é atômico** (copy + delete) — em falha no meio, pode sobrar a origem. - **`NO_READY_PROJECT`** → o projeto ainda não tem um GCP project provisionado (geralmente porque nenhuma instância de Database foi criada nele ainda).