API de FirmaWeb
Firma masiva y firma desatendida desde tu propio sistema (ERP, remuneraciones, RR.HH.).
Introducción
Con la API tu sistema envía a firmar lotes (muchas personas de una vez, cada una con sus documentos) y hace que el firmante de tu empresa (el gerente, el representante legal) firme todo lo pendiente con un botón, sin salir de tu sistema. Todas las peticiones usan JSON y un token de corta duración.
Dirección base
| Versión | URL base | Descripción |
|---|---|---|
| V2 | https://escritorio.firmaweb.app/apifw/v2 | Firma masiva (lotes) y firma desatendida. Es la que describe esta página. |
| V1 | https://escritorio.firmaweb.app/apifw/v1 | La API anterior: sigue funcionando igual, con las mismas credenciales. Se pueden usar las dos a la vez. |
Flujo de integración típico
Antes de empezar (una vez, en la consola)
- Administrar → Clave de API → Generar credenciales. Aparecen el
APIUSERy laAPIKEY. La clave se muestra una sola vez. - Administrar → Firmantes registrados: registra a quienes firmarán por la empresa. Cada uno activa su firma desde su propia consola.
- Si quieres recibir avisos: en la misma página de la clave, Generar secreto de avisos.
Autenticación
Todas las llamadas llevan un token en la cabecera:
Authorization: Bearer <token>
El token se pide con el usuario y la clave de API, unidos por dos puntos y en Base64, en POST /apifw/v1/token (el mismo token sirve para las dos versiones):
Authorization: Bearer base64(APIUSER:APIKEY)
El token dura 15 minutos. Cuando venza (HTTP 401), pide otro.
Petición de ejemplo
curl -X POST https://escritorio.firmaweb.app/apifw/v1/token \
-H "Authorization: Bearer $(printf '%s' 'APIUSER:APIKEY' | base64)"Respuesta
{
"Estado": 1, "Mensaje": "Correcto",
"Token": "eyJhbGciOi...",
"Token_tipo": "Bearer",
"Expira_en": 1791234567
}Firma desatendida Nuevo
El firmante de tu empresa firma todo lo que le toca, de una vez, con un botón en tu propio sistema: sin entrar a FirmaWeb, sin abrir un enlace por documento y sin PIN por correo. Con 200 contratos, es una sola acción.
Junto al botón, tu sistema le pide al firmante el código de 6 dígitos de su aplicación de autenticación (la misma con que entra a la consola) y lo envía en codigo. Un código firma todo lo pendiente: no se pide por documento.
El firmante entra una vez a su consola, en Documentos → Sin firmar → Firmar desde el sistema de tu empresa, acepta la autorización permanente y la confirma con un código. Desde entonces tu sistema llama a /firmar sin código, incluso de forma automática. La retira ahí mismo cuando quiera.
Cómo se arma
- Inscribir la firma (una vez): un administrador registra al firmante en Administrar → Firmantes registrados, y él la activa en su consola con su RUT y un código de su aplicación.
- Ponerlo como firmante: en los lotes, en
firmantesComunes; en los procesos de/apifw/v1, agregándolo al final conagregar_firmante, con el mismo RUT y correo de su registro. - Mostrarle lo pendiente:
GET /firmas_pendientes. - El botón:
POST /firmar.
GET /lotes/{idLote} o GET /firmas_pendientes.Firma desatendida
GET /firmas_pendientes
Lo que le toca firmar al firmante registrado
Devuelve los procesos en que el firmante registrado ya puede firmar: aquellos en que firmaron todos los anteriores. Úsala para mostrarle la lista en tu sistema antes del botón, y para saber si hay que pedirle el código.
Parámetros (en la dirección)
| Campo | Tipo | Req. | Descripción |
|---|---|---|---|
rutFirmante | string | REQ | RUT del firmante registrado de tu cuenta. |
idLote | integer | OPC | Para ver solo lo pendiente de un lote. |
Respuestas
Petición de ejemplo
Respuestas
200 — Correcto
{
"firmante": { "rut": "12345678-5", "nombre": "Ana Pérez Soto", "autorizacionPermanente": false },
"pideCodigo": true, "total": 2, "documentos": 3,
"procesos": [
{ "idProceso": 1000345, "referencia": "trabajador-0041", "idLote": 1000012, "nombre": "Liquidaciones septiembre 2026 · Jorge Díaz", "documentos": 2 },
{ "idProceso": 1000346, "referencia": "trabajador-0042", "idLote": 1000012, "nombre": "Liquidaciones septiembre 2026 · María Soto", "documentos": 1 }
]
}422 — No registrado
{ "error": "firmante_comun_no_registrado", "mensaje": "Ese RUT no es un firmante registrado y activo de la cuenta. Consulta GET /firmantes_registrados." }POST /firmar
Firmar todo lo pendiente, de una vez
Aplica la firma del firmante registrado en todo lo que le toca, en una sola llamada: es el botón de tu sistema. Sirve para los lotes y también para los procesos creados con /apifw/v1, siempre que él figure como firmante con el RUT y el correo de su registro.
Si quedan es mayor que cero (lotes muy grandes), repite la llamada. Repetirla nunca firma ni cobra dos veces.
Cuerpo de la petición (JSON)
| Campo | Tipo | Req. | Descripción |
|---|---|---|---|
rutFirmante | string | REQ | RUT del firmante registrado de tu cuenta. |
codigo | string (6 dígitos) | SEGÚN | El código que muestra en ese momento la aplicación de autenticación del firmante. Cada código sirve una vez y firma todo lo pendiente. No se envía si el firmante dio la autorización permanente. |
idLote | integer | OPC | Para firmar solo lo de un lote. |
procesos | array de integer | OPC | Lista de idProceso, para firmar solo esos. |
Respuestas
Petición de ejemplo
{
"rutFirmante": "12.345.678-5",
"codigo": "492817"
}Respuestas
200 — Firmado
{
"firmante": { "rut": "12345678-5", "nombre": "Ana Pérez Soto" },
"modo": "codigo",
"firmados": [
{ "idProceso": 1000345, "referencia": "trabajador-0041", "idLote": 1000012, "nombre": "Liquidaciones septiembre 2026 · Jorge Díaz", "documentos": 2 }
],
"noFirmados": [],
"quedan": 0
}422 — Falta código
{ "error": "falta_codigo", "mensaje": "Falta `codigo`: el código de 6 dígitos de la aplicación de autenticación del firmante. Solo se omite si el firmante dio la autorización permanente en su consola." }403 — Código inválido
{ "error": "codigo_invalido", "mensaje": "El código no es válido o ya se usó. Pide al firmante uno nuevo de su aplicación." }Lotes (firma masiva)
POST /lotes
Crear un lote (y enviarlo en la misma llamada)
Envía a firmar muchas personas de una vez, cada una con sus documentos. Con "enviar": true el lote sale en esta misma llamada. Todo o nada: si una fila tiene un error, no se crea ni se cobra nada, y la respuesta dice qué corregir en cada fila.
Sin personas ni enviar, el lote queda en borrador para llenarlo por tandas (lotes de más de 30 MB).
Cuerpo de la petición (JSON)
| Campo | Tipo | Req. | Descripción |
|---|---|---|---|
nombre | string ≤80 | REQ | Nombre del lote. Lo ven tus usuarios en la consola. |
referencia | string ≤100 | OPC | Tu identificador del lote. Si repites la llamada con la misma referencia, no se crea ni se cobra otro lote: recibes el que ya existe, con "repetido": true. Úsala siempre. |
firmantesComunes | array de string | OPC | RUT de los firmantes registrados que firman todos los procesos, en el orden en que deben firmar. |
urlNotifica | string (URL) | OPC | Dirección de tu sistema que recibirá los avisos. |
idEmpresa | integer | OPC | Si tu cuenta tiene varias empresas, a cuál corresponde. |
enviar | boolean | OPC | true para enviarlo ahora. Sin él queda en borrador. |
personas[].referencia | string ≤100 | OPC | Tu identificador de la persona (id de trabajador, de contrato…). Vuelve en las respuestas y en los avisos. |
personas[].nombres | string | REQ | Nombres de quien firma. |
personas[].apellidoPaterno | string | REQ | Apellido paterno. apellidoMaterno es opcional. |
personas[].rut | string | REQ | RUT con dígito verificador (se valida). Con o sin puntos. |
personas[].email | string | REQ | Correo donde recibe el enlace y el PIN. |
personas[].documentos[] | array | REQ | De 1 a 10 documentos: nombre y pdf (el PDF en Base64, hasta 10 MB cada uno). |
Respuestas
Petición de ejemplo
{
"nombre": "Liquidaciones septiembre 2026",
"referencia": "liq-2026-09",
"firmantesComunes": ["12.345.678-5"],
"urlNotifica": "https://tu-sistema.cl/firmaweb/avisos",
"enviar": true,
"personas": [
{
"referencia": "trabajador-0041",
"nombres": "Jorge",
"apellidoPaterno": "Díaz",
"apellidoMaterno": "Rojas",
"rut": "11.111.111-1",
"email": "jorge@correo.cl",
"documentos": [
{ "nombre": "Liquidación septiembre.pdf", "pdf": "JVBERi0xLjcK... (Base64)" },
{ "nombre": "Anexo de contrato.pdf", "pdf": "JVBERi0xLjcK... (Base64)" }
]
}
]
}Respuestas
201 — Creado
{
"idLote": 1000012, "nombre": "Liquidaciones septiembre 2026", "referencia": "liq-2026-09",
"estado": "en_firma",
"firmantesComunes": [{ "rut": "12345678-5", "nombre": "Ana Pérez Soto" }],
"resumen": { "personas": 1, "documentos": 2, "enFirma": 1, "faltaPersona": 1, "faltaFirmanteComun": 0, "completados": 0, "anulados": 0 },
"personas": [{
"referencia": "trabajador-0041", "idProceso": 1000345, "estado": "en_firma",
"rut": "11.111.111-1", "nombre": "Jorge Díaz Rojas", "email": "jorge@correo.cl",
"firmantes": [
{ "orden": 1, "rut": "11.111.111-1", "nombre": "Jorge Díaz Rojas", "firmo": false },
{ "orden": 2, "rut": "12345678-5", "nombre": "Ana Pérez Soto", "firmo": false }
],
"documentos": [
{ "idDocumento": 1000801, "nombre": "Liquidación septiembre.pdf", "firmado": false },
{ "idDocumento": 1000802, "nombre": "Anexo de contrato.pdf", "firmado": false }
]
}]
}422 — Filas con errores
{
"error": "filas_con_errores",
"mensaje": "No se creó nada: hay 2 fila(s) por corregir.",
"filas": [
{ "fila": 3, "referencia": "trabajador-0057", "error": "RUT no válido" },
{ "fila": 8, "referencia": "trabajador-0102", "error": "\"Liquidación.pdf\" no es un PDF en Base64" }
]
}402 — Sin saldo
{ "error": "saldo_insuficiente", "mensaje": "No se envió nada: el lote necesita 400 firmas y la cuenta tiene 380.", "necesarias": 400, "disponibles": 380 }POST /lotes/{idLote}/personas
Agregar personas a un borrador
Para lotes grandes: agrega las personas por tandas (por ejemplo, de 20 en 20), las veces que haga falta. Cada tanda es todo o nada: si una persona tiene un error, no se agrega ninguna de esa tanda. Nada se cobra ni se envía todavía.
Cuerpo de la petición (JSON)
| Campo | Tipo | Req. | Descripción |
|---|---|---|---|
personas[].referencia | string ≤100 | OPC | Tu identificador de la persona (id de trabajador, de contrato…). Vuelve en las respuestas y en los avisos. |
personas[].nombres | string | REQ | Nombres de quien firma. |
personas[].apellidoPaterno | string | REQ | Apellido paterno. apellidoMaterno es opcional. |
personas[].rut | string | REQ | RUT con dígito verificador (se valida). Con o sin puntos. |
personas[].email | string | REQ | Correo donde recibe el enlace y el PIN. |
personas[].documentos[] | array | REQ | De 1 a 10 documentos: nombre y pdf (el PDF en Base64, hasta 10 MB cada uno). |
Respuestas
Petición de ejemplo
{
"personas": [
{
"referencia": "trabajador-0042",
"nombres": "María", "apellidoPaterno": "Soto",
"rut": "22.222.222-2", "email": "maria@correo.cl",
"documentos": [{ "nombre": "Liquidación septiembre.pdf", "pdf": "JVBERi0xLjcK... (Base64)" }]
}
]
}Respuestas
200 — Agregadas
{ "idLote": 1000012, "estado": "borrador", "resumen": { "personas": 2, "documentos": 3, "enFirma": 0, "completados": 0, "anulados": 0 }, "personas": [ "..." ] }422 — Filas con errores
{
"error": "filas_con_errores",
"mensaje": "No se agregó nada: hay 1 fila(s) por corregir.",
"filas": [{ "fila": 2, "referencia": "trabajador-0041", "error": "esta persona ya está en el lote" }]
}POST /lotes/{idLote}/enviar
Enviar un borrador
Revisa el saldo, descuenta las firmas y envía los correos a las personas. Si no alcanza el saldo, el lote sigue en borrador: carga firmas y vuelve a enviarlo. No lleva cuerpo.
Respuestas
Petición de ejemplo
Respuestas
200 — Enviado
{ "idLote": 1000012, "estado": "en_firma", "resumen": { "personas": 200, "documentos": 200, "enFirma": 200, "completados": 0, "anulados": 0 }, "personas": [ "..." ] }GET /lotes/{idLote}
Consultar el avance de un lote
El lote al día: quién firmó (firmo), qué documentos están listos (firmado) y el estado de cada persona. En resumen viene la etapa del lote: faltaPersona (procesos que esperan a la persona), faltaFirmanteComun (ya firmó la persona; esperan al firmante común), completados y anulados.
| Estado | Significa |
|---|---|
borrador | Todavía no se envía. |
en_firma | Enviado; faltan firmas. |
completado (persona) | Firmaron todos. Sus documentos ya se pueden descargar. |
anulado (persona) | Pasaron 8 días sin completarse, o alguien lo anuló en la consola. Las firmas que no se usaron vuelven al saldo. |
terminado (lote) | Ya no queda ninguna persona en firma. |
Respuestas
Petición de ejemplo
Respuestas
200 — Correcto
{
"idLote": 1000012, "nombre": "Liquidaciones septiembre 2026", "referencia": "liq-2026-09",
"estado": "en_firma",
"firmantesComunes": [{ "rut": "12345678-5", "nombre": "Ana Pérez Soto" }],
"resumen": { "personas": 1, "documentos": 2, "enFirma": 1, "faltaPersona": 1, "faltaFirmanteComun": 0, "completados": 0, "anulados": 0 },
"personas": [{
"referencia": "trabajador-0041", "idProceso": 1000345, "estado": "en_firma",
"rut": "11.111.111-1", "nombre": "Jorge Díaz Rojas", "email": "jorge@correo.cl",
"firmantes": [
{ "orden": 1, "rut": "11.111.111-1", "nombre": "Jorge Díaz Rojas", "firmo": false },
{ "orden": 2, "rut": "12345678-5", "nombre": "Ana Pérez Soto", "firmo": false }
],
"documentos": [
{ "idDocumento": 1000801, "nombre": "Liquidación septiembre.pdf", "firmado": false },
{ "idDocumento": 1000802, "nombre": "Anexo de contrato.pdf", "firmado": false }
]
}]
}DELETE /lotes/{idLote}
Descartar un borrador
Borra un lote que todavía no se envía, con sus documentos. Un lote enviado no se borra.
Respuestas
Petición de ejemplo
Respuestas
409 — Ya enviado
{ "error": "lote_ya_enviado", "mensaje": "El lote ya se envió: no se puede modificar." }Documentos
GET /documentos/{idDocumento}
Descargar un documento firmado
Devuelve el PDF firmado tal cual (Content-Type: application/pdf), no en Base64. Solo cuando ya firmaron todos. Sirve para cualquier documento de tu cuenta, también los de /apifw/v1.
Respuestas
Petición de ejemplo
Respuestas
200 — PDF
Content-Type: application/pdf
Content-Disposition: attachment; filename*=UTF-8''Liquidaci%C3%B3n%20septiembre.pdf
%PDF-1.7 ...409 — Sin firmar
{ "error": "documento_sin_firmar", "mensaje": "Al documento todavía le faltan firmas." }Cuenta
GET /firmantes_registrados
Firmantes registrados de la cuenta
Quiénes pueden ir como firmantes comunes de un lote y firmar con /firmar: los firmantes registrados y activos de tu cuenta.
Respuestas
Petición de ejemplo
Respuestas
200 — Correcto
{ "firmantes": [{ "rut": "12345678-5", "nombre": "Ana Pérez Soto", "email": "ana@tu-empresa.cl" }] }GET /saldo
Firmas disponibles
El saldo de firmas de la cuenta. fes es firma electrónica simple.
Respuestas
Petición de ejemplo
Respuestas
200 — Correcto
{ "fes": 480, "fao": 0 }Avisos a tu sistema
POST tu urlNotifica
FirmaWeb le avisa a tu sistema
Si el lote lleva urlNotifica, FirmaWeb hace un POST con JSON a esa dirección cada vez que pasa algo. Todos los avisos traen evento, fecha, idLote y referenciaLote.
Eventos
| Evento | Cuándo | Datos |
|---|---|---|
firma | Alguien firmó un documento (cada firmante, cada documento). | idProceso, referencia, idDocumento, documento, firmante |
proceso_completado | Firmaron todos los de una persona: sus documentos ya se pueden descargar. | idProceso, referencia |
proceso_anulado | El proceso de una persona se anuló. | idProceso, referencia |
lote_completado | Ya no queda ninguna persona en firma. | completados, anulados |
Qué debe hacer tu sistema
X-FirmaWeb-Entrega: úsalo para no procesarlo dos veces. La cabecera X-FirmaWeb-Firma: t=…,v1=… trae el HMAC-SHA256, en hexadecimal, del texto t + punto + cuerpo tal como llegó, calculado con tu secreto de avisos (se genera en Administrar → Clave de API). Si no coincide, descarta el aviso.Aviso de ejemplo
{
"evento": "proceso_completado",
"fecha": "2026-10-05T14:03:22.118Z",
"idLote": 1000012, "referenciaLote": "liq-2026-09",
"idProceso": 1000345, "referencia": "trabajador-0041"
}Comprobar que el aviso es nuestro
// Node.js: cuerpoCrudo es el cuerpo recibido, sin modificar
const crypto = require('node:crypto');
function avisoValido(cabecera, cuerpoCrudo, secreto) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(cabecera || '');
if (!m) return false;
// más de 5 minutos de diferencia: se descarta
if (Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
const esperado = crypto.createHmac('sha256', secreto)
.update(m[1] + '.' + cuerpoCrudo).digest('hex');
return crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(m[2]));
}Errores
Un error responde con su código HTTP y { "error": "…", "mensaje": "…" }. error es fijo, para tu programa; mensaje es para una persona.
| HTTP | error | Qué hacer |
|---|---|---|
| 400 | solicitud_invalida | El cuerpo no es un JSON válido. |
| 401 | no_autorizado | Falta el token, no es válido o venció: pide uno nuevo. |
| 402 | saldo_insuficiente | No alcanzan las firmas. Trae necesarias y disponibles. |
| 403 | codigo_invalido | El código del firmante no es válido o ya se usó: pídele uno nuevo. |
| 404 | lote_no_existe, documento_no_existe | Revisa el número. |
| 409 | lote_ya_enviado | El lote ya se envió: no admite cambios. |
| 409 | documento_sin_firmar | Todavía faltan firmas. |
| 413 | solicitud_muy_grande | Más de 30 MB: crea el lote en borrador y agrega las personas por tandas. |
| 422 | filas_con_errores | Corrige las filas de filas y reenvía. |
| 422 | firmante_comun_no_registrado | Ese RUT no es un firmante registrado y activo de tu cuenta. |
| 422 | falta_codigo | Envía el código del firmante, o que él dé la autorización permanente. |
| 422 | falta_secreto_avisos | Genera el secreto de avisos en la consola. |
| 422 | falta_nombre, sin_personas, lote_vacio, demasiadas_personas, empresa_no_existe, url_notifica_invalida | Lo explica el mensaje. |
| 429 | demasiadas_solicitudes, demasiados_codigos | Espera: más de 120 solicitudes por minuto, o más de 5 intentos de código en 15 minutos. |
| 500 | error_interno | Reintenta. Con referencia, el lote no se duplica. |
Límites y cobro
- Hasta 200 personas por lote y 10 documentos por persona; cada PDF, hasta 10 MB.
- Hasta 30 MB por solicitud (unos 20 MB de PDF, porque el Base64 ocupa un tercio más) y 120 solicitudes por minuto.
- Una persona aparece una sola vez en el lote (junta todos sus documentos en ella) y no puede ser a la vez firmante común.
- Cobro: una firma por cada persona que firma, sin importar cuántos documentos tenga. Un lote de 200 personas con un firmante común descuenta 400 firmas, tenga cada persona uno o varios documentos. Se descuentan al enviar; si un proceso se anula, vuelven al saldo las de quienes no alcanzaron a firmar.
- Un proceso que no se completa en 8 días se anula solo.
¿Dudas con la integración?
Escríbenos a ayuda@mail.firmaweb.app con la llamada que hiciste y la respuesta que recibiste (sin la clave ni el token).
Todos los derechos reservados | 2026 © FirmaWeb