Documentación de la API

Integre Robodam Scan en sus procesos: envíe el PDF de una factura y obtenga datos estructurados (JSON) y, si es necesario, un archivo de importación contable (Rivilė .eip, Centas XML o Finvalda XML). Funciona con «Power Automate», cron, Postman y scripts.

abajo {BASE} — la dirección de la API https://scan-api.robodam.com (NE la dirección de la aplicación/inicio de sesión es scan.robodam.com — solo sirve la interfaz del navegador). Para monitorización: GET {BASE}/health (una ruta raíz, no bajo /api/v1) devuelve {"status":"healthy"}.

1. Autentifikacija

Cada solicitud debe incluir su clave Bearer personal en la cabecera. Para la automatización (Power Automate, cron, scripts) cree una clave API permanente: Configuración → Cuenta → Claves API — raktas (rbs_…) se muestra solo una vez y sigue siendo válido hasta que lo revoque. Para pruebas manuales rápidas también sirve el «Clave de prueba» de arriba, sin embargo, es temporal (clave de sesión) — no la utilice para flujos automatizados.

Authorization: Bearer <su-clave>

2. Extracción de facturas (PDF → JSON)

Extracción síncrona: envía el PDF y obtiene los datos en la misma respuesta (sin solicitudes adicionales ni polling).

POST{BASE}/api/v1/invoices/extract-direct

Solicitud

multipart/form-data, el documento se pasa en el campo file. Formatos aceptados: PDF y fotos (JPG / PNG / HEIC) — las fotos se convierten a PDF automáticamente en el servidor. Parámetros opcionales:

ParametrasNumatytaValor
save_to_profiletrueSi guardar la extracción en su historial.
allow_duplicatefalseJei false — al reenviar el mismo archivo se devuelve el resultado anterior de forma gratuita (véase la sección 5).
include_export—rivile, centas arba finvalda — devolver también el archivo de contabilidad (véase la sección 6).
include_expensefalseSi es true, la respuesta incluye también el bloque expense: para cada línea, el código de gasto y la clase de IVA sugeridos según las reglas y la memoria contable de su empresa. null significa que la asignación de gastos está desactivada para la empresa de este documento (en la cuenta de una asesoría contable, también cuando el documento aún no está asignado a ninguna empresa); en un documento de venta las líneas no llevan códigos.
split_documentsfalseCon true, un archivo que contiene varios documentos (un paquete de tarjetas de combustible: una factura con IVA por país) se devuelve como documentos independientes en documents[]; cada uno se cobra como una factura. Sin este parámetro, un paquete nunca se devuelve como documentos independientes. No afecta a un archivo que es un solo documento. Solo funciona en las cuentas en las que Robodam ha activado la lectura de extractos de tarjetas de combustible (se activa por cuenta); en las demás cuentas el parámetro se ignora.

Límites: un PDF o una foto JPG / PNG / HEIC; archivo de hasta 10 MB; hasta 20 páginas (/extract-direct tiene su propio límite, pero por defecto es igual al de /upload: 20 cada uno; un administrador puede aumentar el límite de /upload para un cliente concreto, hasta 50, y el de /extract-direct no cambia con ello; en las cuentas en las que Robodam ha activado la lectura de extractos de tarjetas de combustible, un extracto cuyos totales impresos cuadran al céntimo queda exento del límite de páginas, hasta 200 páginas; para las demás cuentas el límite no cambia); 30 solicitudes/min. Un PDF con varias facturas se trata aquí como un único documento (la división solo se produce mediante /upload).

La solicitud completa (el cuerpo multipart): hasta 20 MB; lo que supere ese tamaño lo rechaza la pasarela antes de llegar a la API (413 sin cuerpo JSON). Un archivo: hasta 10 MB, salvo que un administrador haya fijado otro límite para su cuenta (como máximo 18 MB). En /batch-upload el límite se aplica a la suma de todos los archivos.

Ejemplo (cURL)

curl -X POST "{BASE}/api/v1/invoices/extract-direct" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@saskaita.pdf"

Respuesta (200 OK)

{
  "success": true,
  "filename": "saskaita.pdf",
  "confidence_score": 100,        // 0–100, confianza de la IA
  "auto_validated": true,         // true kai confidence_score >= 85
  "validation_warnings": [],      // texto para una persona
  "validation_issues": [],        // lo mismo para una máquina — con código (véase más abajo)
  "risk_flags": [],               // qué vale la pena comprobar antes de contabilizar
  "document_type": "invoice",     // invoice / credit_note / receipt / unknown / ...
  "from_cache": false,
  "duplicate_of": null,
  "invoice_id": "3f2b9c1e-...",   // ID de la factura guardada en Scan; null cuando save_to_profile=false
  "extracted_data": { ... },      // campos de la factura (véase la sección 3)
  "export": null                  // se rellena solo con include_export
}

3. Campos extraídos (extracted_data)

Todos los campos son opcionales — si un campo no está en la factura, es null u omitido.

{
  "invoice_number": "SF Nr. 70",
  "invoice_date": "2026-05-29",     // YYYY-MM-DD
  "due_date": "2026-06-12",
  "currency": "EUR",
  "subtotal": 1100.0,               // suma be PVM
  "tax_amount": 231.0,              // importe del IVA (ausente cuando 0% / sin IVA)
  "tax_rate": 21,                   // dokumento PVM %, sveikas sk.
  "total_amount": 1331.0,           // suma su PVM
  "bank_account": "LT74...",
  "document_type": "invoice",
  "vendor": {                       // proveedor (vendedor)
    "name": "MB Decentrum",
    "company_code": "306265414",    // código de empresa
    "tax_id": "306265414",          // código de IVA (LT… si es sujeto pasivo de IVA)
    "address": "Ceikiniu g. 3, Lietuva"
  },
  "customer": {                     // comprador
    "name": "UAB Robodam",
    "company_code": "...",
    "tax_id": "LT100017242115",
    "address": "Kaunas, Vytauto pr. 27"
  },
  "line_items": [
    {
      "description": "Programavimo paslaugos",
      "quantity": 1.0,
      "unit": "vnt",
      "unit_price": 1100.0,         // vieneto kaina be PVM
      "total": 1100.0,              // importe de la línea sin IVA
      "tax_rate": 21,               // % de IVA de la línea (si falta — el tax_rate del documento)
      "item_number": "..."          // código del artículo del proveedor, si existe
    }
  ],
  "notes": "..."
}

La respuesta también puede contener _validation bei _recovery blokai — tai vidiniai diagnostikos duomenys, juos galite ignoruoti.

4. Señales de calidad

confidence_score pasako, kiek estamos. Estos dos campos indican kas negerai — y están pensados para un programa, no para una persona. Ambos son siempre en la respuesta; para un documento limpio son listas vacías.

validation_issues — qué comprobamos nosotros

{
  "code": "total_mismatch",       // estable — seguro para ramificar según él
  "field": "total_amount",
  "message": "Total amount (100.0) does not match calculated total (121.00)",
  "severity": "warning",          // warning | error
  "kind": "semantic"              // format | semantic | extraction
}

kind skiria nuestro comprobación (format — formato, semantic — importes y fechas) del propio comentario del modelo sobre su propia lectura (extraction). Estos últimos son texto libre y no llevan código — inventarlo supondría prometer un contrato que no cumpliríamos. Los códigos más comunes: total_mismatch, line_sum_mismatch, tax_amount_mismatch,date_format, due_before_invoice, iban_checksum.El textual validation_warnings se mantiene — está pensado para mostrarse a una persona.

risk_flags — qué vale la pena comprobar

Tai faktai, o ne verdiktai. La decisión la toma usted, porque usted conoce la dirección:

  • own_company_in_vendor_slot — la empresa de su cliente está en el del vendedor lauke. En una factura de compra esto significa que las partes están intercambiadas; en una factura de venta esto es normal.
  • same_party_both_sides — la misma empresa en ambos lados. No existe ningún caso en que esto sea correcto.
  • duplicate_invoice_number — el mismo proveedor ya ha enviado una factura con este mismo número. El campo duplicate_of_invoice_number indica el id, el importe y la fecha de la factura anterior — la diferencia de importes es precisamente lo que le dice si el proveedor reescribió el documento o simplemente lo reenvió. La comprobación de duplicados a nivel de archivo no puede ver esto: un documento reescrito son bytes distintos.
  • credit_note_signs_applied — el documento fue reconocido como factura rectificativa, por lo que total_amount,subtotal y los importes de las líneas forzadas a negativo. Los números en la respuesta pueden no coincidir con los signos impresos en el PDF.

La lista de códigos sigue creciendo. Un código desconocido debe ignórelasen lugar de tratarse como un error — así su integración no se romperá cuando añadamos uno nuevo.

5. Reenvío

Si reenvía el mismo archivo (los mismos bytes, la misma cuenta), entonces allow_duplicate yra false (por defecto) — devuelve la extracción anterior, de forma gratuita(from_cache: true, duplicate_of apunta al ID anterior; la IA no se vuelve a ejecutar). De este modo, un flujo de «Power Automate» que lee la misma carpeta cada día no se factura de nuevo. Para forzar una nueva extracción, envíe allow_duplicate=true.

Reintentos con su propio ID de documento

Envíe su propio ID de documento en la cabecera Idempotency-Key o en el campo de formulario external_id (1–128 caracteres), en /upload, /extract-direct y /batch-upload. Se guarda como external_id de la factura, se devuelve en la respuesta y se puede filtrar (GET /invoices?external_id=…). Si repite la solicitud con el mismo ID, se devuelve el PRIMER resultado —aunque el archivo se haya escaneado de nuevo y sus bytes sean distintos— y no se crea ni se cobra una segunda factura.

curl -X POST "{BASE}/api/v1/invoices/upload" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: DMS-2026-000123" \
  -F "file=@saskaita.pdf"

En /batch-upload, envíe un campo external_id por archivo, en el mismo orden; una sola cabecera Idempotency-Key para varios archivos se rechaza (422).

Atención: con save_to_profile=false el resultado no se guarda, así que una solicitud repetida es una nueva lectura y un nuevo cargo (un ID con este parámetro se rechaza). Las automatizaciones que reintentan tras un error o un tiempo de espera deben mantener el valor predeterminado save_to_profile=true y enviar su propio ID.

6. Exportación contable (Rivilė / Centas / Finvalda)

Puede obtener el archivo de dos formas. Ambas devuelven el archivo base64 en formato JSON dentro (content_base64) — decodifíquelo y guárdelo.

A. Junto con la extracción (un archivo)

Añada el parámetro a la solicitud de la sección 2 include_export (rivile, centas arba finvalda) — atsakyme atsiras export campo con el archivo de esa factura:

POST{BASE}/api/v1/invoices/extract-direct?include_export=rivile
POST{BASE}/api/v1/invoices/extract-direct?include_export=centas

export el campo tiene la misma estructura que la respuesta por lotes (véase más abajo).

B. Para facturas seleccionadas / filtradas (lote)

POST{BASE}/api/v1/invoices/export/{format}

{format} = rivile, centas o finvalda. El cuerpo de la solicitud son o bien ids específicos, o bien filtros (como en la lista de facturas, hasta 1000):

{ "invoice_ids": ["<id1>", "<id2>"] }
// arba
{ "start_date": "2026-05-01", "end_date": "2026-05-31" }

Respuesta — Rivilė (.zip con archivos .eip):

{
  "format": "rivile",
  "filename": "Rivile_eksportas-20260612-061255.zip",
  "content_type": "application/zip",
  "content_base64": "UEsDBB...",   // base64 → bytes → guardar como .xml
  "problems": [],                  // avisos para el contable (véase más abajo)
  "skipped_count": 0,
  "purchase_count": 1,
  "sale_count": 0
}

Respuesta — Centas (.xml, o .zip cuando hay tanto compras como ventas):)

{
  "format": "centas",
  "filename": "Centas_eksportas-20260612-061255.xml",
  "content_type": "application/xml",   // o application/zip
  "content_base64": "PD94bWw...",      // base64 → bytes → guardar como .xml
  "problems": [],
  "skipped_count": 0,
  "purchase_count": 1,
  "sale_count": 0
}
FormatoArchivoContenido
rivile.zipKlientai / Prekes / Pirkimai / Pardavimai .eip (ISO-8859-13)
centas.xml o .zipPirkimai.xml / Pardavimai.xml (UTF-8)
finvalda.xml o .zipPirkimai.xml / Pardavimai.xml (UTF-8)

problems — advertencias dirigidas a una persona (facturas omitidas, una clase de IVA al 0%/inversión del sujeto pasivo que necesita confirmación) — muéstrelas antes de la importación.

7. Configuración de la empresa

Algunos valores no están en la factura — su código de empresa (para distinguir compras de ventas), los códigos de clase de IVA. Guárdelos una vez y cada exportación los usará:

GET{BASE}/api/v1/export-config?format={format}
PUT{BASE}/api/v1/export-config?format={format}

{format} = rivile, centas o finvalda. Cuerpo del PUT (envíe solo lo que desee establecer; el resto se mantiene con sus valores predeterminados). Los campos difieren según el formato:

Rivilė:

{ "config": {
    "own_company_code": "304827491",
    "own_vat_code": "LT100001738313",
    "vat_class_map": { "21": "PVM1", "0": "PVM5" },
    "partner_group": "PT001",
    "product_group": "PR001",
    "op_tip_purchase": 1,
    "op_tip_sale": 51
} }

Centas:

{ "config": {
    "own_company_code": "304827491",
    "own_vat_code": "LT100001738313",
    "vat_class_map": { "21": "PVM1", "0": "PVM5" },
    "account_purchase": "SANAUDOS",
    "account_sale": "PAJAMOS"
} }

Finvalda:

{ "config": {
    "own_company_code": "304827491",
    "own_vat_code": "LT100001738313",
    "op_type_purchase": "1",
    "op_type_sale": "51",
    "journal_purchase": "PIRK",
    "journal_sale": "PARD",
    "import_param": "IMP1",
    "default_client_code": "KL001",
    "account_purchase": "6001",
    "account_sale": "5001"
} }

Finvalda PVM klasės lauko neturi — klasę parenka imp_param, tad vat_class_map šiam formatui netaikomas.

Utilice snake_case claves (una clave desconocida devuelve 422). Importante: la clase de IVA del 0% / inversión del sujeto pasivo ("0") por defecto es PVM5 — confirme el código correcto con su contable (un código incorrecto pero válido distorsiona silenciosamente la contabilidad del IVA). Establezca abu — own_company_code ir own_vat_code: la compra/venta se reconoce cuando coincide cualquiera de los dos (la IA a veces no logra leer el código de empresa, especialmente en facturas extranjeras).

8. Errores y límites

CódigoValor
401Falta la clave o no es válida.
403La clave es válida, pero la acción no está permitida: no es un error de autenticación. Lo más habitual es uno de los errores de facturación (payment_blocked, trial_exhausted, insufficient_credits): no repita la solicitud hasta que una persona pague la factura, firme el contrato o recargue el crédito. detail.error indica el motivo exacto (véase la sección 10).
400 / 413 / 422Archivo no válido (no es PDF / JPG / PNG / HEIC, > 10 MB, supera el límite de páginas) o parámetros no válidos.
404No se encontraron facturas para la exportación con los ids/filtro indicados.
422 (exportación)Todas las facturas seleccionadas fueron omitidas (los motivos están en el texto del error) — no se creó ningún archivo. También: formato desconocido o un config.
429Límite de frecuencia superado — espere los segundos que indica Retry-After y vuelva a intentarlo. Los límites se cuentan por clave y varían según la ruta (bajo /api/v1/invoices): /upload — 120/min, /extract-direct y /batch-upload — 30/min, /export/{format} y /{id}/retry — 10/min.

9. La vía masiva: subir → consultar → obtener → exportar

Cuando hay muchos documentos, o son largos, use la vía asíncrona: suba ahora y recoja los resultados cuando estén listos.

  1. Subir: POST /invoices/upload (un archivo) o /batch-upload (hasta 100 archivos). /batch-upload siempre responde 201 con un resultado por archivo, incluso si se rechazaron todos: compruebe cada results[].status.
  2. Consultar: GET /invoices con sort_by=updated_at&sort_order=asc&page_size=100. Mientras el next_cursor de la respuesta no sea null, envíelo de vuelta como cursor.
  3. Obtener: GET /invoices/{id} devuelve todos los campos leídos (la lista los abrevia).
  4. Exportar: POST /invoices/export/{format} con invoice_ids (§6).
GET {BASE}/api/v1/invoices?sort_by=updated_at&sort_order=asc&page_size=100&updated_after=2026-09-27T08:00:00Z
→ { "items": [ … ], "total": 240, "next_cursor": "eyJ1Ijoi…" }
GET {BASE}/api/v1/invoices?sort_by=updated_at&sort_order=asc&page_size=100&updated_after=2026-09-27T08:00:00Z&cursor=eyJ1Ijoi…
→ { "items": [ … ], "next_cursor": null }

Receta: sincronización incremental segura

Guarde el updated_at de la última fila recibida. Empiece la siguiente sincronización con updated_after = ese valor menos 2 minutos y recorra next_cursor. El solapamiento es necesario: un registro confirmado más tarde puede llevar una marca anterior. Puede recibir la misma factura dos veces: reconózcala por su id. La paginación por page puede saltarse filas mientras cambian los datos: no la use para sincronizar.

Ciclo de vida de los estados (status)

uploaded → processing → un estado final. Un PDF de varias páginas pasa primero por detection_pending; si contiene varias facturas, la fila padre pasa a split y cada factura se procesa como una fila propia.

EstadoValor¿Final?
uploadedSubida, en espera en la cola.no
detection_pendingPDF de varias páginas: se busca dónde termina una factura y empieza la siguiente.no
processingEn lectura.no
processedLeída: el resultado está listo.sí
unprocessedIlegible: faltan campos obligatorios. No se cobra.sí
failedNo se pudo leer. No se cobra; se puede reintentar con POST /invoices/{id}/retry.sí
rejectedRechazada en la revisión.sí
splitLa fila padre de un PDF con varias facturas: no se lee; las facturas son filas separadas.sí
skipped_trial_exhaustedOmitida: se agotó el cupo de prueba. No se cobra.sí

Campos en la lista y en el detalle

En la lista, extracted_data se abrevia a cinco campos —vendor.name, total_amount, subtotal, tax_amount, currency— con los mismos nombres que en el detalle (§3). Las líneas, el comprador, las fechas y el número solo están en GET /invoices/{id}. Ambos incluyen updated_at y external_id. Los nombres de los campos no cambian: solo se añaden nuevos.

10. El contrato: OpenAPI, versión y códigos de error

El contrato legible por máquina: openapi-public.json (OpenAPI 3.1, sin las rutas de administración). Cada respuesta lleva la cabecera X-Contract-Version (una fecha): dentro de una versión los cambios solo añaden; una eliminación o un cambio de significado implica una fecha nueva. Ignore los campos y códigos que no conozca.

Cada respuesta lleva X-Request-ID, y cada cuerpo de error también request_id: indíquelo cuando nos escriba. El código de error es detail.error (decida según él); el texto para una persona es detail.message.

HTTP/1.1 403 Forbidden
X-Request-ID: 5f0c…
X-Contract-Version: 2026-09-30

{ "detail": { "error": "trial_exhausted", "message": "…", "retryable": false },
  "request_id": "5f0c…" }

Los límites de frecuencia se cuentan por clave de API (o por usuario con sesión iniciada), no por dirección IP; solo las rutas de inicio de sesión y registro se limitan por IP. Las respuestas limitadas llevan X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset; una respuesta 429, también Retry-After (segundos).

Códigos de error

CódigoValor¿Reintentar?
invalid_tokenLa sesión ya no es válida — inicie sesión de nuevo.no: debe actuar una persona
user_inactiveLa cuenta está inactiva. Póngase en contacto con la administración.no: debe actuar una persona
api_key_requires_sessionSolo se puede crear una clave de API nueva con la sesión iniciada: Configuración → Cuenta → Claves API.no: debe actuar una persona
api_key_limit_reachedSe alcanzó el límite de claves API activas.no: debe actuar una persona
payment_blockedSu cuenta está bloqueada por facturas impagadas. Póngase en contacto con la administración.no: debe actuar una persona
trial_exhaustedEl período de prueba gratuito se ha agotado.no: debe actuar una persona
insufficient_creditsSu límite de crédito se ha agotado. Póngase en contacto con la administración para aumentarlo.no: debe actuar una persona
pdf_onlySolo se pueden subir archivos en formato PDFno: debe actuar una persona
invalid_content_typeTipo de archivo no válido — solo se permite PDF.no: debe actuar una persona
file_too_largeEl archivo es demasiado grande.no: debe actuar una persona
invalid_pdf_contentPDF no válido: el contenido no coincide con el formato PDF.no: debe actuar una persona
invalid_pdf_mimePDF no válido: se detectó un tipo de archivo diferente.no: debe actuar una persona
pdf_too_complexEl PDF no se puede procesar de forma segura: las dimensiones de la página son demasiado grandes o el número de páginas no es válido.no: debe actuar una persona
too_many_pagesEl PDF tiene demasiadas páginas ({page_count}). El número máximo permitido es {max_pages}.no: debe actuar una persona
too_many_pages_for_syncEl documento es demasiado largo para la extracción sincrónica ({page_count} pág.; límite {max_pages}). Utilice la vía asíncrona.no: debe actuar una persona
too_many_filesDemasiados archivos.no: debe actuar una persona
duplicate_invoiceEsta factura ya ha sido subida.no: debe actuar una persona
file_emptyEl archivo está vacío.no: debe actuar una persona
image_unreadableNo se pudo leer la foto — suba otra.no: debe actuar una persona
image_too_largeLa foto es demasiado grande para procesarla de forma segura.no: debe actuar una persona
heic_unsupportedEl formato HEIC no es compatible por el momento — suba un JPG o PNG.no: debe actuar una persona
extraction_failedNo se pudieron extraer los datos del PDF.no: debe actuar una persona
extraction_too_largeEl documento es demasiado largo para leerlo de una sola vez: reintentar no servirá. Lea el PDF localmente o envíelo en partes más pequeñas. No se cobra.no: debe actuar una persona
invalid_external_idID de documento no válido: de 1 a 128 caracteres visibles, uno por archivo.no: debe actuar una persona
external_id_conflictEste ID de documento ya pertenece a una factura cuyo resultado aún no está listo: espere o use otro ID.no: debe actuar una persona
invalid_filter_valueValor de filtro no válido: elija uno de la lista.no: debe actuar una persona
invalid_cursorLa posición de la lista ya no es válida: vuelva a empezar desde la primera página.no: debe actuar una persona
unknown_companyLa empresa «{company_code}» no está registrada: elija una empresa de la lista.no: debe actuar una persona
invoice_not_foundFactura no encontrada.no: debe actuar una persona
pdf_not_foundNo se encontró el PDF en el servidor — puede que haya sido eliminado.no: debe actuar una persona
export_no_invoicesNo se encontraron facturas según los criterios de exportación.no: debe actuar una persona
export_all_skippedSe omitieron todas las facturas seleccionadas — no se creó ningún archivo.no: debe actuar una persona
invalid_export_configConfiguración de exportación no válida.no: debe actuar una persona
key_unsendableClave no guardada: contiene caracteres que no se pueden enviar — copie la clave de nuevo, sin comillas ni espacios.no: debe actuar una persona
invoice_save_failedNo se pudo guardar la factura — inténtelo de nuevo.sí, tras una pausa
internal_errorError del servidor: vuelva a intentarlo en un minuto. Si se repite, escríbanos.sí, tras una pausa
service_unavailableEl servicio no está disponible temporalmente. Inténtelo de nuevo dentro de un minuto.sí, tras una pausa
extraction_internal_errorSe produjo un error interno de extracción.sí, tras una pausa

Trate un código desconocido como «no reintentar» por ahora: vuelva a intentarlo en una ejecución posterior, pero nunca lo considere definitivo. Una respuesta sin retryable no significa false. Los errores de facturación (payment_blocked, trial_exhausted, insufficient_credits) siempre son 403 con retryable: false: detenga toda la ejecución y avise a una persona, no omita el archivo.

¿Preguntas sobre la integración? Póngase en contacto — kontaktai.