# Conceptos (/docs/empezar/conceptos)

Cuatro ideas. Si las tienes claras, el resto de esta documentación es detalle.

## 1. Empresa [#1-empresa]

Una **empresa** es una compañía chilena — un RUT — para la que Facturia emite. Es el límite de
tenencia de todo: folios, documentos, RCV, payloads de webhook y consumo. Nada cruza de una empresa
a otra.

Una cuenta es un contenedor de facturación y control de acceso sobre muchas empresas. Eso significa
que un ERP, un SaaS vertical o un estudio contable pueden operar N clientes bajo una sola cuenta,
con una llave por cliente si quieren.

```bash
curl -sS https://api.facturia.cl/v1/empresas \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "rut": "77928532-4",
    "legal_name": "Connect SpA",
    "giro": "Servicios de desarrollo de software",
    "acteco": [620200],
    "address": { "street": "Av. Apoquindo 4700, of. 1201", "comuna": "Las Condes", "city": "Santiago" },
    "contact_email": "facturacion@connect.cl",
    "origen": "ninguno",
    "doc_types": [33, 34, 39, 61]
  }'
```

`legal_name`, `giro`, `address`, `acteco` y `contact_email` se vuelven el **bloque emisor** de cada
documento. No los repites nunca en un payload de emisión — ese es el punto de guardar el perfil en
la empresa.

### `origen` y canal de emisión [#origen-y-canal-de-emisión]

`origen` es &#x2A;*obligatorio con una llave `sk_live_`** y opcional en modo test, donde vale `ninguno`.
Omitirlo en modo real es `400 invalid_field` — el `hint` te rescata, pero es exactamente el
round-trip que este ejemplo existe para evitarte.

Es tu declaración de **cómo emite hoy** la empresa: `gratuito` si ya usa el Sistema de Facturación
Gratuita del SII, `mercado` si ya usa un proveedor, `ninguno` si todavía no emite electrónicamente.
Enruta la copia del asistente de puesta en marcha y nada más — **no elige el carril y no decide qué
se puede saltar**. Que una empresa pueda saltarse la certificación se resuelve contra el SII,
empíricamente: estar inscrito en el sistema gratuito ya convierte a un RUT en emisor de DTE reales,
así que un `mercado` declarado que creyéramos saltaría la certificación justo a quien la necesita.

El **canal de emisión** es otra cosa y se declara aparte: es la preferencia entre seguir emitiendo
por el portal gratuito del SII, operado por nosotros, o pasar al motor certificado. Y `ambiente` no
se declara en absoluto: es derivado y de solo lectura. Los dos carriles, con sus pasos lado a lado,
están en [De sandbox a producción](/docs/empezar/produccion).

`DELETE` no existe: los documentos emitidos son registros tributarios. Un
`PATCH {"status":"archived"}` detiene la emisión y la facturación conservando la historia.

### Puesta en marcha [#puesta-en-marcha]

Llevar una empresa hasta `emitting` es una máquina de estados que puedes dibujar como asistente con
`GET /v1/empresas/{empresa_id}/onboarding`. En modo test la máquina se salta entera.

**Los pasos que necesitan a una persona son cinco**, no tres. Un paso necesita a una persona cuando
`actor: "customer"` (lo ejecuta ella) **o** cuando lo ejecutamos nosotros y no puede avanzar sin una
decisión suya. En orden:

1. **Mandato** — el representante legal nos autoriza a actuar ante el SII. Revocable cuando quiera.
2. **Certificado digital** (carril mercado) o la **clave del Sistema de Facturación Centralizado**
   (carril SFG) — la credencial con la que operamos.
3. **La autorización para postular** — la postulación la presentamos nosotros, con el certificado del
   representante legal que tenemos en custodia y bajo la cláusula 4.2 a) del mandato. Lo que decide el
   titular es **el momento**: si la empresa hoy emite en el Sistema de Facturación Gratuito, deja de
   ser usuaria activa de ese sistema en cuanto el SII la autorice.
4. **La Declaración de Cumplimiento** — la presentamos nosotros, con el mismo certificado, después de
   que el representante legal la aprueba en una página alojada (acto `cumplimiento_declaracion`, rol
   `rep_legal`). Esa misma aprobación fija también el mes desde el cual la empresa queda operando, si
   el titular no lo fijó antes por separado.
5. **Habilitación de boletas** — solo si declaraste boletas: la hacemos nosotros de punta a punta con
   el mismo certificado, con dos aprobaciones del representante legal en el camino — cuándo empezar y
   la declaración final.

Los actos 3, 4 y 5 los ejecutamos nosotros con el certificado en custodia y solo esperamos que la
persona correspondiente apruebe en su página; ninguno de los tres lo presenta una persona ante el SII
en persona. Los dos primeros son tuyos porque nadie más puede autorizarnos ni entregarnos una
credencial. Todo el resto — sets de pruebas, libros, simulación, intercambio, muestras impresas,
timbraje y la puesta en vivo — es guionizado y desatendido, y viene marcado `actor: "facturia"` o
`actor: "sii"`.

> **El certificado que nos entregas sí identifica a su titular**
>
> Un certificado digital chileno se emite a una **persona natural** —el representante legal— y una
> sesión con certificado ante el SII autentica a esa persona. Por eso la postulación y la declaración
> de cumplimiento las presenta Facturia con ese certificado, después de que quien corresponde las
> aprueba en una página. Cuando aun así te pedimos que hagas algo tú —agregar un usuario autorizado,
> dar el permiso «Enviar Doctos.», cambiar la casilla de intercambio—, la razón es de autoridad, no
> legal: esos actos se hacen en la sesión del **usuario administrador** de tu cuenta en el SII, y el
> certificado que custodiamos no tiene esa autoridad justo en los casos en que el acto falta. Nunca
> «el certificado no sirve».

`onboarding.status` nunca se queda en `action_required` sin un `action_url` que puedas poner delante
de una persona, ni sin un `pending_action` que diga a quién dárselo y qué hacer con él. El patrón
completo está en [De sandbox a producción](/docs/empezar/produccion).

## 2. Documento [#2-documento]

Un **documento** es un DTE emitido. Cada recurso de la API es un objeto JSON con `id`, `object`,
`mode` y `created_at`. Los ids son opacos, con prefijo, seguros para URL y ordenables por fecha de
creación (ULID por debajo). &#x2A;*Nunca parsees un id.**

| Prefijo         | Recurso                             |
| --------------- | ----------------------------------- |
| `emp_`          | empresa                             |
| `doc_`          | documento emitido                   |
| `rdoc_`         | documento recibido                  |
| `caf_`          | CAF                                 |
| `whe_` / `whd_` | endpoint de webhook / entrega       |
| `evt_`          | evento                              |
| `job_`          | job asíncrono                       |
| `rcv_`          | entrada del RCV                     |
| `f29_`          | propuesta o declaración de F29      |
| `sug_`          | sugerencia tributaria               |
| `exp_`          | gasto ingresado                     |
| `con_`          | conexión de conector (Fintoc)       |
| `key_`          | llave de API (su id, no su secreto) |
| `usg_`          | registro de consumo                 |

### Plata, cantidades y fechas [#plata-cantidades-y-fechas]

* **Los montos son enteros en CLP.** Sin decimales, sin strings, sin centavos. `29990` son
  veintinueve mil novecientos noventa pesos.
* **Los precios unitarios** pueden llevar hasta 4 decimales (`unit_price: 1234.5678`), porque el SII
  lo permite. Los montos de línea y los totales del documento siempre son enteros, redondeados
  medio-arriba como especifica el SII.
* **`issue_date`, `period`** y demás fechas civiles son `YYYY-MM-DD` / `YYYY-MM` en
  **America/Santiago**. La fecha de un DTE es una fecha del calendario chileno, no un instante.
* **Los timestamps** (`created_at`, `accepted_at`, …) son RFC 3339 en **UTC**: `2026-08-14T13:04:11Z`.
* **Los RUT** son canónicos `########-D`: sin puntos, un guion, K mayúscula. Aceptamos
  `77.928.532-4` y `779285324` en la entrada y siempre devolvemos la forma canónica.

## 3. Modo: test y live [#3-modo-test-y-live]

El modo lo decide **el prefijo de la llave**, y es inmutable.

| Prefijo    | Modo                                      | ¿Habla con el SII?                                        | Costo                                                                          |
| ---------- | ----------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `sk_test_` | **test** — sandbox completamente simulado | Nunca                                                     | Gratis para siempre                                                            |
| `sk_live_` | **live** — emisión real                   | Sí (certificación o producción del SII, según la empresa) | El plan único: $19.990 CLP + IVA al mes **por empresa**, documentos ilimitados |

> **Trampa — El modo y el ambiente son dos ejes distintos**
>
> El **modo** viene de la llave. El **ambiente** (`certificacion` / `produccion`) viene de la empresa.
> Una llave de test contra una empresa de producción sigue estando completamente simulada: no toca el
> SII. Y al revés, una llave live contra una empresa en `ambiente: certificacion` sí habla con el SII,
> pero con sus servidores de prueba (maullin / apicert).
>
> Todo objeto lleva estampado su `"mode"`. Los objetos de test son invisibles para las llaves live y
> viceversa: no comparten ids, ni contadores de folios, ni libro de consumo.

El modo se hereda y no se puede escalar: una llave `sk_test_root_` solo mintea llaves de test. No
hay camino de una credencial de test a una live.

## 4. El header `Facturia-Empresa` [#4-el-header-facturia-empresa]

Cada llamada con alcance de empresa nombra su empresa destino en un header:

```
Facturia-Empresa: 77928532-4
```

El valor es el RUT en forma canónica (sin puntos, guion, `K` mayúscula — `77928532-4`, `76543212-K`)
o el id de Facturia (`emp_…`). Recomendamos el RUT: es la llave natural que tus propios sistemas ya
tienen.

> **Trampa — Este header es el único mecanismo**
>
> No hay parámetro `?empresa=` ni campo `empresa` en el cuerpo, y no lo habrá dentro de la v1. Una
> segunda forma de decir lo mismo es una segunda forma de decirlo *distinto*, y sobre un recurso cuya
> identidad determina en qué registros tributarios estás escribiendo, esa ambigüedad no vale la
> conveniencia. Si tu cliente HTTP no puede poner un header, no puede llamar a esta API.

Las reglas:

| Situación                              | Comportamiento                                                                                                                                         |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Requerido** en                       | `/documents`, `/received_documents`, `/tax/*`, `/folios`, `/cafs`, `/connections`, `/test_helpers/*`                                                   |
| **Ignorado** en                        | `/empresas/{empresa_id}/...` — la empresa sale de la ruta                                                                                              |
| **Prohibido** en                       | `/empresas` (listar/crear), `/webhook_endpoints`, `/events`, `/usage`, `/account`, `/api_keys`                                                         |
| Omitido donde se requiere              | `400 empresa_required`, salvo que la llave sea un token de empresa                                                                                     |
| Presente pero fuera de la lista blanca | `403 empresa_not_allowed`. Nunca un `404`: te decimos que la empresa está fuera de tu alcance en vez de fingir que no existe, porque ya conoces su RUT |

### Token de empresa: cuando el header sobra [#token-de-empresa]

Una llave cuya lista blanca tiene exactamente una empresa es un **token de empresa**. Para un token
de empresa el header es opcional; si lo mandas, debe coincidir, o recibes `400 empresa_mismatch`.

```bash
# token de empresa — sin header Facturia-Empresa
curl https://api.facturia.cl/v1/documents \
  -H "Authorization: Bearer sk_live_qT8vN2wLmR5xKpJ7hYdCbA3s" \
  -H "Content-Type: application/json" \
  -d '{"tipo_dte":39,"items":[{"description":"Café","quantity":2,"unit_price":2500}]}'
```

Es la forma recomendada cuando integras una sola compañía (el caso común) o cuando le entregas una
llave al sistema del propio cliente: la llave no puede direccionar nada más que ese RUT, y tu código
nunca tiene que pensar en empresas.

## Convenciones transversales [#convenciones-transversales]

### Listas y paginación [#listas-y-paginación]

Todos los endpoints de lista devuelven el mismo sobre y usan paginación por cursor.

```json
{
  "object": "list",
  "url": "/v1/documents",
  "data": [ { "object": "document", "…": "…" } ],
  "has_more": true,
  "next_cursor": "cur_01K2R7Q4XW9M3B8ZC5YHTVJD6N"
}
```

Parámetros: `limit` (1–100, por defecto 25) y `cursor` (opaco, del `next_cursor`). El orden por
defecto es del más nuevo al más antiguo por `created_at`. No hay offsets ni números de página: los
cursores son estables mientras se están emitiendo documentos nuevos.

### Idempotencia [#idempotencia]

Todo `POST` acepta un header `Idempotency-Key`. Usa un UUID nuevo por operación lógica y reúsalo en
los reintentos.

```bash
-H "Idempotency-Key: 0f9f3f4e-6a3d-4f0a-9d70-9b5f19d4b0e6"
```

* Las llaves tienen alcance `(cuenta, empresa, endpoint)` y se retienen **24 horas**.
* Un replay devuelve el código y el cuerpo **originales**, más `Facturia-Idempotent-Replay: true`.
* Misma llave con cuerpo distinto → `409 idempotency_key_reused`.
* Un request todavía en vuelo bajo la misma llave → `409 idempotency_in_progress`; reintenta en un
  momento.
* «En vuelo» es un **arriendo, no una bandera**: la toma se respeta 120 segundos y después se
  considera abandonada. Si el proceso que atendía tu request se muere — un deploy, una máquina
  reemplazada — el siguiente reintento con la misma llave **retoma** la operación en vez de chocar
  con un 409 por el resto de las 24 horas. El reintento tiene que traer el mismo cuerpo: uno
  distinto, dentro de la ventana de retención, sigue siendo `409 idempotency_key_reused`.
* Los requests que fallan con 4xx **no** queman la llave (arregla y reintenta con la misma). Los
  fallos 5xx son seguros de reintentar con la misma llave.

Debajo de eso vive una garantía más fuerte: el motor de emisión sostiene una restricción única sobre
`(RUT de empresa, tipo_dte, folio)`. Un folio nunca se emite dos veces, y una vez asignado a un
documento, cualquier reintento de esa misma emisión lógica reutiliza el folio asignado en vez de
quemar uno nuevo. Eso es lo que de verdad te protege del modo de falla caro — una factura duplicada
con un folio botado — y se sostiene aunque olvides el header.

### Tu propio identificador [#tu-propio-identificador]

```bash
curl "https://api.facturia.cl/v1/documents?external_id=order-8842" \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA"
```

`external_id` es único por `(empresa, external_id)`; una segunda emisión con el mismo valor devuelve
`409 external_id_exists` con el id del documento existente en `error.existing_id`.

### Versionado [#versionado]

* El contrato es el &#x2A;*prefijo `/v1`**. Los cambios incompatibles van a `/v2`; `/v1` no se reforma en
  silencio.
* Dentro de `/v1` evolucionamos de forma **aditiva**: endpoints nuevos, campos de request opcionales
  nuevos, campos de respuesta nuevos, valores de enum nuevos, tipos de evento nuevos.
* Por lo tanto tu cliente debe: ignorar campos de respuesta desconocidos, tolerar valores de enum
  desconocidos (mapéalos a una rama por defecto en vez de lanzar) e ignorar tipos de webhook
  desconocidos. Un cliente que se cae con un campo desconocido tiene un bug, y esa es la única regla
  de compatibilidad que te pedimos.
* Las eliminaciones y los cambios de comportamiento tienen **12 meses** de aviso, un header `Sunset`
  en los endpoints afectados y una entrada en el changelog.
* **No hay header de versión fechada en la v1.** El prefijo de ruta es todo el contrato, lo que
  mantiene el modelo mental en una sola pieza móvil.

- [El sandbox a fondo](/docs/empezar/sandbox): Determinismo, cómo forzar un rechazo y los test helpers.

- [De sandbox a producción](/docs/empezar/produccion): Los dos carriles, `origen`, el mandato y los pasos que hace una persona en el SII.

- [Autenticación](/docs/autenticacion): Scopes, listas blancas, rotación y el arranque en frío.
