> ## Documentation Index
> Fetch the complete documentation index at: https://docs.puente.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# API Reference

> Referencia completa de los endpoints REST de Puente Studio.

Toda la API está en `{BASE_URL}/studio/...` (privada, requiere `STUDIO_KEY`) o `{BASE_URL}/public/artefacto/{id}/...` (pública, requiere `api_key` del artefacto).

## Convenciones

| Concepto         | Valor                                                             |
| ---------------- | ----------------------------------------------------------------- |
| **BASE\_URL**    | `https://puente-backend-721178029791.southamerica-west1.run.app/` |
| **Auth privada** | `X-API-Key: puente_studio_xxxxxxxxxxxx`                           |
| **Auth pública** | `X-API-Key: puente_artifact_xxxxxxxxxxxx`                         |
| **Content-Type** | `application/json` en POST/PUT                                    |

***

## Endpoints privados — Artefactos

Requieren `X-API-Key: {STUDIO_KEY}`.

### Listar artefactos

```http theme={null}
GET {BASE_URL}/studio/artefactos
```

Retorna los artefactos del equipo asociado a tu `STUDIO_KEY`.

### Obtener un artefacto

```http theme={null}
GET {BASE_URL}/studio/artefactos/{id}
```

Retorna el código fuente completo en formato JSON (o HTML si es legacy).

### Crear artefacto

```http theme={null}
POST {BASE_URL}/studio/artefactos
Content-Type: application/json

{
  "titulo": "Nombre de la app",
  "descripcion": "Descripción opcional",
  "equipo_id": null,
  "app_content": {
    "index.tsx": { "content": "// código aquí", "type": "tsx" }
  }
}
```

<Note>
  Si `equipo_id` es `null`, se asigna automáticamente el equipo de la `STUDIO_KEY`.
</Note>

**Respuesta:**

```json theme={null}
{
  "message": "Artefacto insertado correctamente",
  "request_id": "330482e2-...",
  "empresa_id": 1,
  "artefacto_insertado": {
    "id": 545,
    "titulo": "Mi Primera App",
    "equipo_id": 2,
    "fecha_creacion": "2026-04-30T20:21:51.588362+00:00"
  },
  "api_key": "puente_artifact_xxxxx"
}
```

<Warning>
  Guarda `id`, `public_id` (obtenlo con `/meta`) y `api_key` inmediatamente. La `api_key` **solo se muestra una vez**.
</Warning>

### Actualizar artefacto

```http theme={null}
PUT {BASE_URL}/studio/artefactos/{id}
Content-Type: application/json

{
  "titulo": "Nuevo nombre",
  "descripcion": "Nueva descripción",
  "app_content": { ... TODOS los archivos ... }
}
```

<Warning>
  **El PUT reemplaza todo el `app_content`.** Si envías solo los archivos modificados, los demás **se eliminan permanentemente**.

  Flujo seguro: `GET → modificar en memoria → PUT con todo el contenido`.
</Warning>

### Obtener metadatos (incluye public\_id)

```http theme={null}
GET {BASE_URL}/studio/artefactos/{id}/meta
```

**Respuesta:**

```json theme={null}
{
  "request_id": "8789ad9e-...",
  "artefacto": {
    "id": 541,
    "titulo": "App Base",
    "public_id": "788517f2-a8e1-44dc-9667-d3a48eff6972",
    "equipo_id": 2,
    "empresa_id": 1,
    "fecha_creacion": "2026-04-30T17:59:02.631483+00:00"
  }
}
```

La URL pública final de la app es `https://app.puente.xyz/public/{public_id}/`.

***

## Endpoints privados — Tablas

### Listar tablas

```http theme={null}
GET {BASE_URL}/studio/tablas
```

### Crear tabla

```http theme={null}
POST {BASE_URL}/studio/tablas
Content-Type: application/json

{
  "nombre": "pedidos",
  "descripcion": "Pedidos del CRM",
  "equipo_id": null,
  "columnas": [
    { "key": "cliente",  "label": "Cliente",  "tipo": "text",    "requerido": true },
    { "key": "monto",    "label": "Monto",    "tipo": "number",  "requerido": true },
    { "key": "fecha",    "label": "Fecha",    "tipo": "date",    "requerido": false },
    { "key": "activo",   "label": "Activo",   "tipo": "boolean", "requerido": false },
    { "key": "estado",   "label": "Estado",   "tipo": "select",  "requerido": true,
      "opciones": ["pendiente", "pagado", "enviado"] }
  ]
}
```

**Tipos de columna:**

| Tipo      | Formato                       |
| --------- | ----------------------------- |
| `text`    | string                        |
| `number`  | number (decimal soportado)    |
| `date`    | `"YYYY-MM-DD"`                |
| `boolean` | `true` / `false` (no strings) |
| `select`  | string de las `opciones`      |

### Actualizar tabla

```http theme={null}
PUT {BASE_URL}/studio/tablas/{tabla_id}
Content-Type: application/json

{ "nombre": "nuevo_nombre", "descripcion": "nueva descripción" }
```

### Leer filas

```http theme={null}
GET {BASE_URL}/studio/tablas/{tabla_id}/datos?limit=500&offset=0
```

Máximo `limit=5000`. Para datasets grandes, pagina con `offset`.

### Insertar fila

```http theme={null}
POST {BASE_URL}/studio/tablas/{tabla_id}/datos
Content-Type: application/json

{
  "datos": {
    "cliente": "Ana",
    "monto": 1000,
    "fecha": "2026-04-29",
    "estado": "pendiente"
  }
}
```

### Insertar en masa (bulk)

```http theme={null}
POST {BASE_URL}/studio/tablas/{tabla_id}/datos/bulk
Content-Type: application/json

{
  "filas": [
    { "cliente": "Ana", "monto": 1000 },
    { "cliente": "Luis", "monto": 2500 }
  ]
}
```

Máximo **10,000 filas por request**. Atómico: si una fila falla, **ninguna** se inserta.

***

## API Keys de artefactos

Cada artefacto tiene su propia `api_key` (`puente_artifact_xxx`) que le permite conectarse a tablas desde el frontend sin JWT.

### Obtener configuración (sin la key en texto plano)

```http theme={null}
GET {BASE_URL}/studio/artefactos/{id}/api-key
```

**Respuesta:**

```json theme={null}
{
  "id": 456,
  "artefacto_id": 123,
  "tipo": "artifact",
  "rate_limit_config": {
    "requests_per_minute": 60,
    "requests_per_hour": 1000,
    "requests_per_day": 10000
  },
  "revoked": false,
  "uso_count": 12543,
  "last_used_at": "2026-04-29T14:23:45Z"
}
```

### Actualizar rate limit

```http theme={null}
PUT {BASE_URL}/studio/artefactos/{id}/api-key
Content-Type: application/json

{
  "rate_limit_config": {
    "requests_per_minute": 120,
    "requests_per_hour": 5000,
    "requests_per_day": 50000
  }
}
```

### Regenerar API Key

<Warning>
  **OPERACIÓN DESTRUCTIVA.** La key anterior queda invalidada inmediatamente. Cualquier app que la use deja de funcionar hasta actualizar el código.
</Warning>

```http theme={null}
POST {BASE_URL}/studio/artefactos/{id}/api-key/regenerate
```

**Respuesta:**

```json theme={null}
{
  "api_key": "puente_artifact_NEW_KEY_HERE",
  "message": "API key regenerada exitosamente. Guárdala de forma segura, no se podrá recuperar."
}
```

***

## Acceso de artefactos a tablas

Un artefacto **no puede leer/escribir** una tabla por default. Hay que conceder acceso explícito.

### Listar tablas accesibles

```http theme={null}
GET {BASE_URL}/studio/artefactos/{id}/tablas-acceso
```

### Conceder acceso

```http theme={null}
POST {BASE_URL}/studio/artefactos/{id}/tablas-acceso
Content-Type: application/json

{ "tabla_id": "uuid-tabla", "permisos": ["read", "write"] }
```

**Permisos disponibles:**

```json theme={null}
["read"]                     // Solo lectura
["read", "write"]            // Lectura + insertar/actualizar
["read", "write", "delete"]  // Control total
```

<Tip>
  Aplica **principio de mínimo privilegio**: un dashboard de consulta solo necesita `["read"]`.
</Tip>

### Actualizar permisos

```http theme={null}
PUT {BASE_URL}/studio/artefactos/{id}/tablas-acceso/{tabla_id}
Content-Type: application/json

{ "permisos": ["read"] }
```

### Revocar acceso

```http theme={null}
DELETE {BASE_URL}/studio/artefactos/{id}/tablas-acceso/{tabla_id}
```

<Note>
  La tabla debe pertenecer al **mismo equipo** que el artefacto.
</Note>

***

## Endpoints públicos — Datos del artefacto

Los usa la **app publicada** desde el frontend. Se autentican con la `api_key` del artefacto.

**Base URL:** `{BASE_URL}/public/artefacto/{artefacto_id}`

**Auth:** `X-API-Key: puente_artifact_xxxxxxxxxxxx`

### Obtener metadatos de tabla

```http theme={null}
GET /public/artefacto/{artefacto_id}/tablas/{tabla_id}
```

Permiso requerido: `read`.

### Listar filas (con filtros)

```http theme={null}
GET /public/artefacto/{artefacto_id}/tablas/{tabla_id}/datos?limit=50&offset=0
```

Permiso requerido: `read`.

#### Filtros con `where`

```
?where=(campo,operador,valor)~and(campo2,operador2,valor2)
```

**Operadores:**

| Operador                    | Descripción                               |
| --------------------------- | ----------------------------------------- |
| `eq` / `neq`                | Igual / no igual                          |
| `gt` / `gte` / `lt` / `lte` | Comparaciones numéricas                   |
| `like` / `nlike`            | Contiene / no contiene (case-insensitive) |
| `starts` / `ends`           | Empieza / termina con                     |
| `is` / `isnot`              | Es `null`, `notnull`, `true`, `false`     |
| `in` / `notin`              | En lista / no en lista                    |
| `empty` / `notempty`        | Nulo o vacío / no                         |

**Operadores lógicos:** `~and`, `~or`.

**Ejemplos:**

```
?where=(plan,eq,Pro)~and(activo,is,true)
?where=(monto,gte,1000)~and(monto,lte,5000)
?where=(activo,is,true)&limit=20&offset=40
```

<Warning>
  Si el valor contiene espacios o caracteres especiales, URL-encodea el parámetro completo.
</Warning>

**Respuesta:**

```json theme={null}
[
  {
    "id": "uuid-fila",
    "tabla_id": "uuid-tabla",
    "fila_data": {
      "fecha": "2026-04-29",
      "sucursal": "Santiago",
      "monto": 1250000
    },
    "created_at": "2026-04-29T12:00:00Z"
  }
]
```

<Note>
  El campo de datos se llama **`fila_data`**, no `datos`. Es un gotcha frecuente.
</Note>

#### Headers de rate limit en respuesta

```http theme={null}
X-RateLimit-Limit-Minute: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1709988123
```

### Insertar fila

```http theme={null}
POST /public/artefacto/{artefacto_id}/tablas/{tabla_id}/dato
Content-Type: application/json

{ "datos": { "campo": "valor", "monto": 1000 } }
```

Permiso requerido: `write`.

### Actualizar fila

```http theme={null}
PUT /public/artefacto/{artefacto_id}/tablas/{tabla_id}/dato/{fila_id}
Content-Type: application/json

{ "datos": { "monto": 2000 } }
```

Permiso requerido: `write`.

### Eliminar fila

```http theme={null}
DELETE /public/artefacto/{artefacto_id}/tablas/{tabla_id}/dato/{fila_id}
```

Permiso requerido: `delete`.

***

## Límites del sistema

| Concepto                               | Límite                   |
| -------------------------------------- | ------------------------ |
| API keys activas por artefacto         | 1                        |
| Tablas accesibles por artefacto        | Ilimitado                |
| Filas retornadas por request (pública) | 500 max                  |
| Filas retornadas por request (privada) | 5,000 max                |
| Bulk insert                            | 10,000 filas por request |
| Rate limit default / minuto            | 60 requests              |
| Rate limit default / hora              | 1,000 requests           |
| Rate limit default / día               | 10,000 requests          |

***

## Códigos de error

### Con STUDIO\_KEY (gestión)

| Código | Significado                     | Qué hacer                                       |
| ------ | ------------------------------- | ----------------------------------------------- |
| `401`  | STUDIO\_KEY inválida o revocada | Generar nueva en app.puente.xyz → Configuración |
| `403`  | Sin créditos                    | Contactar admin de la cuenta                    |
| `404`  | Recurso no existe en tu equipo  | Verificar ID con endpoint de listado            |
| `422`  | Campo con formato incorrecto    | Leer mensaje — indica qué campo falló           |

### Con API Key de artefacto (públicos)

| Código                      | Causa                                      | Solución                                 |
| --------------------------- | ------------------------------------------ | ---------------------------------------- |
| `400` Datos inválidos       | Tipos incorrectos o requerido faltante     | Revisar schema de columnas               |
| `401` API Key requerida     | Falta header                               | Incluir `X-API-Key: puente_artifact_xxx` |
| `401` API Key inválida      | Key incorrecta, revocada o tipo incorrecto | Verificar o regenerar                    |
| `401` API Key no autorizada | Key pertenece a otro artefacto             | Usar la key correcta                     |
| `403` Acceso no configurado | El artefacto no tiene acceso a esa tabla   | `POST .../tablas-acceso`                 |
| `403` Permiso insuficiente  | Permisos no incluyen la acción             | `PUT .../tablas-acceso/{tabla_id}`       |
| `429` Rate limit excedido   | Demasiados requests                        | Esperar `Retry-After` segundos           |

***

## Ejemplo en JavaScript (dentro de una app publicada)

```javascript theme={null}
const API_KEY      = 'puente_artifact_xxxxxxxxxxxx';
const ARTEFACTO_ID = 123;
const TABLA_ID     = 'uuid-de-la-tabla';
const BASE         = `https://puente-backend-721178029791.southamerica-west1.run.app/public/artefacto/${ARTEFACTO_ID}`;

// Leer datos con filtro
const res = await fetch(
  `${BASE}/tablas/${TABLA_ID}/datos?where=(activo,is,true)&limit=50`,
  { headers: { 'X-API-Key': API_KEY } }
);
const filas = await res.json();

// Insertar fila
await fetch(`${BASE}/tablas/${TABLA_ID}/dato`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY },
  body: JSON.stringify({ datos: { nombre: 'Ana', monto: 100, fecha: '2026-04-29' } })
});

// Manejar rate limit
if (res.status === 429) {
  const retryAfter = res.headers.get('Retry-After') || 60;
  console.warn(`Rate limit excedido. Reintentar en ${retryAfter}s`);
}
```

<Warning>
  La `api_key` del artefacto es **visible en el código fuente** de la app publicada (el bundle se sirve al browser del usuario). Usa siempre el mínimo privilegio (`["read"]` si solo lees) para limitar el riesgo si la key se filtra.
</Warning>
