Facturiadocs
Autenticación

Scopes y empresas

Qué concede cada scope, cómo se combina con la lista blanca de empresas y qué error recibes cuando falta uno.

La tabla de scopes

ScopeConcede
documents:readLeer documentos emitidos, PDF, XML y eventos
documents:writeEmitir documentos, reintentar, enviar por correo
received:readLeer los DTE recibidos de proveedores
received:writeAceptar / reclamar / acusar recibo
empresas:readLeer empresas, puesta en marcha, configuración, folios
empresas:writeCrear empresas, cargar certificados, cambiar configuración
folios:writeSolicitar CAF al SII, cargar CAF
tax:readRCV, propuestas y declaraciones de F29, sugerencias
tax:writeCaracterizar, declarar F29, confirmar sugerencias, ingresar gastos
webhooks:read / webhooks:writeEndpoints de webhook y replay de eventos
usage:readLibro de consumo
keys:writeMintear, rotar y revocar llaves de API. Solo llaves root; nunca combinado con los scopes de arriba

Si falta un scope recibes 403 insufficient_scope, con el scope requerido nombrado en error.message. No tienes que adivinar cuál era.

El set de arranque

La primera llave de una cuenta se mintea con estos seis, que son exactamente lo que recorre el quickstart de punta a punta:

documents:read, documents:write, empresas:read, empresas:write, webhooks:read, webhooks:write

Ni uno más. keys:write queda fuera porque es el poder de una llave root, y los cuatro scopes de la puesta en marcha quedan fuera porque conceden actos con efecto legal.

Los scopes de la puesta en marcha

Cuatro scopes estrechos gobiernan los actos que autorizan a Facturia ante el SII (disponibles desde v1.1). Ninguno entra en el set de arranque, ni por defecto ni por conveniencia, y se piden uno por uno:

ScopeConcede
mandato:writeAcuñar el enlace de firma del mandato y leerlo (GET …/mandato)
mandato:attestPostear una aceptación con cuerpo (el integrador recogió la firma en su propio flujo; queda registrado como acceptance_method: api) y revocar el mandato con DELETE …/mandato — la revocación es el acto de mayor efecto del contrato y desde v1.1 no la concede empresas:write
onboarding:writeAcuñar enlaces de pasos humanos, pedir una re-lectura, acuñar el enlace de aprobación de go_live, cargar el certificado
onboarding:attestPostear la autorización de go_live con cuerpo

empresas:write implica los dos scopes :write (mandato:write, onboarding:write) — así una llave que ya existe sigue pudiendo acuñar el enlace de firma y los enlaces de los pasos humanos. No implica ninguno de los dos :attest: esos se piden explícitamente o no se conceden nunca (ADR-0012 §4). Una llave con solo empresas:write que intenta POST …/mandato con cuerpo recibe 403 insufficient_scope: mandato:attest, exactamente como si no tuviera ningún scope de la puesta en marcha. La diferencia entre :write y :attest es la diferencia entre pedirle a una persona que apruebe y afirmar que ya aprobó en otra parte: lo segundo es una atestación, se registra visiblemente como tal, y por eso vive detrás de su propio scope que nunca se concede por implicación ni por defecto. El camino completo está en De sandbox a producción.

Trampa

Una llave root no puede leer documentos

keys:write no se combina con ningún otro scope. Una llave root (sk_live_root_… / sk_test_root_…) no puede emitir, no puede leer documentos y no puede tocar datos tributarios: su único poder es mintear y revocar otras llaves.

Es la confusión más común al empezar: minteas la llave root, la pruebas contra /v1/documents y recibes un 403. No está rota — está haciendo exactamente lo que debe. Mintea con ella una llave ordinaria y usa esa.

Lista blanca de empresas

Además de los scopes, cada llave lleva una lista de empresas:

  • ["*"] — toda empresa de la cuenta, incluidas las que se creen después.
  • Una lista explícita de RUT — ["77928532-4", "78281439-7"].

Una llave con exactamente un RUT es un token de empresa y el header Facturia-Empresa se vuelve opcional.

Los dos ejes se aplican juntos: una llave con documents:write sobre ["77928532-4"] puede emitir para esa empresa y para ninguna otra. Nombrar otra empresa devuelve 403 empresa_not_allowed — no un 404, porque ya conoces su RUT y fingir que no existe no protege a nadie.

Combinaciones que conviene tener en la cabeza

FormaScopesEmpresasPara qué
Backend de un producto propiodocuments:*, received:*, tax:*["*"]Una sola llave que opera todas las empresas de la cuenta
Llave por cliente (ERP, SaaS vertical)lo mínimo que ese cliente necesitaun solo RUTEl sistema del cliente no puede direccionar nada más
Solo lectura para BI o conciliacióndocuments:read, tax:read, usage:read["*"]Una llave que no puede emitir aunque se filtre
Rootkeys:writesegún lo que va a delegarMintear y revocar llaves. Nada más
Exploración y demoscualquiera, pero sk_test_cualquieraUna llave de test es un límite de capacidad, no una política

Modo de sesión: Facturia-Mode

La superficie /dashboard/* se autentica con un JWT de cuenta en vez de una llave sk_, porque un navegador nunca debe sostener una llave sk_. Una sesión no tiene prefijo que cargue su modo, así que el modo viaja en un header:

Facturia-Mode: test

Por defecto es live. El backend lo aplica exactamente igual que aplicaría el modo de una llave: en modo live una empresa de sandbox no existe, y viceversa. Un valor no reconocido es un 400, nunca una caída silenciosa a live.

Dos reglas aplican a una sesión que no aplican a una llave:

  • Una sesión puede mintear llaves Y leer datos. Una llave root no puede lo segundo. La diferencia es intencional: una llave root es una credencial delegada, y una sesión es el dueño de la cuenta en persona.
  • Una sesión de cuenta es la única credencial que mintea una llave root: POST /dashboard/api_keys con {"root": true} (sin scopes ni empresas) devuelve una sk_live_root_… / sk_test_root_… sin abrir ningún panel. Una llave —root u ordinaria— que pide root: true recibe 403 privilege_escalation: la delegación llega hasta un nivel y una llave root no mintea otra llave root. Ver Llaves root y rotación.

En esta página