> For the complete documentation index, see [llms.txt](https://doc.velneo.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://doc.velneo.com/38/velneo-vserver/api-rest-en-vserver/api-rest-de-instancia.md).

# API REST de instancia

[Velneo vServer](/38/velneo-vserver/que-es-velneo-vserver.md) puede publicar directamente, a través de su [servicio HTTPS](/38/velneo-vserver/servicio-https.md), una **API REST** sobre los datos de una [instancia](/38/velneo-vadmin/gestion-de-aplicaciones/instancias.md), sin necesidad de Velneo vModApache ni de programar nada en la aplicación.

La API REST de instancia permite:

* Consultar la estructura de las tablas.
* Listar, leer, dar de alta, modificar y eliminar registros, con filtros, ordenación y paginación.
* Leer los campos de tipo objeto (imágenes, documentos…).
* Consultar tablas estáticas.
* Ejecutar procesos y búsquedas de la aplicación.

Incluye la especificación **OpenAPI 3.1** generada automáticamente para cada instancia y un visor **Swagger UI** para probarla desde el navegador.

{% hint style="info" %}
No hay que confundir esta API con la API REST de Velneo vERP, que forma parte de la plantilla vERP y se configura desde la propia aplicación, ni con la API REST de administración de Velneo vServer, que sirve para administrar el servidor.
{% endhint %}

## Requisitos

* Licencia de Velneo vServer con servicio HTTPS.
* La instancia debe estar en marcha. Si está pausada o en mantenimiento, la API responde con un error 503 y la cabecera `Retry-After: 30`.

## Qué se publica

Lo que se publica de la aplicación depende de las propiedades de los propios objetos y de los permisos del perfil de seguridad de la ruta:

| Objeto                                    | Condición                                                       |
| ----------------------------------------- | --------------------------------------------------------------- |
| Tablas, campos e índices                  | No privados. Se incluyen las tablas de los proyectos heredados. |
| Tablas estáticas                          | No privadas. Solo lectura.                                      |
| Procesos                                  | No privados y con el estilo accesible web activado.             |
| Búsquedas                                 | No privadas.                                                    |
| Variables locales de procesos y búsquedas | Las no privadas son los parámetros de la API.                   |

{% hint style="info" %}
Los objetos privados nunca se publican, aunque el perfil tenga acceso completo. Los campos de tipo fórmula son siempre de solo lectura.
{% endhint %}

## Configuración en Velneo vAdmin

La API se publica creando una [ruta](/38/velneo-vadmin/web.md#rutas) de tipo **Apirest instancia** en un *virtual host* del servicio HTTPS:

1. Abrir Velneo vAdmin y conectarse al servidor.
2. En la opción [Web](/38/velneo-vadmin/web.md), editar el *virtual host* y añadir una ruta de tipo **Apirest instancia**, por ejemplo `/mascotas_api`.
3. En **Instancia**, seleccionar la instancia cuyos datos se publican. Puede ser una instancia de datos o de aplicación; en este último caso se usará su instancia de datos.
4. En **Usuario**, indicar un usuario de Velneo vServer con permiso sobre la instancia. Las peticiones se ejecutan con este usuario.
5. Configurar la autenticación y los permisos en sus [perfiles de seguridad](/38/velneo-vadmin/web.md#perfiles-de-seguridad). Con **Acceso restringido** se puede indicar, tabla a tabla y campo a campo, qué verbos se permiten, qué índices se pueden usar y qué tablas estáticas, procesos y búsquedas están disponibles.
6. Aceptar, guardar la configuración y reiniciar el servicio HTTPS.

{% hint style="danger" %}
Una ruta sin ningún método de seguridad **no se sirve** y responde con un error 503.
{% endhint %}

{% hint style="warning" %}
Permitir un proceso o una búsqueda implica confiar en todo lo que hace internamente: puede leer o escribir en otras tablas a las que tenga acceso el usuario de la ruta. Un proceso con destino (ficha o lista) requiere además permiso de escritura sobre su tabla de destino.
{% endhint %}

## Autenticación

Todas las peticiones deben incluir la cabecera:

`Authorization: Bearer <API Key o token OAuth>`

Una credencial solo es válida en la ruta para la que se ha configurado. Si se usa en otra ruta o para otra instancia, se responde con un error 403.

## URL base

`https://dominio_del_servidor[:puerto]/ruta/`

Por ejemplo: `https://api.midominio.com/mascotas_api/`. Una petición a la ruta sin barra final se redirige (302) a la ruta con barra.

## Endpoints

Paths relativos a la URL base:

| Método      | Path                                         | Descripción                               |
| ----------- | -------------------------------------------- | ----------------------------------------- |
| GET         | `data/tables`                                | Lista de tablas accesibles.               |
| GET         | `data/tables/{tabla}/schema`                 | Estructura de la tabla: campos e índices. |
| GET         | `data/tables/{tabla}/records`                | Lista de registros.                       |
| POST        | `data/tables/{tabla}/records`                | Alta de un registro.                      |
| POST        | `data/tables/{tabla}/records/bulk`           | Alta de varios registros.                 |
| GET         | `data/tables/{tabla}/records/{code}`         | Lee un registro.                          |
| PUT / PATCH | `data/tables/{tabla}/records/{code}`         | Modifica un registro.                     |
| DELETE      | `data/tables/{tabla}/records/{code}`         | Elimina un registro.                      |
| GET         | `data/tables/{tabla}/records/{code}/{campo}` | Contenido de un campo de tipo objeto.     |
| GET         | `data/static-tables`                         | Lista de tablas estáticas.                |
| GET         | `data/static-tables/{tabla}/items`           | Elementos de una tabla estática.          |
| GET         | `data/static-tables/{tabla}/items/{code}`    | Un elemento de una tabla estática.        |
| POST        | `data/processes/{id}`                        | Ejecuta un proceso.                       |
| GET         | `data/queries/{id}`                          | Ejecuta una búsqueda.                     |
| GET         | `openapi.json`                               | Especificación OpenAPI 3.1 de la API.     |
| GET         | `docs`                                       | Visor Swagger UI.                         |

Todos los GET admiten también HEAD (mismas cabeceras, sin cuerpo).

## Peticiones y respuestas

* Las peticiones con cuerpo deben enviarse en JSON (`Content-Type: application/json`). Otro tipo de contenido se rechaza con un error 415.
* Las respuestas son JSON (`application/json; charset=utf-8`) y no se almacenan en caché (`Cache-Control: no-store`).
* Todas las filas incluyen la propiedad `_dir`, número interno del registro en la tabla.

{% hint style="warning" %}
El `_dir` identifica el registro en ese momento, pero **no es permanente**: puede cambiar, por ejemplo, tras una regeneración de la tabla. Para guardar referencias a registros en sistemas externos, usa la clave primaria del registro.
{% endhint %}

### Listar registros

`GET data/tables/{tabla}/records`

| Parámetro                       | Descripción                                                                         |
| ------------------------------- | ----------------------------------------------------------------------------------- |
| `limit`                         | Número de registros por página, entre 1 y 500. Por defecto 50.                      |
| `offset`                        | Número de registros a saltar.                                                       |
| `fields`                        | Campos a devolver, separados por comas.                                             |
| `sort`                          | Campos de ordenación separados por comas. `CAMPO` ascendente, `-CAMPO` descendente. |
| `filter[CAMPO]=valor`           | Filtro de igualdad.                                                                 |
| `filter[CAMPO][operador]=valor` | Filtro con operador. Se pueden combinar varios filtros (se aplican todos).          |

#### Operadores de filtro

| Operador                   | Significado                                                          |
| -------------------------- | -------------------------------------------------------------------- |
| `eq` / `ne`                | Igual / distinto.                                                    |
| `gt` / `gte`               | Mayor / mayor o igual.                                               |
| `lt` / `lte`               | Menor / menor o igual.                                               |
| `between`                  | Entre dos valores separados por coma: `filter[EDAD][between]=18,65`. |
| `contains` / `notContains` | Contiene / no contiene el texto.                                     |
| `empty` / `notEmpty`       | Vacío / no vacío.                                                    |

También se puede filtrar por el número interno del registro, por ejemplo `filter[_dir][between]=100,200`.

Para filtrar u ordenar por un campo, el perfil debe permitir GET sobre el campo y, si el campo es un índice, el uso de ese índice.

Ejemplo:

```http
GET /mascotas_api/data/tables/CLIENTES/records?limit=20&sort=-ALTA&filter[EDAD][gte]=18&fields=ID,NOMBRE
Authorization: Bearer ak_xxxxxxxx…
```

```json
{
  "totalRecords": 123, "offset": 0, "limit": 20, "count": 20, "hasMore": true,
  "records": [
    { "ID": 1, "NOMBRE": "Ana", "_dir": 1 }
  ]
}
```

Los campos de tipo objeto se devuelven como la URL desde la que se puede descargar su contenido, o `null` si están vacíos.

{% hint style="info" %}
Las tablas sin índice utilizable para recorrerlas no admiten filtros ni ordenación. En ese caso la respuesta incluye `"totalIsExact": false` y la propiedad `next_since_dir`, que se pasará en el parámetro `since_dir` para obtener la página siguiente.
{% endhint %}

### Leer un registro

`GET data/tables/{tabla}/records/{code}`

El registro se puede identificar de varias formas:

* Por su `_dir`: `records/57`.
* Por su clave alfanumérica, si la clave primaria de la tabla es un único campo alfabético: `records/ES`.
* Por el valor de un campo concreto: `records/12345?keyField=NIF`.
* Por una clave única compuesta (por ejemplo, en submaestras): `records/x?index=IDX_UNICO&key[CAMPO1]=valor1&key[CAMPO2]=valor2`.

Admite el parámetro `fields` para devolver solo algunos campos.

Los campos puntero a una tabla con clave compuesta se devuelven como un objeto con los valores de cada parte de la clave; la estructura de la tabla (`schema`) indica sus componentes en la propiedad `pointerKey`.

### Alta de un registro

`POST data/tables/{tabla}/records`

```json
{ "fields": { "NOMBRE": "Ana", "FOTO": "data:image/png;base64,iVBORw0…" } }
```

Respuesta: **201 Created**, con la cabecera `Location` apuntando al nuevo registro:

```json
{
  "success": true, "_dir": 57, "key": { "ID": 57 }, "id": 57,
  "record": { "ID": 57, "NOMBRE": "Ana", "_dir": 57 },
  "fieldsIgnored": ["EMAILX"]
}
```

* `record`: el registro tal como ha quedado grabado (con autonuméricos, valores por defecto, fórmulas y cambios de los *triggers*).
* `key`: clave primaria del registro. `id` solo se devuelve si la clave es de un único campo.
* `fieldsIgnored`: campos enviados que no existen en la tabla. Si ninguno de los campos existe, se responde con un error 400.
* Si se envía algún campo sin permiso, de tipo fórmula o privado, se responde con un error 403 indicando los campos en `forbiddenFields` y no se graba nada.
* Si ya existe un registro con la misma clave, se responde con un error **409**. Si un *trigger* rechaza el alta, se responde con un error 400.

Los campos de tipo objeto se envían en base64 (se admite el prefijo `data:…;base64,`), salvo los de tipo texto o texto enriquecido, que se envían como texto. Un valor `null`, o la misma URL devuelta por la lectura, deja el campo sin modificar.

### Alta de varios registros

`POST data/tables/{tabla}/records/bulk`

```json
{ "records": [ { "NOMBRE": "Ana" }, { "NOMBRE": "Luis" } ] }
```

Máximo 10.000 registros por petición. Cada registro se graba de forma independiente (no es una operación transaccional): la respuesta indica los registros creados (`created`) y los errores de cada uno (`errors`), con su posición en la lista (`index`).

### Modificar un registro

`PUT` o `PATCH data/tables/{tabla}/records/{code}`, con el mismo formato de cuerpo que el alta. Se modifican únicamente los campos enviados. Si se modifica la clave y ya existe otro registro con esa clave, se responde con un error 409.

### Eliminar un registro

`DELETE data/tables/{tabla}/records/{code}`. Requiere que el perfil permita DELETE sobre la tabla.

### Campos de tipo objeto

`GET data/tables/{tabla}/records/{code}/{campo}`

* `format=bytearray` (por defecto): contenido binario con su tipo (`image/png`, `application/pdf`, `text/plain`…).
* `format=base64`: contenido en base64 como texto. El tipo real se indica en la cabecera `X-Velneo-Object-Content-Type`.

Tamaño máximo: 32 MB. Si el campo está vacío, se responde con un error 404.

### Ejecutar un proceso

`POST data/processes/{id}`

```json
{ "arguments": { "CLIENTE": 57, "IMPORTE": 12.5 } }
```

Los argumentos son las variables locales no privadas del proceso y deben ir dentro de `arguments`. La respuesta incluye en `variables` el valor final de las variables no privadas:

```json
{
  "processId": "ALTA_PEDIDO", "processName": "Alta de pedido", "status": "success",
  "returnValue": null,
  "variables": { "CLIENTE": 57, "IMPORTE": 12.5, "NUM_PEDIDO": 1024 }
}
```

Si la ejecución supera el tiempo máximo de la ruta se responde con un error 504, pero el proceso continúa hasta finalizar.

### Ejecutar una búsqueda

`GET data/queries/{id}?VARIABLE=valor&limit=10`

Las variables no privadas de la búsqueda se pasan como parámetros. Admite `limit` y `offset`. La respuesta tiene el mismo formato que la lista de registros, con `queryId` y `queryName`.

## OpenAPI y Swagger UI

* `GET openapi.json`: especificación OpenAPI 3.1 generada para la instancia, que incluye **solo** lo que permite el perfil de la credencial utilizada. Requiere autenticación.
* `GET docs`: visor Swagger UI para consultar y probar la API desde el navegador. Al abrirlo se pide la credencial, según la seguridad de la ruta:

  * **API Key u OAuth externo**: se muestra un campo en el que pegar la API Key o el token, que se usará como *Bearer*, y el botón **Entrar**. La credencial se guarda únicamente durante la sesión del navegador.
  * **Solo restricción de IP**: el visor no puede iniciar sesión y muestra un aviso para contactar con el administrador.

  Una vez dentro, para probar una operación se despliega, se pulsa **Try it out**, se rellenan los parámetros y se pulsa **Execute**: se envía la petición real con la credencial indicada.

La especificación admite caché (`ETag`), por lo que el cliente puede volver a pedirla con `If-None-Match` y recibir 304 si no ha cambiado.

## Errores

Los errores se devuelven en formato *problem details* (RFC 9457), con `Content-Type: application/problem+json`:

```json
{
  "type": "https://velneo.com/api/problems/403",
  "title": "Forbidden",
  "status": 403,
  "detail": "El perfil no permite POST sobre la tabla 'CLIENTES'."
}
```

En los errores 5xx no se muestra el detalle interno, sino un identificador de correlación para buscarlo en el log del servidor.

<table data-search="false"><thead><tr><th>Código</th><th>Causa</th></tr></thead><tbody><tr><td>400</td><td>Parámetros no válidos, campos desconocidos, operador de filtro no válido, error al grabar.</td></tr><tr><td>401</td><td>Falta la cabecera <code>Authorization</code> o la credencial no es válida o ha caducado.</td></tr><tr><td>403</td><td>El perfil no permite la operación (la cabecera <code>Allow</code> indica los verbos permitidos), campos no permitidos (<code>forbiddenFields</code>), IP no permitida, credencial de otra ruta o instancia no expuesta en el <em>virtual host</em>.</td></tr><tr><td>404</td><td>No existe la tabla, registro, proceso, búsqueda o campo, o no está permitido.</td></tr><tr><td>405</td><td>Método no soportado en el recurso (cabecera <code>Allow</code>).</td></tr><tr><td>409</td><td>Ya existe un registro con esa clave.</td></tr><tr><td>413</td><td>Cuerpo de la petición demasiado grande.</td></tr><tr><td>415</td><td>El cuerpo no es JSON.</td></tr><tr><td>429</td><td>Se ha superado el límite de peticiones.</td></tr><tr><td>503</td><td>La instancia está pausada o no hay licencias de ejecución libres (cabecera <code>Retry-After</code>), o la ruta no tiene método de seguridad.</td></tr><tr><td>504</td><td>Se ha superado el tiempo máximo de la ruta.</td></tr></tbody></table>

## Log de accesos

Las peticiones se registran en el log **API REST** del servicio HTTPS, que podemos consultar en Velneo vAdmin en la opción Web.

## Límites

* **Registros por página**: 50 por defecto, máximo 500.
* **Alta masiva**: 10.000 registros por petición.
* **Campo objeto**: 32 MB.
* **API Keys**: 2 por perfil.
* **Cuerpo de la petición**: 10 MiB por defecto (configurable por perfil).

{% hint style="info" %}
Las operaciones de datos de esta API también están disponibles para agentes de IA a través del servidor MCP de Velneo vServer.
{% endhint %}
