Referencia de la API REST
Los endpoints de la API REST en la URL de tu empresa, con su método, parámetros, cuerpo, respuesta, límites de peticiones y códigos de error.
La API REST la sirve el servidor de tu empresa, en su propia URL. El token y el camino de una petición están en API REST: cómo funciona. Las cuentas, sesiones y el almacén clave-valor de tus usuarios finales, en Usuarios finales.
Antes de empezar
- Una clave API con Compatible WebHook marcado y un empleado por defecto. Sin las dos cosas el token no valida.
- El token Bearer de esa clave. Lo muestra la vista previa curl del playground Webhooks Entrantes, en la pestaña Whoami.
La URL de tu empresa
Cada empresa tiene la suya: https://<clave-de-conexion>.dinaup.io/api, con la clave de conexión de la empresa en minúsculas. Play la muestra en tres sitios: Tu endpoint base en Desarrollo → Inicio, Endpoint Base en Desarrollo → Conectar → Webhooks Entrantes y Base URL en la tarjeta Cómo conectar de Administración → Claves API.
Los cuerpos de las peticiones van en JSON. Las respuestas también, salvo las de dynamicdocuments, /ping y /version y la mayoría de los mensajes de error, que llegan como texto. Las propiedades de las respuestas JSON llegan en camelCase (currentPage, rowID, aError).
Endpoints
Las rutas no distinguen mayúsculas de minúsculas y admiten una barra final. Una ruta bajo /api que no está en la tabla responde 404 sin cuerpo. Con otro método, la respuesta es 405 sin cuerpo.
| Método | Ruta | Para qué |
|---|---|---|
GET | /api/whoami | El usuario que hay detrás de la clave. |
POST | /api/reports | Ejecutar un informe de Flex. |
POST | /api/dynamicdocuments | Renderizar un documento dinámico. |
POST | /api/writeoperations | Crear, editar y borrar registros. |
POST | /api/Register, /api/ActivateAccount, /api/RecoverPassword, /api/ChangePassword | Cuentas de tus usuarios finales. Usuarios finales |
POST | /api/Login, /api/Session, /api/Logout | Sesiones de tus usuarios finales. |
POST | /api/RequestLoginCode, /api/LoginWithCode | Inicio de sesión con un código de un solo uso. |
POST | /api/GetKV, /api/SetKV | Almacén clave-valor. |
GET | /ping, /version | Estado y versión del servidor. Fuera de /api y sin token. |
Autenticación
Toda petición a /api lleva el token en la cabecera Authorization:
Authorization: Bearer <token>El token no es la clave API: se copia de la tarjeta Cómo conectar de Claves API. Solo vale el de una clave con Compatible WebHook marcado, con secreto y con empleado por defecto. Dónde copiarlo: El token Bearer.
Sin token, con un formato incorrecto o con un token que no corresponde a ninguna clave, la respuesta es 401. Llega con la cabecera WWW-Authenticate: Bearer y un cuerpo {"error": "..."}. Los tres mensajes posibles son Bearer token was not provided., The provided token has an invalid format. y The provided token is invalid.
reports y writeoperations comprueban los parámetros y el cuerpo antes que el token. Un 400 de esos endpoints no confirma que el token sea válido.
Límites
Cada clave API tiene su propia cola en el servidor de tu empresa. Una petición que no cabe en estos límites se rechaza sin ejecutarse:
| Límite | Valor | Al superarlo |
|---|---|---|
| Peticiones a la vez | Tantas como vCores tiene la clave. La cabecera X-Dinaup-VCores de cada respuesta dice cuántos son. Si Dinaup fija un tope de vCores en tu licencia, todas las claves lo comparten. | Las demás esperan turno en la cola, por orden de llegada. |
| Cola de la clave | 100 peticiones esperando turno. | La siguiente recibe 503, sin Retry-After. |
| Espera de turno | 30 segundos por defecto, desde que llega la petición. Con la cabecera X-Dinaup-Timeout-Ms pides menos: los milisegundos que esperas tú, 1000 como mínimo. | 503 con Retry-After: 2. |
| Ritmo | Solo si Dinaup fija un ritmo en tu licencia. Cada clave admite un número de peticiones por segundo y vCore, y guarda un minuto de ese ritmo para las ráfagas. La licencia entera puede tener además su propio ritmo. | 429 con Retry-After: los segundos que faltan para la siguiente petición, 1 como mínimo. |
| Tamaño del cuerpo | 1.232.896 bytes por defecto. | 413. |
Con 429 y con 503, espera antes de repetir la petición. Retry-After, cuando viene, dice cuántos segundos.
Estado del servidor
/ping y /version cuelgan de la raíz de la URL, no de /api, y no piden token.
| Ruta | Respuesta |
|---|---|
GET /ping | 200 con el texto pong. Mientras el servidor arranca, 503. |
GET /version | 200 con la versión del servidor en texto. Responde también mientras el servidor arranca. |
curl "https://<clave-de-conexion>.dinaup.io/ping"GET /api/whoami: Usuario de la clave
Devuelve el usuario con el que operan las peticiones de datos: el empleado por defecto de la clave.
curl "https://<clave-de-conexion>.dinaup.io/api/whoami" -H "Authorization: Bearer <token>"{ "user": "..." }user es un texto con los datos de la sesión que abre el servidor de tu empresa. Sirve para comprobar que el token vale y qué empleado firma. Si el servidor rechaza la petición, llega su código 4xx con el motivo en texto. Cualquier otro fallo responde 500 sin cuerpo.
POST /api/reports: Consultar informes
Ejecuta un informe de Flex y devuelve una página de sus filas en JSON. El informe define las columnas, los filtros y las agrupaciones. La petición elige la página y puede añadir filtros y orden.
Parámetros de la URL
| Parámetro | Tipo | Por defecto | Qué hace |
|---|---|---|---|
id | GUID | obligatorio | El identificador del informe. Con el de una sección, el servidor usa el informe principal de esa sección. |
page | entero | 1 | La página. Tiene que ser mayor que 0. |
resultsPerPage | entero | 10 | Filas por página. Tiene que ser mayor que 0. |
withFiles | booleano | false | Incluye los archivos adjuntos de las filas en files. |
safeColumnsName | booleano | false | Nombra las columnas por su GUID en vez de por su nombre. Renombrar un campo en Dinaup no rompe tu integración. |
calculatePages | booleano | true | Con false el servidor no cuenta las filas: totalPages llega a -1 y la respuesta tarda menos en informes grandes. |
Los booleanos van como true o false. Un booleano o un entero mal escrito responde 400 con un cuerpo application/problem+json.
Cuerpo
El cuerpo es opcional. Admite dos formas.
Un diccionario plano con las variables del informe, todas como texto:
{ "desde": "2026-01-01", "hasta": "2026-01-31" }O una forma estructurada con variables, filtros y orden. Solo admite esas tres propiedades:
{
"variables": { "desde": "2026-01-01" },
"filter": [
{ "field": "pr_estado", "op": "=", "value": "1" }
],
"order": [
{ "field": "fechaia", "desc": true }
]
}| Propiedad | Reglas |
|---|---|
variables | Objeto de textos. Admite null como valor. |
filter | Lista de { "field", "op", "value" }. Operadores: =, <>, >, <, >=, <=, * (contiene) e in. |
order | Lista de { "field", "desc" }. desc es opcional. Máximo 5 criterios, sin campos repetidos y sin espacios en el nombre. |
in recibe varios valores separados por [|]; en equivale a in. Cualquier otro operador responde 400 con la lista de los válidos. El valor de cada filtro sigue el formato del tipo de su campo, igual que en las escrituras: un valor que no lo cumple responde 400 con el motivo.
field admite cuatro formas: el nombre de la columna en la base (pr_..., con letras, dígitos, _ y -), un campo de sistema, el GUID de la columna o un camino guidDeRelacion.campo. Entre los campos de sistema están id, nombre, fecha, fecham, fechaia, eliminado y posicion. Un nombre fuera de esas reglas, o un campo que la sección del informe no tiene, responde 400 con el motivo.
Si el informe tiene variables, el cuerpo lleva sus valores.
Petición
curl -X POST "https://<clave-de-conexion>.dinaup.io/api/reports?id=<id-del-informe>&page=1&resultsPerPage=100&safeColumnsName=true" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d "{}"Respuesta
{
"data": [
{ "columna1": "valor", "columna2": "valor" }
],
"currentPage": 1,
"totalPages": 5,
"totalResults": 0,
"files": null
}| Campo | Qué es |
|---|---|
data | Las filas. Cada valor llega como texto. |
currentPage | La página que pediste. |
totalPages | Las páginas del informe. -1 si no hay recuento: con calculatePages=false, o cuando el recuento no termina en 4 segundos. 1 si el informe tiene marcado Ocultar la paginación. |
totalResults | Siempre 0. No trae el total de filas. |
files | Con withFiles=true, los adjuntos de las filas: id, name, file, path, extension, height, width, crc, sizeInBytes, compatibilityPreview, mime, isImage y las URL url_original, url_1080, url_720, url_300, url_100 y url_32. Sin adjuntos, null. |
Paginar
totalResults no trae el total y totalPages no siempre dice el número real de páginas. Para recorrer un informe, pide páginas hasta que llegue una con menos filas que resultsPerPage. Con calculatePages=false, cada página responde antes. resultsPerPage no tiene máximo, pero la consulta de cada página tiene 30 segundos por defecto.
La página más alta que sirve el servidor es 50000 / resultsPerPage + 1, con división entera: con resultsPerPage=100, la 501. Por encima de esa, data llega vacía aunque el informe tenga más filas. Para recorrer más filas, divide el informe en tramos con filter y pagina cada tramo.
Errores
| Código | Cuándo |
|---|---|
400 | Un parámetro de la URL no es válido, el cuerpo no es JSON válido, o un filtro u orden no cumple las reglas. El cuerpo lleva el motivo en inglés. |
400 del servidor | El servidor de tu empresa no encuentra el informe (No se ha detectado listado.) o rechaza un filtro o un orden: un campo que la sección no tiene, o un valor que no cumple el formato de su campo. |
401 | El token no vale, o el empleado de la clave no puede consultar el informe: No tiene permisos para consultar este informe. |
500 | Cualquier otro fallo al ejecutar el informe. El cuerpo lleva el motivo. |
POST /api/dynamicdocuments: Documentos dinámicos
Renderiza un documento dinámico y devuelve su contenido. Qué hacer con ese HTML: Mostrar e imprimir.
| Parámetro | Tipo | Qué hace |
|---|---|---|
id | GUID | El identificador del documento. Obligatorio. |
El cuerpo es opcional: un diccionario plano con las variables del documento, todas como texto.
curl -X POST "https://<clave-de-conexion>.dinaup.io/api/dynamicdocuments?id=<id-del-documento>" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d "{\"clienteId\": \"123e4567-e89b-12d3-a456-426614174000\"}"La respuesta es 200 con el documento renderizado como texto (text/plain), no como JSON. Un id que no es GUID o un cuerpo que no es un diccionario de textos responden 400. Si el documento no existe o está eliminado, el servidor de tu empresa responde 400 con el motivo; otros rechazos suyos llegan también con su 4xx. Cualquier otro fallo responde 500 con el motivo en el cuerpo.
POST /api/writeoperations: Escribir datos
Crea y edita registros en una sección. El mismo endpoint sirve para altas, ediciones y borrados lógicos. Cómo se comporta el servidor al escribir (autorrellenado, orden de los campos, scripts): Escribir en secciones.
Parámetros de la URL
| Parámetro | Tipo | Qué hace |
|---|---|---|
sectionId | GUID | La sección donde escribir. Obligatorio. Los IDs de las secciones de fábrica están en Secciones núcleo. |
FieldPrimary | texto | El campo que identifica el registro. Obligatorio; normalmente id. |
scripts | booleano | Con true el servidor ejecuta los scripts de la sección al guardar, igual que la interfaz. Guarda un registro cada vez y espera 100 ms tras cada uno: un lote de 25 tarda al menos 2,5 segundos. Las escrituras con scripts de todas las peticiones hacen cola en el servidor de tu empresa. Con false el servidor intenta escribir el lote de una vez. Por defecto false. |
El cuerpo es obligatorio y va con Content-Type: application/json. Sin esa cabecera, la respuesta es 415.
Crear, editar y borrar
La operación la decide el campo id de cada objeto:
id | Operación |
|---|---|
"" o ausente | Alta. Dinaup asigna el ID y lo devuelve en rowID. |
| Un GUID existente | Edición de los campos que envías. Los demás no se tocan. |
No hay DELETE. Un borrado es una edición con eliminado a "1":
{ "id": "123e4567-e89b-12d3-a456-426614174000", "eliminado": "1" }Al borrar, scripts va en false
Deja scripts fuera de la URL en toda petición que ponga eliminado a "1".
Los valores
Cada campo se nombra por su columna en la base, pr_XXXXXXXXX. Los nombres de cada sección están en Desarrollo → Esquema de Play (Esquema) y en la biblioteca MyDinaup. Todos los valores viajan como texto:
| Tipo | Formato | Ejemplo |
|---|---|---|
| Decimal | Punto decimal, sin separador de miles | "1234.5" |
| Entero | Dígitos | "42" |
| Booleano | 1 o 0 | "1" |
| Fecha y hora | yyyy-MM-dd HH:mm:ss, en UTC | "2026-08-10 14:30:00" |
| Fecha | yyyy-MM-dd | "2026-08-10" |
| Hora | HH:mm:ss | "14:30:00" |
| Referencia a otro registro | Su GUID, "" para dejarla sin valor, o [campo=valor] para que el servidor busque el registro por nombre o por una columna pr_... | "123e4567-...", "[nombre=Kilogramos]" |
Un número JSON llega como su texto y null llega vacío. true y false llegan como True y False, que un campo booleano no acepta: usa "1" y "0". El servidor quita los espacios del principio y del final de cada valor antes de escribir.
Estos campos los gestiona el servidor. Si los envías, se descartan en silencio, sin error y sin guardarse:
fecha · fecham · fechaia · fechasyn · modificado · plantillapid · usuarioid · ubicacionid · ubicacionLas cuatro formas del cuerpo
Un registro:
{ "id": "", "pr_cliente": "<id-del-cliente>", "pr_importe": "100.00" }Un registro con sus líneas (Main y List), en secciones con tabla de líneas como una venta:
{
"Main": { "id": "", "pr_cliente": "<id-del-cliente>" },
"List": [
{ "pr_item": "<id-del-producto>", "pr_cantidad": "10" },
{ "pr_item": "<id-del-producto>", "pr_cantidad": "20" }
]
}Un lote de registros:
[
{ "id": "", "pr_campo": "valor1" },
{ "id": "", "pr_campo": "valor2" }
]Un lote de registros con líneas:
[
{ "Main": { "id": "", "pr_campo": "valor1" }, "List": [ { "pr_item": "..." } ] },
{ "Main": { "id": "", "pr_campo": "valor2" }, "List": [ { "pr_item": "..." } ] }
]Un objeto se trata como registro con líneas cuando tiene a la vez las propiedades Main y List, con la inicial en mayúscula. En un lote, el primer objeto decide la forma de todos. Cualquier otro tipo de cuerpo responde 400 con Invalid data format. Un elemento del lote que no es un objeto, un Main que no es un objeto o un List que no es una lista también responden 400, con el motivo.
Límites de un lote
| Límite | Valor |
|---|---|
| Operaciones por petición | 25. |
| Operaciones más líneas, en total | 2.500 |
| Lista vacía | Rechazada con 400 e Invalid data format. |
| Juego de campos | Todos los objetos del lote llevan los mismos campos, y todas las líneas también. Si uno lleva tres y otro cuatro, el lote entero falla. |
Un lote que pasa de un tope o mezcla juegos de campos falla entero con 400 y el motivo, sin escribir nada. Para más de 25 registros, divide el lote en varias peticiones. Cuando un campo no aplica a un objeto, envíalo vacío en ese objeto o agrupa los objetos por juego de campos en peticiones distintas. En una edición, el vacío es un valor: borra lo que el registro tuviera.
Petición
curl -X POST "https://<clave-de-conexion>.dinaup.io/api/writeoperations?sectionId=<id-de-la-seccion>&FieldPrimary=id&scripts=true" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d "{\"id\": \"\", \"pr_cliente\": \"<id-del-cliente>\", \"pr_importe\": \"100.00\"}"Respuesta
Un registro suelto devuelve un objeto; un lote devuelve una lista con un objeto por registro:
{
"sectionID": "...",
"aError": "",
"description": null,
"dynamicDocumentResult": "",
"optimized": false,
"rowID": "123e4567-e89b-12d3-a456-426614174000",
"tokenID": "...",
"confirmed": true,
"isAdded": true,
"dateResult": "2026-09-23T10:00:00.0000000Z"
}| Campo | Qué dice |
|---|---|
confirmed | La fila se guardó. |
aError | El motivo del fallo de esa fila. Vacío si fue bien. |
rowID | El ID del registro; en un alta, el que asignó Dinaup. |
isAdded | true si se creó, false si se editó. |
Algunos errores los detecta el servidor antes de escribir. Con ellos la petición entera responde 400 con el motivo, también en un lote, y no se escribe ninguna fila:
- Un campo que no existe en la sección.
- Un valor que no cumple el formato de su tipo o pasa del largo máximo del campo.
- Con
FieldPrimary=id, unidque no es un GUID o que no existe. - Una referencia a un registro que no existe o está eliminado.
- Un enlace
[campo=valor]que no encuentra ninguna fila.
Una fila que el servidor rechaza al guardarla no falla igual en un registro suelto que en un lote:
| Envías | Si el servidor rechaza una fila al guardarla | Código |
|---|---|---|
| Un registro, con o sin líneas | La petición falla entera | 500, con el motivo en el cuerpo |
| Un lote | Las demás filas se escriben | 200, con un resultado por fila |
En un lote, el código de respuesta no basta
Un lote con la mitad de las filas rechazadas responde 200. Comprueba confirmed y aError de cada resultado. Si reintentas, reintenta solo las filas con aError: repetir el lote entero duplica las que sí entraron.
Las escrituras respetan los permisos de la clave. Si su empleado no puede escribir en la sección, la operación falla con el motivo.
Códigos de respuesta
| Código | Cuándo |
|---|---|
200 | Operación correcta. En un lote de writeoperations, revisa cada fila. |
400 | Parámetros o cuerpo inválidos. El cuerpo lleva el motivo. |
401 | Token ausente o inválido. Llega con WWW-Authenticate: Bearer. |
404 | La ruta no existe bajo /api. Sin cuerpo. |
405 | Método incorrecto para la ruta. |
413 | El cuerpo pasa del tamaño máximo. |
415 | El cuerpo no lleva Content-Type: application/json en writeoperations o en los endpoints de usuarios finales. |
429 | Ritmo agotado. Repite pasados los segundos de Retry-After. |
4xx del servidor | En todos los endpoints, el rechazo del servidor de tu empresa llega como 4xx con su mensaje: un campo o un valor que no valen, un informe o un documento que no existe, un permiso que falta. Así las plataformas de automatización no lo repiten como un fallo temporal. |
500 | Fallo al ejecutar la operación, como un registro suelto de writeoperations que el servidor no pudo guardar. El cuerpo lleva el motivo. |
503 | El servidor arranca, la base de datos no responde (Retry-After: 5), la cola de la clave está llena, o el turno no llegó a tiempo (Retry-After: 2). |
Relacionado
- API REST: cómo funciona: el token y el camino de una petición.
- Usuarios finales: cuentas, sesiones y clave-valor.
- Webhooks Entrantes: el playground que ejecuta estas llamadas desde el navegador.
- SDK .NET: el cliente .NET con clases tipadas, con su propia conexión.