De sandbox a producción
El camino completo de una empresa real — origen y canal, mandato, certificado o clave centralizada, los pasos que hace una persona en el SII, la aprobación del mes, la puesta en vivo y los folios.
El quickstart termina con una boleta emitida en el sandbox. Esta página es lo que falta entre eso y una factura con valor tributario: una máquina de estados que puedes recorrer entera por API, con tres o cuatro momentos en que la ley chilena exige que actúe una persona y nosotros te damos la URL para dársela.
Todo lo de acá vive detrás de un solo recurso de lectura:
curl -sS https://api.facturia.cl/v1/empresas/emp_01K2R7Q4XW9M3B8ZC5YHTVJD6N/onboarding \
-H "Authorization: Bearer $FACTURIA_KEY"La forma exacta del cuerpo está en Onboarding, y la del
handoff a una persona en PendingAction.
Los dos carriles
Una empresa emite por uno de dos canales, y el canal decide qué lista de pasos recorre. No son dos productos: son el mismo endpoint, los mismos campos y la misma respuesta.
| mercado | SFG (Sistema de Facturación Gratuito) | |
|---|---|---|
| Quién emite | Nuestro motor propio, certificado ante el SII | El portal gratuito del SII, operado por nosotros |
| Certificación | Sí: set de pruebas, simulación, intercambio, muestras impresas | Ninguna — el SII ya habilitó a la empresa al inscribirla |
| Ambiente | certificacion hasta la resolución, después produccion | Siempre produccion |
| Folios | Timbraje propio (CAF) | Los asigna el SII en cada emisión |
| Pasos | 11 (10 sin boletas) | 7 |
| Copia cedible | Sí (tipos 33 y 34) | No |
emission_channel es cómo emite hoy la empresa. certification_track es para qué se está
certificando, y puede ser otra cosa o null: una empresa puede emitir en SFG mientras certifica
para mercado, y una empresa contenta con el portal gratuito para siempre tiene la pista en null.
Los dos campos vienen en el objeto Empresa y son de solo
lectura salvo el canal, que declaras tú.
Dibuja los pasos que te entregan
onboarding.steps[] trae una lista o la otra según el carril activo. No supongas el largo ni las
llaves: recorre lo que venga, lee actor de cada paso y muestra help. Un paso con una llave que
tu código no conoce se dibuja igual; uno que desapareció no era exigible.
Los pasos, con su actor
actor dice de quién es el paso: customer es tuyo o de tu cliente, facturia lo corremos
nosotros, sii es esperar. Tres pasos del carril de mercado son nuestros y aun así no avanzan sin
una persona —sii_postulacion, cumplimiento y habilitacion: los presentamos con el certificado
en custodia, después de una aprobación—, y lo que lo dice es pending_action.approval, no actor.
execution es el otro campo del cable (Onboarding):
automatic es un trabajo en cola y operator es una persona nuestra frente a un terminal, que es la
diferencia entre «queda durante la noche» y «queda cuando alguien se siente a hacerlo».
No es un dato fijo del producto: execution vuelve a operator en el API si la cerca de ese paso
se cierra en producción, y con ella sube el eta_business_days del paso y la fecha de
estimated_ready_at. La tabla de abajo muestra el estado de producción —todas las cercas abiertas,
automatic en las once filas—, así que léelo del cable y no de acá. Mientras un paso diga
operator, su eta_business_days no baja de 2 días hábiles: una sesión de trabajo se agenda, no se
encola.
Carril mercado — once pasos, o diez si no declaraste boletas:
| # | key | actor | execution | Qué es |
|---|---|---|---|---|
| 1 | mandato | customer | automatic | El representante legal nos autoriza a actuar ante el SII. Revocable |
| 2 | certificate | customer | automatic | El .pfx del representante legal, a la bóveda |
| 3 | sii_postulacion | facturia | automatic | La presentamos nosotros con el certificado en custodia; el titular autoriza el momento |
| 4 | set_de_pruebas | facturia | automatic | Emitimos el set que el SII exige por cada tipo |
| 5 | simulacion | facturia | automatic | La simulación previa a la autorización |
| 6 | intercambio | facturia | automatic | Probamos que la empresa recibe y responde documentos de terceros |
| 7 | muestras_impresas | facturia | automatic | Enviamos las muestras impresas de cada tipo |
| 8 | cumplimiento | facturia | automatic | La presentamos nosotros con el certificado en custodia; el representante legal aprueba el acto y, si no estaba fijado antes, el mes |
| 9 | resolucion | sii | automatic | El SII dicta la resolución. Acá solo se espera |
| 10 | habilitacion | facturia | automatic | Solo si declaraste boletas: las habilitamos nosotros de principio a fin con el certificado en custodia; el representante legal decide cuándo empezar y aprueba la declaración final |
| 11 | folios | facturia | automatic | Pedimos el primer timbraje (CAF) de cada tipo |
Carril SFG — siete pasos, sin certificación, más dos si declaras boletas:
| # | key | actor | execution | Qué es |
|---|---|---|---|---|
| 1 | mandato | customer | automatic | El mismo mandato, el mismo registro, la misma revocación |
| 2 | credential | customer | automatic | Certificado en custodia o delegación: nuestro RUT como usuario autorizado |
| 3 | clave_centralizado | customer | automatic | La clave del Sistema de Facturación Centralizado, por POST /v1/empresas/{empresa_id}/sfg_credential |
| 4 | usuario_autorizado | customer | automatic | Comprobamos que nuestro certificado puede actuar por la empresa en el Portal MIPYME |
| 5 | emisor_verificado | facturia | automatic | Confirmamos que la empresa está inscrita y autorizada a emitir |
| 6 | puesta_en_vivo | facturia | automatic | Nuestro: el canario y la habilitación de emisión real por empresa |
| 7 | emitting | facturia | automatic | Listo: emitimos y recibimos por su cuenta del sistema gratuito |
| 8 | habilitacion | facturia | automatic | Solo si declaraste boletas: el portal gratuito no tiene formulario de boleta, así que las emite nuestro motor y antes las habilitamos ante el SII. Dos decisiones tuyas: cuándo empezar y la declaración final |
| 9 | folios | facturia | automatic | Solo si declaraste boletas: el primer timbraje (CAF) de la 39/41. Las facturas del carril gratuito no llevan CAF |
Trampa
Las boletas del carril gratuito salen por nuestro motor
El Portal MIPYME nunca tuvo formulario de boleta, así que una 39 o una 41 no viaja por el
sistema gratuito: la construye, la firma y la envía el motor propio de Facturia, exactamente igual
que la de una empresa del carril mercado. El emission_channel de la empresa no decide esto — lo
decide el tipo. Lo que sí decide si puedes emitirla es la habilitación de boletas del SII (paso 8) y
el primer CAF (paso 9); hasta que estén, POST /v1/documents responde 422 empresa_not_emitting
nombrando el paso habilitacion, y capabilities.tipos no lista la 39 ni la 41. Tus facturas no
esperan a nada de esto: salen en cuanto termina el paso 7.
Trampa
`puesta_en_vivo` nunca es tuyo
Es un paso actor: facturia con stall_actor: facturia: una espera con estimación, no un
action_required. Si tu asistente lo dibuja como algo que el cliente puede apretar, va a mandar a
una persona a mirar una página donde no hay nada que hacer. habilitación está reservado para actos
del SII; puesta en vivo es el nuestro, y ocurre en los dos carriles.
¿Ya emites con otro proveedor?
El SII certifica al contribuyente, no al software. La resolución que tu empresa ya tiene sigue
valiendo aunque cambies de proveedor, y volver a certificarse sería ganarse de nuevo algo que ya se
tiene. Así que se declara al crear la empresa — o después, con PATCH /v1/empresas/{empresa_id} —
y nosotros la comprobamos.
curl -X POST https://api.facturia.cl/v1/empresas \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Content-Type: application/json" \
-d '{
"rut": "77928532-4",
"legal_name": "Connect SpA",
"origen": "mercado",
"doc_types": [33, 34, 61],
"resolucion": { "numero": 80, "fecha": "2026-06-21" }
}'numero y fecha son el NroResol y el FchResol de la carátula: están impresos en cualquier
documento que tu empresa ya haya emitido con su proveedor actual. No hay que ir al SII a buscarlos.
Declarar no salta nada. El campo escribe dos columnas y no mueve ni un paso. Léelo de vuelta:
resolucion.verified es false hasta que lo comprobemos, y mientras tanto la empresa camina la
lista completa.
{
"resolucion": {
"numero": 80,
"fecha": "2026-06-21",
"verified": false,
"verified_at": null
}
}Cómo lo comprobamos
Pidiéndole folios al SII. En cuanto tenemos tu certificado en custodia, la máquina le pide al portal que timbre un tipo de documento de los que declaraste: el SII rechaza el timbraje de un RUT que no autorizó para ese tipo, así que un rango otorgado es la prueba y un rechazo es la respuesta.
- Te los da →
resolucion.verifiedpasa atrue, los pasos de certificación quedanskippedcon la nota que dice por qué, yestimated_ready_ates hoy: lo único que falta es el resto de tus folios. - Te los niega →
verifiedse queda enfalsey el pasosii_postulacionvuelve a ser tuyo, conblocked_reason.resolucion_claimllevando la frase textual del SII. Nunca te dejamos sin explicación.
Es un acto, no una lectura, y no hay otra forma: ninguna consulta del SII distingue una resolución de mercado de una inscripción en el sistema gratuito, y por eso lo comprobamos usándola.
Boletas
La habilitación de boleta electrónica es otra autorización, con su propia fecha y sin número — el
correo del SII dice «habilitados los documentos de boleta electrónica a contar del …» y eso es todo.
Se declara aparte, junto a doc_types con 39 o 41:
{ "resolucion_boletas": { "fecha": "2026-09-01" } }Se comprueba igual, con un timbraje de 39 o 41. Si resulta, tus boletas quedan habilitadas sin pasar por nuestra cadena de habilitación; si no, aparecen los pasos de habilitación normales.
La casilla de intercambio sigue siendo tuya
Cambiar el correo de intercambio en Mi SII es un acto de administración de tu cuenta y el mandato nos
prohíbe expresamente tocarlo. Mientras no lo cambies, tus proveedores le siguen enviando los
documentos a tu proveedor anterior: nada se pierde, pero no llega acá. Tu dirección con nosotros la
publica cada empresa en reception_email, y el acto casilla_intercambio de
GET /v1/empresas/{empresa_id}/onboarding trae las instrucciones para la persona que puede hacerlo.
El camino, paso por paso
Cuenta y primera llave
Lo mismo que el quickstart, con un cambio: Facturia-Mode: live en vez
de test, y el set de scopes completo del arranque en frío.
curl -sS https://api.facturia.cl/dashboard/api_keys \
-H "Authorization: Bearer $TOKEN" \
-H "Facturia-Mode: live" \
-H "Content-Type: application/json" \
-d '{"name":"produccion","scopes":["documents:read","documents:write","empresas:read","empresas:write","webhooks:read","webhooks:write"],"empresas":["*"]}'Mintear una llave sk_live_ desde una cuenta sin verificar responde 403 email_verification_required.
Construye entero contra el sandbox primero
Una llave sk_test_ recorre el ciclo completo — emisión, estados, webhooks, PDF — sin tocar el SII
y sin costo. La empresa de sandbox nace emitting: la máquina de esta página se salta entera en
modo test, y eso es a propósito. Termina la integración ahí y cambia el prefijo después.
La empresa real: origen y canal de emisión
curl -sS https://api.facturia.cl/v1/empresas \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"rut": "76000002-7",
"legal_name": "Ejemplo SpA",
"origen": "ninguno",
"doc_types": [33, 34, 61]
}'origen es obligatorio en modo real (Origen): gratuito
si la empresa ya emite por el portal del SII, mercado si ya usa un proveedor, ninguno si todavía
no emite electrónicamente. Omitirlo con una llave sk_live_ es 400 invalid_field.
origen no elige el canal y no decide qué se puede saltar. Es una declaración sobre el presente
que enruta la copia del asistente; el salto de certificación se decide contra el SII, empíricamente.
El canal de emisión es una preferencia distinta —seguir en el portal gratuito o pasar al motor
certificado— y por eso se declara aparte.
ambiente es derivado y de solo lectura: SFG es siempre producción; mercado es certificación
hasta que llega la resolución. No lo mandes.
Mandato: un enlace de aprobación, no una casilla
El mandato lo firma el representante legal, que muy seguido no es quien tiene la cuenta. Así que el camino por defecto es acuñar un enlace y dárselo:
# Cuerpo vacío = acuñar el enlace de firma.
curl -sS -X POST https://api.facturia.cl/v1/empresas/$EMPRESA_ID/mandato \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Content-Type: application/json" \
-d '{}'La respuesta trae el signing_url. Se devuelve una sola vez — de ahí en adelante solo guardamos
su hash. Para saber cómo va, se lee:
curl -sS https://api.facturia.cl/v1/empresas/$EMPRESA_ID/mandato \
-H "Authorization: Bearer $FACTURIA_KEY"que responde status, terms_current y acceptance_method (disponible desde v1.1).
Scopes: acuñar y leer el mandato pide mandato:write. Postear una aceptación con cuerpo —el caso
del integrador que recogió la firma en su propio flujo— pide mandato:attest, se registra visible
como acceptance_method: api y nunca entra en el set por defecto de una llave nueva ni se concede
por implicación: empresas:write implica mandato:write (así una llave que ya existe sigue
pudiendo acuñar y leer el mandato) pero no mandato:attest, que hay que pedirlo explícitamente
(ADR-0012 §4). Ver Scopes.
GET /v1/mandato_terms es público (sin autenticación) y hoy publica la versión vigente:
{ "object": "mandato_terms", "version": "1.0", "status": "active", "effective_date": "2026-08-24" }Una aceptación contra una versión distinta a la vigente —la tuya quedó vieja porque el texto
cambió— se rechaza con 422 mandato_terms_changed, no con mandato_terms_draft: no hay ningún
estado de borrador que bloquee la aceptación en modo real. GET …/mandato publica terms_current
con la versión vigente y su content_hash; si no coincide con lo que firmaste, toca re-firmar con
un signing_url nuevo (ver «Revocar y re-firmar» más abajo).
Revocar y re-firmar
El mandato es revocable «en cualquier tiempo, sin expresión de causa, sin sanción ni cobro alguno» (cláusula 7.1), y hay dos puertas:
- Con tu llave o tu sesión:
DELETE /v1/empresas/{empresa_id}/mandato—204incluso si ya no había un mandato vivo, porque pediste un estado («ya no tengo autorización tuya») y ese es el resultado en los dos casos. - El representante legal, sin llave: el correo-recibo de la firma lleva un enlace perpetuo a
.../onboarding/{empresa_id}/mandato_revocation?token=…. La página pide el RUT del representante legal tal como quedó registrado en el mandato que firmó —el enlace no vence, así que el RUT es lo que evita que un correo reenviado sea un botón de parada— y llamaPOST https://api.facturia.cl/onboarding/mandato/revokecon{"token": "…", "rep_legal_rut": "…", "revoke": true}. Si el enlace se perdió, se remite conPOST .../onboarding/mandato/revocation_link(mismos dos factores).
Qué pasa al revocar: la empresa queda suspended de inmediato —el mismo tick, no en cinco
minutos— y deja de poder emitir; los documentos ya emitidos con valor tributario siguen existiendo,
la revocación no los borra. La custodia del certificado es una decisión aparte (cláusula 7.1.1):
revocar el mandato no destruye el .pfx, eso es DELETE /v1/empresas/{empresa_id}/certificate.
Volver a firmar restablece la posición que la empresa tenía antes de revocar (cláusula 7.1.3): si ya había certificado, no vuelve a certificar solo porque re-firmó. El mismo flujo del signing link de arriba sirve para volver a firmar.
Cambio de versión del texto, el otro motivo para re-firmar: si GET /v1/mandato_terms publica
una versión distinta a la que el mandato vigente aceptó, cualquier acto que dependa del mandato
responde 422 mandato_terms_changed en vez de proceder. La salida es la misma: acuña un signing_url
nuevo con POST …/mandato {} y que el representante legal lo firme de nuevo; el resto de la posición
de la empresa no se mueve.
Certificado (mercado) o clave centralizada (SFG)
En el carril mercado el paso 2 es el certificado digital del representante legal:
curl -sS -X POST https://api.facturia.cl/v1/empresas/$EMPRESA_ID/certificate \
-H "Authorization: Bearer $FACTURIA_KEY" \
-F "[email protected]" -F "password=…"Queda cifrado en una bóveda por empresa y no vuelve a salir; no es un argumento de la llamada de
emisión. Se puede purgar con DELETE /v1/empresas/{empresa_id}/certificate, lo que revoca la
custodia y suspende la emisión.
En el carril SFG el paso equivalente es la credencial del portal: la clave del certificado centralizado del SII (no la clave tributaria) que el Portal MIPYME vuelve a pedir en el paso final de cada emisión.
curl -sS -X POST https://api.facturia.cl/v1/empresas/$EMPRESA_ID/sfg_credential \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Content-Type: application/json" \
-d '{"clave_centralizado": "…"}'{
"object": "sfg_credential",
"empresa_id": "emp_01K2R7Q4XW9M3B8ZC5YHTVJD6N",
"clave_centralizado": { "stored": true, "stored_at": "2026-08-24T12:03:11Z" },
"purpose": "sfg_emission_centralizado_signing"
}GET del mismo recurso responde la misma forma —solo si la tenemos guardada y desde cuándo, nunca
la clave misma— y DELETE /v1/empresas/{empresa_id}/sfg_credential la destruye (204, también
cuando no había nada que borrar: pediste un estado, «no tengo nada tuyo», y ese es el resultado en
los dos casos).
El paso clave_centralizado (disponible desde v1.1) es el que nombra ese handoff. Es actor: customer, su performed_by.role es usuario_administrador y su evidencia es own_record: cae en
nuestras propias tablas en el mismo acto del POST, así que lo vemos ocurrir sin preguntarle al SII:
{
"blocked_on": "clave_centralizado",
"pending_action": {
"object": "pending_action",
"act": "sfg_clave_centralizado",
"step": "clave_centralizado",
"title": "Entregarnos la clave de tu Sistema de Facturación Gratuito",
"performed_by": {
"role": "usuario_administrador",
"label": "El usuario administrador de la empresa ante el SII, o quien tenga la clave del Sistema de Facturación Gratuito."
},
"verification": {
"method": "own_record",
"recheck_url": "POST /v1/empresas/{empresa_id}/onboarding/steps/clave_centralizado/recheck"
},
"agent_actions": [
{
"method": "POST",
"url": "https://api.facturia.cl/v1/empresas/{empresa_id}/sfg_credential",
"body": { "clave_centralizado": "…" }
}
]
}
}Trampa
Nunca pedimos la clave tributaria
Toda la suite tributaria funciona con una sesión autenticada por certificado contra la zona de datos del propio SII. La clave del Sistema de Facturación Centralizado no es la clave tributaria y no la sustituye.
Los pasos humanos: el patrón de handoff
Cuando un paso necesita a una persona, la respuesta no dice «bloqueado». Dice quién, dónde, qué hacer y cómo nos vamos a enterar — la misma forma con que OAuth entrega la autenticación.
{
"object": "onboarding",
"status": "action_required",
"blocked_on": "sii_postulacion",
"pending_action": {
"object": "pending_action",
"act": "sii_postulacion",
"step": "sii_postulacion",
"title": "Autorizar la postulación al sistema de facturación de mercado",
"action_url": "https://app.facturia.cl/onboarding/…",
"sii_url": "https://www.sii.cl/…",
"performed_by": {
"role": "titular_cuenta",
"label": "El titular de la cuenta de Facturia",
"why_not_delegable": "…",
"legal_basis": ["Mandato Facturia, cláusula 4.2 a) «Certificación como emisor electrónico»", "…"]
},
"approval": {
"act": "postulacion_autorizacion",
"role": "titular_cuenta",
"headline": "¿Presentamos la postulación ahora?",
"consequences": ["Si hoy emites en el Sistema de Facturación Gratuito, tu empresa pierde su calidad de usuario activo…"],
"button_label": "Sí, preséntenla",
"reject_label": "No todavía"
},
"instructions": [{ "order": 1, "text": "…" }],
"verification": {
"method": "sii_probe",
"recheck_url": "POST /v1/empresas/{empresa_id}/onboarding/steps/sii_postulacion/recheck",
"recheck": {
"method": "POST",
"url": "https://api.facturia.cl/v1/empresas/{empresa_id}/onboarding/steps/sii_postulacion/recheck"
},
"sii_latency": "…"
},
"evidence": "captured",
"caveat": null
}
}`recheck_url` está deprecado
recheck_url sigue viniendo, tal cual, para no romper a nadie que ya lo lee. Es un string "POST <path>" relativo — hay que separar el método a mano y anteponer el host. Úsalo como el recheck
de arriba: el mismo endpoint, como {method, url}, absoluto. Lo mismo pasa con
error.details.onboarding_url más abajo: error.details.onboarding es la versión estructurada.
Tres campos deciden qué hace tu agente con esto:
performed_by.role— a quién se lo entregas.representante_legal,usuario_administrador,titular_certificadootitular_cuenta. Un handoff se dirige a un rol, nunca a una persona: nosotros no sabemos cuál de ellos es tu principal.verification.method— cómo nos enteramos.sii_probe(releemos el SII),own_record(cae en nuestras tablas) ono_producer, que significa que nada en esta versión lo observa y que acá una persona tiene que confirmarlo. No le digas a nadie que estás vigilando unno_producer.evidenceycaveat— qué tan respaldada está la ruta de clics. Sievidenceno escaptured,caveatno es nulo y dice de qué no estamos seguros. Trasládalo.
instructions[] va en español y se traslada textual: no la parafrasees ni le agregues pasos.
El enlace: action_url o link.mint
pending_action.action_url (y steps[].action_url) es la URL de un enlace vivo —no vencido, no
revocado— si existe uno para ese paso. Si no existe, es null. Nunca es una URL que abra «falta el
enlace».
Para acuñar uno, el propio handoff dice cómo (disponible desde v1.1):
"link": {
"mint": {
"method": "POST",
"url": "https://api.facturia.cl/v1/empresas/{empresa_id}/onboarding/links",
"body": { "step": "sii_postulacion" }
},
"ttl_seconds": 2592000,
"reusable": true
}POST /v1/empresas/{empresa_id}/onboarding/links acepta cualquier paso actor: customer del
catálogo activo de la empresa, sea del carril mercado o del SFG. La única excepción es mandato, que
tiene su propio endpoint (POST …/mandato con cuerpo vacío) y por eso su link.mint apunta ahí con
body: {}. Acuñar pide onboarding:write.
La url se devuelve una sola vez; re-acuñar revoca la anterior. Se revoca con
DELETE /v1/empresas/{empresa_id}/onboarding/links/{step}, que responde 204 haya existido o no.
La vuelta: recheck
Cuando la persona terminó, tu agente nos lo dice y nosotros vamos a mirar:
STEP=sii_postulacion
curl -sS -X POST \
https://api.facturia.cl/v1/empresas/$EMPRESA_ID/onboarding/steps/$STEP/recheck \
-H "Authorization: Bearer $FACTURIA_KEY"Esto acepta una declaración y no produce ningún hecho. No lleva cuerpo, no escribe el estado de
ningún paso y no mueve la empresa. Encola al productor que lee el SII y devuelve el estado tal como
estaba al momento de llamar. Si posteas sobre un paso en action_required y lees action_required
de vuelta, eso es el endpoint funcionando: verified viene en la respuesta y siempre es false.
La forma completa está en OnboardingRecheck.
No es obligatorio: sii_probe y own_record nos llegan solos. recheck sirve para no esperar el
próximo barrido.
Cuando el paso es NUESTRO y sólo falta que alguien diga que sí: approve
Tres pasos los ejecutamos nosotros y aun así no arrancan solos, porque cada uno tiene una consecuencia
que alguien tiene que decidir primero: la postulación (una empresa del Sistema de Facturación
Gratuito pierde su calidad de usuario activo en cuanto el SII la autorice), la Declaración de
Cumplimiento (cierra la certificación y abre, desde el mes que se declare, la obligación de
Información Electrónica de Compras y Ventas) y la habilitación de boletas (abre un reloj de 24
horas del SII). Esos pasos traen un bloque approval en su pending_action:
"approval": {
"act": "postulacion_autorizacion",
"role": "titular_cuenta",
"headline": "¿Presentamos la postulación ahora?",
"consequences": ["…lo que hay que leer ANTES de apretar…"],
"button_label": "Sí, preséntenla",
"reject_label": "No todavía"
}La forma canónica de tomar esa decisión es la página alojada (ADR-0012 §1): acuña el enlace del paso y entrégaselo al titular. Si tu integración recogió la decisión en su propio flujo, la puerta es:
curl -sS -X POST \
https://api.facturia.cl/v1/empresas/$EMPRESA_ID/onboarding/steps/$STEP/approve \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Content-Type: application/json" \
-d '{"act": "postulacion_autorizacion", "decision": "approve"}'Pide onboarding:attest —nunca implicado por empresas:write—, el act tiene que ser el que el
paso está pidiendo ahora (si no, 409 onboarding_approval_not_offered o 400 invalid_field), y
decision: "reject" se registra igual que una aprobación: «no todavía» es una respuesta, y los
ejecutores leen la MÁS RECIENTE.
La Declaración de Cumplimiento: la presentamos nosotros
La Declaración de Cumplimiento cierra la certificación de mercado. Al presentarla, la empresa queda operando en el sistema de facturación electrónica y debe la Información Electrónica de Compras y Ventas desde el mes que se declare, emita o no emita ese mes. El SII permite postergarla, así que el mes sigue siendo una decisión y no un efecto automático del resto de la certificación.
La presentamos nosotros, con el certificado del representante legal que tenemos en custodia — el mismo con el que firmamos tus documentos. No tienes que entrar al sitio del SII ni completar ningún formulario allá.
Lo único que falta es que el representante legal apruebe, en su propia página. El rol que puede
aprobar este acto es rep_legal, no cualquier titular_cuenta:
"approval": {
"act": "cumplimiento_declaracion",
"role": "rep_legal",
"headline": "¿Presentamos la Declaración de Cumplimiento?",
"consequences": [
"Tu empresa queda operando en el sistema de facturación electrónica desde el mes indicado abajo.",
"Desde ese mes debes la Información Electrónica de Compras y Ventas, emitas o no."
],
"button_label": "Sí, preséntenla",
"reject_label": "No todavía"
}Ese único clic aprueba dos cosas a la vez: el acto ante el SII, y —si el titular no había fijado el mes antes por la vía de la sección siguiente— el mes mismo. Antes de presentar comprobamos, contra el propio SII, que el titular del certificado en custodia es el mismo representante legal que la empresa tiene autorizado allá: es lo que exige la Res. Ex. SII N° 81 de 2005, resolutivo CUARTO d) — que sea el representante legal quien declare «con su certificado digital» —, y la misma comprobación que ya corrimos para la habilitación de boletas.
Fijar el mes antes, por separado: go_live
Si el titular quiere decidir el mes antes de que exista algo que aprobar —o es una persona distinta del representante legal—, puede fijarlo por adelantado:
# Cuerpo vacío = acuñar el enlace de aprobación para el titular.
curl -sS -X POST https://api.facturia.cl/v1/empresas/$EMPRESA_ID/onboarding/go_live \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Content-Type: application/json" \
-d '{}'La respuesta trae { "approval_url": "…" } (disponible desde v1.1): la página donde el titular ve
exactamente qué está aprobando —las consecuencias del mes arriba, y el botón de autorizar debajo—.
Acuñar pide onboarding:write.
Tiene que haber algo que decidir. Si la empresa todavía no llegó a la declaración, no acuñamos un enlace que abriría una página vacía:
409 go_live_not_ready— el carril de mercado aún no llega a la decisión.error.details.blocked_ondice qué paso lo está deteniendo yerror.details.nextes el{method, url}que avisa cuando se mueve. Vuelve a intentarlo: va a estar listo.409 go_live_not_applicable— la empresa no se está certificando para mercado, así que no tiene declaración de cumplimiento. Lo que abre su emisión es lapuesta_en_vivo, que es nuestra.
Ninguno de los dos aplica a {"authorized": false}: una decisión que existe siempre se puede retirar.
La forma con cuerpo —{"authorized": true} posteado por un integrador que recogió la aprobación en
su propio flujo— queda detrás de onboarding:attest, el mismo trato que mandato:attest. Mandar
{"authorized": false} retira la autorización mientras no se haya declarado nada; después de
declarada no queda nada que retirar y se responde 409 cumplimiento_already_filed.
Esta autorización es gratis (#530). Estuvo entre los actos que exigían suscripción activa hasta
el 2026-09-04, y solo por esta puerta: la página alojada de cumplimiento para el representante
legal respondía 200 para la misma empresa sin suscripción. Es una decisión, no una emisión. La
suscripción se exige donde está el valor: emitir, la nota de crédito, pedir folios y enviar. Una
empresa autorizada que no ha pagado no emite nada.
La presenta Facturia; el representante legal la aprueba
Desde ADR-0013 (2026-09-06) el acto ante el SII lo presentamos nosotros con el certificado en custodia — nunca el representante legal en persona. Lo que él hace es aprobar, en su propia página, un acto que ya lo identifica: la Res. Ex. SII N° 81 de 2005 exige que sea el representante legal quien declare «con su certificado digital», y ese es exactamente el certificado que tenemos en custodia bajo el mandato.
Puesta en vivo
Después de que el carril terminó lo suyo, queda un paso nuestro: habilitar la emisión real de esa
empresa. En SFG es el paso puesta_en_vivo, después del canario; en mercado es lo que sigue a los
folios. Se muestra como una espera con estimación y nunca como algo que el cliente deba hacer.
Mientras tanto, Empresa.capabilities dice la verdad en una lectura (disponible desde v1.1):
"capabilities": {
"can_emit": false,
"tipos": [],
"blocked_by": "puesta_en_vivo",
"next": { "who": "facturia" }
}Es la misma función que usa la puerta de emisión para responder 422 empresa_not_emitting, así
que el bloque y el rechazo no se pueden contradecir. blocked_by toma onboarding, subscription,
status, puesta_en_vivo o null, y next.who es agent, human o facturia.
Folios
En el carril mercado los folios son un CAF que le pedimos al SII y que vive en la empresa:
curl -sS https://api.facturia.cl/v1/folios \
-H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA"auto_request_folios viene encendido por defecto — es la única automatización que sí lo está — y
pide un timbraje nuevo cuando la disponibilidad baja del umbral. También puedes pedirlo tú con
POST /v1/folios/requests, o cargar un CAF propio con POST /v1/cafs.
En el carril SFG no hay timbraje: el SII asigna el folio en cada emisión. GET /v1/folios
responde con la forma sfg_managed y folio_preview es null en los borradores. Eso no es una
carencia de datos; es cómo funciona ese canal.
Detalles en Folios y CAF.
Webhooks
Lo último que conviene tener listo antes de la primera emisión real, porque es lo que evita el polling:
curl -sS https://api.facturia.cl/v1/webhook_endpoints \
-H "Authorization: Bearer $FACTURIA_KEY" -H "Content-Type: application/json" \
-d '{
"url": "https://api.miempresa.cl/hooks/facturia",
"enabled_events": ["document.accepted","document.rejected","document.reparo"]
}'El secret (whsec_…) se devuelve una sola vez. Cómo verificar la firma está en
Webhooks · Firma.
Cuando la puerta se cierra igual
Si emites antes de tiempo, el rechazo trae el handoff adentro:
{
"error": {
"type": "unprocessable_error",
"code": "empresa_not_emitting",
"message": "…",
"hint": "…",
"retriable": false,
"details": {
"onboarding_status": "sii_postulacion_pending",
"onboarding_url": "/v1/empresas/{empresa_id}/onboarding",
"onboarding": { "method": "GET", "url": "https://api.facturia.cl/v1/empresas/{empresa_id}/onboarding" },
"pending_action": { "…": "…" }
}
}
}onboarding_url es la misma dirección de siempre, pero relativa — hay que anteponerle el host.
onboarding (#419) trae el mismo endpoint absoluto, como {method, url}, igual que
pending_action.link.mint. Los dos siguen viniendo juntos; onboarding_url queda marcado
deprecated en el contrato y es el que hay que dejar de leer.
Es el mismo objeto PendingAction del onboarding, así que
tu manejo de errores y tu asistente pueden compartir el código. Los códigos que vas a ver: 422 empresa_not_emitting, 422 mandato_required, 422 postulacion_required, 422 onboarding_requirement_unmet
y, si la empresa ya puede emitir por lo demás y no tiene suscripción, 402 subscription_required.
La elegibilidad se evalúa primero (#475): mientras el checklist esté incompleto el rechazo es
422 empresa_not_emitting con su handoff, nunca «paga». Cada uno tiene su ficha en
Errores.