Managed Tables API
Endpoints para gerenciar tabelas PostgreSQL proprias da company. Cada company tem um banco managed dedicado, provisionado on-demand. Suporta criacao manual de tabelas, upload de arquivos (CSV/Excel/ZIP) com inferencia de schema, e CRUD de linhas.
Diferenca em relacao a Data API:
- Data API = consumo de visualizacoes salvas como endpoint publico (read-only via API key).
- Managed Tables = camada de armazenamento do usuario (CRUD via JWT do console).
Visualizations publicadas via Data API podem ler de tabelas managed-tables, mas os dois modulos sao independentes.
Todos os endpoints exigem autenticacao JWT padrao (Authorization: Bearer <token>) e estao sob o prefixo /v1/managed-tables.
Provisioning
Provisionar banco managed
/v1/managed-tables/provisionCria um banco PostgreSQL dedicado para a company (idempotente — re-chamar retorna a conexao existente).
Required Permission: managed-tables:create
Response: 200 OK
{
"provisioned": true,
"connection_id": "507f1f77bcf86cd799439011"
}
Status do provisionamento
/v1/managed-tables/statusRequired Permission: managed-tables:list
Response: 200 OK
{
"provisioned": true,
"connection_id": "507f1f77bcf86cd799439011"
}
provisioned: false indica que POST /provision ainda nao foi executado.
Tables CRUD
Listar tabelas
/v1/managed-tablesRequired Permission: managed-tables:list
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Numero da pagina |
per_page | integer | 20 | Itens por pagina (max 100) |
search | string | - | Filtra por display_name (case-insensitive) |
Response: 200 OK
{
"total": 12,
"quantity": 12,
"records": [
{
"_id": "507f1f77bcf86cd799439011",
"company_id": "507f1f77bcf86cd799439012",
"connection_id": "507f1f77bcf86cd799439013",
"display_name": "vendas_mensais",
"columns": [
{ "name": "produto", "display_name": "Produto", "type": "text", "nullable": false },
{ "name": "valor", "display_name": "Valor", "type": "decimal", "nullable": true }
],
"source": "manual",
"status": "active",
"row_count": 1500,
"created_by_user_id": "507f1f77bcf86cd799439014",
"created_at": "2026-01-15T10:30:00.000Z",
"updated_at": "2026-01-15T10:30:00.000Z"
}
]
}
Obter tabela
/v1/managed-tables/:idRequired Permission: managed-tables:list
Response: 200 OK — mesmo shape do item em records da listagem.
Errors: 404 { "error": "Tabela nao encontrada" }
Criar tabela (manual)
/v1/managed-tablesRequired Permission: managed-tables:create
Pre-requisito: banco provisionado (POST /provision). Caso contrario retorna 400 Base de dados nao provisionada. Ative "Meus Dados" primeiro.
Body:
| Campo | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
display_name | string | Sim | Nome da tabela. Deve casar ^[a-z_][a-z0-9_]*$ (snake_case sem comecar com numero). |
columns | array | Sim | Lista de colunas (>=1). |
columns[].name | string | Sim | Nome PG (snake_case). |
columns[].display_name | string | Sim | Nome amigavel. |
columns[].type | enum | Sim | text | integer | decimal | boolean | date | datetime. |
columns[].nullable | boolean | Sim | Se aceita NULL. |
{
"display_name": "vendas_mensais",
"columns": [
{ "name": "produto", "display_name": "Produto", "type": "text", "nullable": false },
{ "name": "valor", "display_name": "Valor", "type": "decimal", "nullable": true }
]
}
Response: 201 Created — mesmo shape de GET /:id.
Errors:
| HTTP | Codigo | Descricao |
|---|---|---|
| 400 | MANAGED_TABLE_DUPLICATE_NAME | Nome ja existe ativo |
| 400 | - | Banco nao provisionado |
| 422 | - | Schema invalido (display_name ou columns) |
Atualizar tabela
/v1/managed-tables/:idRequired Permission: managed-tables:update
Body (campos opcionais):
{
"display_name": "novo_nome",
"columns": [...],
"status": "active"
}
Response: 200 OK
Deletar tabela
/v1/managed-tables/:idRequired Permission: managed-tables:delete
Soft-delete: a tabela some das listagens. Restauracao nao e exposta pela API.
Response: 204 No Content
Upload de arquivos
Fluxo de 3 etapas:
- Upload → cria job em status
queued. Worker processa o arquivo e infere schema (statusanalisando→waiting_confirm). - Confirm → usuario revisa schema inferido e confirma; job vai para
inserting→done. - Polling → frontend acompanha estado via
GET /upload-jobs/:jobId.
Aceita .csv, .xlsx, .xls, .zip. Ate 10 arquivos por upload, 100MB total.
Upload (criar tabela nova)
/v1/managed-tables/uploadRequired Permission: managed-tables:upload
Content-Type: multipart/form-data
Form fields:
| Campo | Tipo | Descricao |
|---|---|---|
files | file[] | 1-10 arquivos (campo repetido). |
Response: 201 Created
{
"job_id": "507f1f77bcf86cd799439011",
"status": "queued"
}
Upload (anexar a tabela existente)
/v1/managed-tables/:id/uploadRequired Permission: managed-tables:upload
Content-Type: multipart/form-data
Form fields:
| Campo | Tipo | Default | Descricao |
|---|---|---|---|
files | file[] | - | 1-10 arquivos. |
mode | string | append | append (adiciona linhas) ou replace (truncate + insert). |
Response: mesmo shape do upload de tabela nova.
Listar jobs ativos
/v1/managed-tables/upload-jobs/activeRequired Permission: managed-tables:list
Retorna jobs ainda nao terminais (queued, analisando, waiting_confirm, inserting).
Response: 200 OK
{
"records": [
{
"_id": "...",
"company_id": "...",
"user_table_id": null,
"files": [
{ "original_name": "vendas.csv", "storage_path": "...", "size_bytes": 12345, "mime_type": "text/csv" }
],
"mode": "create",
"status": "waiting_confirm",
"inferred_schema": {
"suggested_table_name": "vendas",
"columns": [{ "name": "produto", "display_name": "Produto", "type": "text", "nullable": false }],
"sample_rows": [{ "produto": "X" }],
"total_row_count_estimate": 1500
},
"confirmed_schema": null,
"progress": null,
"error": null,
"created_at": "2026-01-15T10:30:00.000Z",
"updated_at": "2026-01-15T10:30:00.000Z"
}
]
}
Obter job
/v1/managed-tables/upload-jobs/:jobIdRequired Permission: managed-tables:list
Response: 200 OK — mesmo shape do item em active.
Status possiveis: queued, analisando, waiting_confirm, inserting, done, failed.
Errors: 404 { "error": "Job nao encontrado" }
Confirmar schema do job
/v1/managed-tables/upload-jobs/:jobId/confirmRequired Permission: managed-tables:upload
Aplica o schema confirmado pelo usuario e enfileira a fase inserting.
Body:
{
"display_name": "vendas",
"columns": [
{ "name": "produto", "display_name": "Produto", "type": "text", "nullable": false }
]
}
Para upload em tabela existente (user_table_id setado no job), o display_name reusa o nome legado e nao e validado contra o regex.
Response: 200 OK
{ "status": "inserting" }
Errors:
| HTTP | Codigo | Descricao |
|---|---|---|
| 400 | - | Job nao esta em waiting_confirm |
| 400 | MANAGED_TABLE_DUPLICATE_NAME | Nome ja existe (somente em criacao) |
| 422 | - | display_name invalido (regex), coluna invalida (name/type) |
Data CRUD
Listar linhas
/v1/managed-tables/:id/dataRequired Permission: managed-tables:data-read
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Numero da pagina |
per_page | integer | 50 | Itens por pagina |
sort_by | string | - | field ASC ou -field DESC |
Response: 200 OK
{
"total": 1500,
"quantity": 50,
"records": [
{ "id": "uuid", "produto": "X", "valor": 12.5 }
]
}
Inserir linhas
/v1/managed-tables/:id/dataRequired Permission: managed-tables:data-write
Body:
{
"rows": [
{ "produto": "X", "valor": 12.5 },
{ "produto": "Y", "valor": 30 }
]
}
Response: 201 Created
{ "inserted": 2 }
Errors: 422 se rows ausente ou vazio.
Atualizar linha
/v1/managed-tables/:id/data/:rowIdRequired Permission: managed-tables:data-write
Body: objeto com os campos a atualizar.
Response: 200 OK — linha atualizada.
Errors: 404 { "error": "Linha nao encontrada" }
Deletar linha
/v1/managed-tables/:id/data/:rowIdRequired Permission: managed-tables:data-write
Response: 204 No Content
Errors: 404 { "error": "Linha nao encontrada" }
Tipos de coluna
| Tipo | PostgreSQL DDL | Descricao |
|---|---|---|
text | TEXT | String livre |
integer | BIGINT | Inteiro 64-bit |
decimal | NUMERIC | Numerico de precisao arbitraria |
boolean | BOOLEAN | true/false |
date | DATE | Data (sem hora) |
datetime | TIMESTAMPTZ | Timestamp com timezone |
Limites e restricoes
- Upload: max 100MB total, max 10 arquivos por chamada.
- Nome de tabela / coluna: regex
^[a-z_][a-z0-9_]*$. display_nameda tabela e unico por company entre tabelas ativas.DELETEe soft-delete: a tabela some das listagens. Restauracao nao e exposta pela API.
Codigos de erro
| HTTP | Codigo | Descricao |
|---|---|---|
| 400 | MANAGED_TABLE_DUPLICATE_NAME | Conflito de display_name na company |
| 400 | - | Banco nao provisionado / job nao esta em waiting_confirm |
| 401 | - | JWT ausente ou invalido |
| 403 | - | Sem permissao IAM (managed-tables:*) |
| 404 | - | Tabela, linha ou job inexistente |
| 422 | - | Schema invalido (regex de identificador / tipo de coluna) |
| 500 | - | Erro interno |