Saltar al contenido principal

API de Managed Tables

Endpoints para gestionar tablas PostgreSQL propias de la company. Cada company tiene un banco managed dedicado, provisionado on-demand. Soporta creacion manual de tablas, upload de archivos (CSV/Excel/ZIP) con inferencia de schema, y CRUD de filas.

Diferencia con respecto a Data API:

  • Data API = consumo de visualizaciones guardadas como endpoint publico (read-only via API key).
  • Managed Tables = capa de almacenamiento del usuario (CRUD via JWT del console).

Las visualizaciones publicadas via Data API pueden leer de tablas managed-tables, pero los dos modulos son independientes.

Todos los endpoints requieren autenticacion JWT estandar (Authorization: Bearer <token>) y estan bajo el prefijo /v1/managed-tables.

Provisioning

Provisionar banco managed

POST/v1/managed-tables/provision

Crea un banco PostgreSQL dedicado para la company (idempotente — re-llamar devuelve la conexion existente).

Permiso requerido: managed-tables:create

Respuesta: 200 OK

{
"provisioned": true,
"connection_id": "507f1f77bcf86cd799439011"
}

Estado del provisionamiento

GET/v1/managed-tables/status

Permiso requerido: managed-tables:list

Respuesta: 200 OK

{
"provisioned": true,
"connection_id": "507f1f77bcf86cd799439011"
}

provisioned: false indica que POST /provision aun no se ejecuto.


CRUD de Tablas

Listar tablas

GET/v1/managed-tables

Permiso requerido: managed-tables:list

Query Parameters:

ParametroTipoDefaultDescripcion
pageinteger1Numero de pagina
per_pageinteger20Items por pagina (max 100)
searchstring-Filtra por display_name (case-insensitive)

Respuesta: 200 OK

{
"total": 12,
"quantity": 12,
"records": [
{
"_id": "507f1f77bcf86cd799439011",
"company_id": "507f1f77bcf86cd799439012",
"connection_id": "507f1f77bcf86cd799439013",
"display_name": "vendas_mensais",
"columns": [
{ "name": "produto", "display_name": "Producto", "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"
}
]
}

Obtener tabla

GET/v1/managed-tables/:id

Permiso requerido: managed-tables:list

Respuesta: 200 OK — mismo shape de un item de records del listado.

Errores: 404 { "error": "Tabela não encontrada" }


Crear tabla (manual)

POST/v1/managed-tables

Permiso requerido: managed-tables:create

Pre-requisito: banco provisionado (POST /provision). De lo contrario devuelve 400 Base de dados não provisionada. Ative "Meus Dados" primeiro.

Body:

CampoTipoObligatorioDescripcion
display_namestringSiNombre de la tabla. Debe coincidir con ^[a-z_][a-z0-9_]*$ (snake_case sin empezar con numero).
columnsarraySiLista de columnas (>=1).
columns[].namestringSiNombre PG (snake_case).
columns[].display_namestringSiNombre amigable.
columns[].typeenumSitext | integer | decimal | boolean | date | datetime.
columns[].nullablebooleanSiSi acepta NULL.
{
"display_name": "vendas_mensais",
"columns": [
{ "name": "produto", "display_name": "Producto", "type": "text", "nullable": false },
{ "name": "valor", "display_name": "Valor", "type": "decimal", "nullable": true }
]
}

Respuesta: 201 Created — mismo shape que GET /:id.

Errores:

HTTPCodigoDescripcion
400MANAGED_TABLE_DUPLICATE_NAMENombre ya existe activo
400-Banco no provisionado
422-Schema invalido (display_name o columns)

Actualizar tabla

PUT/v1/managed-tables/:id

Permiso requerido: managed-tables:update

Body (campos opcionales):

{
"display_name": "novo_nome",
"columns": [...],
"status": "active"
}

Respuesta: 200 OK


Eliminar tabla

DELETE/v1/managed-tables/:id

Permiso requerido: managed-tables:delete

Soft-delete: la tabla desaparece de los listados. Restauracion no es expuesta por la API.

Respuesta: 204 No Content


Upload de archivos

Flujo de 3 etapas:

  1. Upload → crea job en estado queued. Worker procesa el archivo e infiere schema (estado analisandowaiting_confirm).
  2. Confirm → usuario revisa el schema inferido y confirma; el job pasa a insertingdone.
  3. Polling → frontend monitorea el estado via GET /upload-jobs/:jobId.

Acepta .csv, .xlsx, .xls, .zip. Hasta 10 archivos por upload, 100MB total.

Upload (crear tabla nueva)

POST/v1/managed-tables/upload

Permiso requerido: managed-tables:upload

Content-Type: multipart/form-data

Form fields:

CampoTipoDescripcion
filesfile[]1-10 archivos (campo repetido).

Respuesta: 201 Created

{
"job_id": "507f1f77bcf86cd799439011",
"status": "queued"
}

Upload (anexar a tabla existente)

POST/v1/managed-tables/:id/upload

Permiso requerido: managed-tables:upload

Content-Type: multipart/form-data

Form fields:

CampoTipoDefaultDescripcion
filesfile[]-1-10 archivos.
modestringappendappend (agrega filas) o replace (truncate + insert).

Respuesta: mismo shape del upload de tabla nueva.


Listar jobs activos

GET/v1/managed-tables/upload-jobs/active

Permiso requerido: managed-tables:list

Devuelve jobs aun no terminales (queued, analisando, waiting_confirm, inserting).

Respuesta: 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": "Producto", "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"
}
]
}

Obtener job

GET/v1/managed-tables/upload-jobs/:jobId

Permiso requerido: managed-tables:list

Respuesta: 200 OK — mismo shape del item de active.

Estados posibles: queued, analisando, waiting_confirm, inserting, done, failed.

Errores: 404 { "error": "Job não encontrado" }


Confirmar schema del job

POST/v1/managed-tables/upload-jobs/:jobId/confirm

Permiso requerido: managed-tables:upload

Aplica el schema confirmado por el usuario y enqueua la fase inserting.

Body:

{
"display_name": "vendas",
"columns": [
{ "name": "produto", "display_name": "Producto", "type": "text", "nullable": false }
]
}

Para upload en tabla existente (user_table_id seteado en el job), el display_name reusa el nombre legacy y no se valida contra el regex.

Respuesta: 200 OK

{ "status": "inserting" }

Errores:

HTTPCodigoDescripcion
400-Job no esta en waiting_confirm
400MANAGED_TABLE_DUPLICATE_NAMENombre ya existe (solo en creacion)
422-display_name invalido (regex), columna invalida (name/type)

CRUD de Datos

Listar filas

GET/v1/managed-tables/:id/data

Permiso requerido: managed-tables:data-read

Query Parameters:

ParametroTipoDefaultDescripcion
pageinteger1Numero de pagina
per_pageinteger50Items por pagina
sort_bystring-field ASC o -field DESC

Respuesta: 200 OK

{
"total": 1500,
"quantity": 50,
"records": [
{ "id": "uuid", "produto": "X", "valor": 12.5 }
]
}

Insertar filas

POST/v1/managed-tables/:id/data

Permiso requerido: managed-tables:data-write

Body:

{
"rows": [
{ "produto": "X", "valor": 12.5 },
{ "produto": "Y", "valor": 30 }
]
}

Respuesta: 201 Created

{ "inserted": 2 }

Errores: 422 si rows ausente o vacio.


Actualizar fila

PUT/v1/managed-tables/:id/data/:rowId

Permiso requerido: managed-tables:data-write

Body: objeto con los campos a actualizar.

Respuesta: 200 OK — fila actualizada.

Errores: 404 { "error": "Linha não encontrada" }


Eliminar fila

DELETE/v1/managed-tables/:id/data/:rowId

Permiso requerido: managed-tables:data-write

Respuesta: 204 No Content

Errores: 404 { "error": "Linha não encontrada" }


Tipos de columna

TipoPostgreSQL DDLDescripcion
textTEXTString libre
integerBIGINTEntero 64-bit
decimalNUMERICNumerico de precision arbitraria
booleanBOOLEANtrue/false
dateDATEFecha (sin hora)
datetimeTIMESTAMPTZTimestamp con timezone

Limites y restricciones

  • Upload: max 100MB total, max 10 archivos por llamada.
  • Nombre de tabla / columna: regex ^[a-z_][a-z0-9_]*$.
  • display_name de la tabla es unico por company entre tablas activas.
  • DELETE es soft-delete: la tabla desaparece de los listados. Restauracion no es expuesta por la API.

Codigos de error

HTTPCodigoDescripcion
400MANAGED_TABLE_DUPLICATE_NAMEConflicto de display_name en la company
400-Banco no provisionado / job no esta en waiting_confirm
401-JWT ausente o invalido
403-Sin permiso IAM (managed-tables:*)
404-Tabla, fila o job inexistente
422-Schema invalido (regex de identificador / tipo de columna)
500-Error interno