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étodoRutaPara qué
GET/api/whoamiEl usuario que hay detrás de la clave.
POST/api/reportsEjecutar un informe de Flex.
POST/api/dynamicdocumentsRenderizar un documento dinámico.
POST/api/writeoperationsCrear, editar y borrar registros.
POST/api/Register, /api/ActivateAccount, /api/RecoverPassword, /api/ChangePasswordCuentas de tus usuarios finales. Usuarios finales
POST/api/Login, /api/Session, /api/LogoutSesiones de tus usuarios finales.
POST/api/RequestLoginCode, /api/LoginWithCodeInicio de sesión con un código de un solo uso.
POST/api/GetKV, /api/SetKVAlmacén clave-valor.
GET/ping, /versionEstado 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ímiteValorAl superarlo
Peticiones a la vezTantas 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 clave100 peticiones esperando turno.La siguiente recibe 503, sin Retry-After.
Espera de turno30 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.
RitmoSolo 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 cuerpo1.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.

RutaRespuesta
GET /ping200 con el texto pong. Mientras el servidor arranca, 503.
GET /version200 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ámetroTipoPor defectoQué hace
idGUIDobligatorioEl identificador del informe. Con el de una sección, el servidor usa el informe principal de esa sección.
pageentero1La página. Tiene que ser mayor que 0.
resultsPerPageentero10Filas por página. Tiene que ser mayor que 0.
withFilesbooleanofalseIncluye los archivos adjuntos de las filas en files.
safeColumnsNamebooleanofalseNombra las columnas por su GUID en vez de por su nombre. Renombrar un campo en Dinaup no rompe tu integración.
calculatePagesbooleanotrueCon 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 }
  ]
}
PropiedadReglas
variablesObjeto de textos. Admite null como valor.
filterLista de { "field", "op", "value" }. Operadores: =, <>, >, <, >=, <=, * (contiene) e in.
orderLista 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
}
CampoQué es
dataLas filas. Cada valor llega como texto.
currentPageLa página que pediste.
totalPagesLas 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.
totalResultsSiempre 0. No trae el total de filas.
filesCon 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ódigoCuándo
400Un 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 servidorEl 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.
401El token no vale, o el empleado de la clave no puede consultar el informe: No tiene permisos para consultar este informe.
500Cualquier 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ámetroTipoQué hace
idGUIDEl 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ámetroTipoQué hace
sectionIdGUIDLa sección donde escribir. Obligatorio. Los IDs de las secciones de fábrica están en Secciones núcleo.
FieldPrimarytextoEl campo que identifica el registro. Obligatorio; normalmente id.
scriptsbooleanoCon 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:

idOperación
"" o ausenteAlta. Dinaup asigna el ID y lo devuelve en rowID.
Un GUID existenteEdició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:

TipoFormatoEjemplo
DecimalPunto decimal, sin separador de miles"1234.5"
EnteroDígitos"42"
Booleano1 o 0"1"
Fecha y horayyyy-MM-dd HH:mm:ss, en UTC"2026-08-10 14:30:00"
Fechayyyy-MM-dd"2026-08-10"
HoraHH:mm:ss"14:30:00"
Referencia a otro registroSu 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 · ubicacion

Las 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ímiteValor
Operaciones por petición25.
Operaciones más líneas, en total2.500
Lista vacíaRechazada con 400 e Invalid data format.
Juego de camposTodos 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"
}
CampoQué dice
confirmedLa fila se guardó.
aErrorEl motivo del fallo de esa fila. Vacío si fue bien.
rowIDEl ID del registro; en un alta, el que asignó Dinaup.
isAddedtrue 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, un id que 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íasSi el servidor rechaza una fila al guardarlaCódigo
Un registro, con o sin líneasLa petición falla entera500, con el motivo en el cuerpo
Un loteLas demás filas se escriben200, 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ódigoCuándo
200Operación correcta. En un lote de writeoperations, revisa cada fila.
400Parámetros o cuerpo inválidos. El cuerpo lleva el motivo.
401Token ausente o inválido. Llega con WWW-Authenticate: Bearer.
404La ruta no existe bajo /api. Sin cuerpo.
405Método incorrecto para la ruta.
413El cuerpo pasa del tamaño máximo.
415El cuerpo no lleva Content-Type: application/json en writeoperations o en los endpoints de usuarios finales.
429Ritmo agotado. Repite pasados los segundos de Retry-After.
4xx del servidorEn 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.
500Fallo al ejecutar la operación, como un registro suelto de writeoperations que el servidor no pudo guardar. El cuerpo lleva el motivo.
503El 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

En esta página