Facturiadocs
Empieza aquí

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.

mercadoSFG (Sistema de Facturación Gratuito)
Quién emiteNuestro motor propio, certificado ante el SIIEl portal gratuito del SII, operado por nosotros
CertificaciónSí: set de pruebas, simulación, intercambio, muestras impresasNinguna — el SII ya habilitó a la empresa al inscribirla
Ambientecertificacion hasta la resolución, después produccionSiempre produccion
FoliosTimbraje propio (CAF)Los asigna el SII en cada emisión
Pasos11 (10 sin boletas)7
Copia cedibleSí (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:

#keyactorexecutionQué es
1mandatocustomerautomaticEl representante legal nos autoriza a actuar ante el SII. Revocable
2certificatecustomerautomaticEl .pfx del representante legal, a la bóveda
3sii_postulacionfacturiaautomaticLa presentamos nosotros con el certificado en custodia; el titular autoriza el momento
4set_de_pruebasfacturiaautomaticEmitimos el set que el SII exige por cada tipo
5simulacionfacturiaautomaticLa simulación previa a la autorización
6intercambiofacturiaautomaticProbamos que la empresa recibe y responde documentos de terceros
7muestras_impresasfacturiaautomaticEnviamos las muestras impresas de cada tipo
8cumplimientofacturiaautomaticLa presentamos nosotros con el certificado en custodia; el representante legal aprueba el acto y, si no estaba fijado antes, el mes
9resolucionsiiautomaticEl SII dicta la resolución. Acá solo se espera
10habilitacionfacturiaautomaticSolo 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
11foliosfacturiaautomaticPedimos el primer timbraje (CAF) de cada tipo

Carril SFG — siete pasos, sin certificación, más dos si declaras boletas:

#keyactorexecutionQué es
1mandatocustomerautomaticEl mismo mandato, el mismo registro, la misma revocación
2credentialcustomerautomaticCertificado en custodia o delegación: nuestro RUT como usuario autorizado
3clave_centralizadocustomerautomaticLa clave del Sistema de Facturación Centralizado, por POST /v1/empresas/{empresa_id}/sfg_credential
4usuario_autorizadocustomerautomaticComprobamos que nuestro certificado puede actuar por la empresa en el Portal MIPYME
5emisor_verificadofacturiaautomaticConfirmamos que la empresa está inscrita y autorizada a emitir
6puesta_en_vivofacturiaautomaticNuestro: el canario y la habilitación de emisión real por empresa
7emittingfacturiaautomaticListo: emitimos y recibimos por su cuenta del sistema gratuito
8habilitacionfacturiaautomaticSolo 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
9foliosfacturiaautomaticSolo 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.verified pasa a true, los pasos de certificación quedan skipped con la nota que dice por qué, y estimated_ready_at es hoy: lo único que falta es el resto de tus folios.
  • Te los niega → verified se queda en false y el paso sii_postulacion vuelve a ser tuyo, con blocked_reason.resolucion_claim llevando 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 — 204 incluso 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 llama POST https://api.facturia.cl/onboarding/mandato/revoke con {"token": "…", "rep_legal_rut": "…", "revoke": true}. Si el enlace se perdió, se remite con POST .../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_certificado o titular_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) o no_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 un no_producer.
  • evidence y caveat — qué tan respaldada está la ruta de clics. Si evidence no es captured, caveat no 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_on dice qué paso lo está deteniendo y error.details.next es 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 la puesta_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.

Siguiente paso

En esta página