Desarrolladores
Emite tu primer documento en minutos
Una API REST con JSON, idempotencia obligatoria y errores con código estable. Esta guía te lleva desde la llave hasta el webhook de aceptación.
URL basehttps://api.indefac.com/api/v1
Antes de empezar
La API pública vive bajo /api/v1. Toda respuesta exitosa trae el resultado en data; todo error trae error con un código estable.
- Ambientes. Cada empresa tiene certificación y producción. La llave de API fija la empresa y el ambiente: no hay que indicarlos en cada llamada.
- Formato. JSON en UTF-8, fechas ISO 8601 e instantes en UTC.
- Límites. 300 solicitudes por minuto por llave y 20 emisiones por segundo por empresa. Al superarlos, la API responde 429 con la cabecera
Retry-After. - Montos. Tu sistema envía los totales calculados; IndeFac los recalcula y los compara. Una diferencia mayor a $1 se rechaza con el detalle de lo esperado.
1. Crea una llave de API
Las llaves se crean en la aplicación, en Ajustes → Llaves de API, y solo las puede crear el administrador de la empresa.
- Elige el ambiente: parte por certificación.
- Marca los alcances que tu sistema necesita, por ejemplo
documents:writeydocuments:read. - Copia la llave: se muestra una sola vez. Tiene la forma
ifk_<prefijo>_<secreto>. - Envíala en cada llamada en la cabecera
Authorization: Bearer.
Alcances disponibles
Comprueba la llave
curl https://api.indefac.com/api/v1/me \
-H "Authorization: Bearer $INDEFAC_API_KEY"{
"data": {
"tenant": { "rut": "76123456-7", "name": "Comercial Ejemplo SpA" },
"environment": "certification",
"scopes": ["documents:write", "documents:read"],
"plan": "essential"
}
}2. Emite un documento
Una sola llamada valida, asigna folio, timbra y firma. La respuesta llega de inmediato; el envío al SII sigue en segundo plano.
curl -X POST https://api.indefac.com/api/v1/documents \
-H "Authorization: Bearer $INDEFAC_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: FAC-2026-000123" \
-d '{
"externalId": "FAC-2026-000123",
"type": 33,
"issueDate": "2026-10-13",
"receiver": {
"rut": "76184649-3",
"name": "COMERCIAL LOS AROMOS SPA",
"businessLine": "Venta al por mayor",
"address": "Av. Providencia 1234",
"commune": "Providencia",
"city": "Santiago",
"email": "[email protected]"
},
"payment": { "form": 2, "dueDate": "2026-11-12" },
"lines": [
{
"number": 1,
"name": "Servicio mensual",
"quantity": 1,
"unit": "UN",
"unitPrice": 160283,
"amount": 160283
}
],
"totals": { "net": 160283, "exempt": 0, "vat": 30454, "total": 190737 },
"sendEmail": true
}'using System.Net.Http.Headers;
using System.Net.Http.Json;
var http = new HttpClient { BaseAddress = new Uri("https://api.indefac.com") };
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("INDEFAC_API_KEY"));
var document = new
{
externalId = "FAC-2026-000123",
type = 33,
issueDate = "2026-10-13",
receiver = new
{
rut = "76184649-3",
name = "COMERCIAL LOS AROMOS SPA",
businessLine = "Venta al por mayor",
address = "Av. Providencia 1234",
commune = "Providencia",
city = "Santiago",
email = "[email protected]",
},
payment = new { form = 2, dueDate = "2026-11-12" },
lines = new[]
{
new { number = 1, name = "Servicio mensual", quantity = 1, unit = "UN", unitPrice = 160283, amount = 160283 },
},
totals = new { net = 160283, exempt = 0, vat = 30454, total = 190737 },
sendEmail = true,
};
using var request = new HttpRequestMessage(HttpMethod.Post, "/api/v1/documents")
{
Content = JsonContent.Create(document),
};
request.Headers.Add("Idempotency-Key", "FAC-2026-000123");
using var response = await http.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
// body trae { "error": { "code", "message", "details" } }
throw new InvalidOperationException($"IndeFac respondió {(int)response.StatusCode}: {body}");
}
Console.WriteLine(body); // { "data": { "id", "folio", "status", "links", ... } }Respuesta
{
"data": {
"id": "0199d2b4-5c1e-7a52-9a53-3f2f7f1f6c10",
"type": 33,
"folio": 1284,
"issueDate": "2026-10-13",
"status": "queued",
"ted": "<TED version=\"1.0\">…</TED>",
"pdf417Base64": "iVBORw0KGgoAAAANSUhEUgAA…",
"links": {
"xml": "https://api.indefac.com/api/v1/documents/0199d2b4-5c1e-7a52-9a53-3f2f7f1f6c10/xml",
"pdf": "https://api.indefac.com/api/v1/documents/0199d2b4-5c1e-7a52-9a53-3f2f7f1f6c10/pdf",
"ticket": "https://api.indefac.com/api/v1/documents/0199d2b4-5c1e-7a52-9a53-3f2f7f1f6c10/ticket",
"public": "https://indefac.com/p/8f3c1d…"
},
"totals": { "net": 160283, "exempt": 0, "vat": 30454, "additionalTaxes": 0, "total": 190737 }
}
}Con 201 el documento ya existe, tiene folio y su PDF está listo. El campo status avanza después: queued, sent, accepted, accepted_with_remarks o rejected.
3. Idempotencia
Toda emisión debe poder repetirse sin riesgo. Identifica cada documento con el campo externalId de tu sistema o con la cabecera Idempotency-Key (hasta 128 caracteres).
- Misma clave, mismo cuerpo: la API responde
200con el documento ya emitido. No se usa otro folio. - Misma clave, otro cuerpo: la API responde
409con el códigoDUPLICATE_IDEMPOTENCY_KEY_MISMATCH. - Sin respuesta por un corte de red: repite la llamada tal cual. Es exactamente el caso para el que existe.
HTTP/1.1 409 Conflict
Content-Type: application/json; charset=utf-8
{
"error": {
"code": "DUPLICATE_IDEMPOTENCY_KEY_MISMATCH",
"message": "La clave FAC-2026-000123 ya se usó con otro contenido."
}
}4. Consulta el estado
El estado ante el SII y ante el receptor se consulta por documento. Devuelve también la lista cronológica de eventos.
curl https://api.indefac.com/api/v1/documents/0199d2b4-5c1e-7a52-9a53-3f2f7f1f6c10/status \
-H "Authorization: Bearer $INDEFAC_API_KEY"{
"data": {
"status": "accepted",
"sii": { "trackId": "7841236590", "status": "accepted", "checkedAt": "2026-10-13T14:02:11Z" },
"receiver": { "status": "pending" },
"events": [
{ "type": "issued", "occurredAt": "2026-10-13T14:01:07Z" },
{ "type": "sent", "occurredAt": "2026-10-13T14:01:12Z" },
{ "type": "accepted", "occurredAt": "2026-10-13T14:02:11Z" }
]
}
}No hace falta consultar en un ciclo: suscribe un webhook y la API te avisará cuando el estado cambie.
5. Recibe webhooks
Registra una dirección de tu sistema y elige los eventos. IndeFac envía un POST con el evento y firma el cuerpo con el secreto del webhook.
curl -X POST https://api.indefac.com/api/v1/webhooks \
-H "Authorization: Bearer $INDEFAC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://erp.miempresa.cl/indefac/eventos",
"events": ["document.accepted", "document.rejected", "received.new"]
}'Cabeceras de cada entrega
X-IndeFac-Event | Nombre del evento, por ejemplo document.accepted. |
|---|---|
X-IndeFac-Event-Id | Identificador del evento, igual en cada reintento y reenvío (también va en el cuerpo como eventId). Úsalo para descartar repetidos. |
X-IndeFac-Delivery | Identificador de la entrega. Cambia si pides un reenvío. |
X-IndeFac-Signature | t=<unix>,v1=<hex>: v1 es el HMAC-SHA256 de <t>. seguido del cuerpo, con el secreto. Rechaza firmas con más de 5 minutos de diferencia. |
POST /indefac/eventos HTTP/1.1
X-IndeFac-Event: document.accepted
X-IndeFac-Event-Id: 0199d2b5-0f00-7b3e-9a11-2c4d6e8f0a12
X-IndeFac-Delivery: 0199d2b5-11aa-7c4e-8f0b-6a1f0c9b2d77
X-IndeFac-Signature: t=1792160531,v1=5f8a1c…
{
"eventId": "0199d2b5-0f00-7b3e-9a11-2c4d6e8f0a12",
"event": "document.accepted",
"occurredAt": "2026-10-13T14:02:11Z",
"tenantRut": "76123456-7",
"environment": "production",
"data": { "id": "0199d2b4-5c1e-7a52-9a53-3f2f7f1f6c10", "type": 33, "folio": 1284, "externalId": "FAC-2026-000123" }
}Verifica la firma
using System.Security.Cryptography;
using System.Text;
// X-IndeFac-Signature: t=<unix>,v1=<hex> con v1 = HMAC-SHA256(secreto, "<t>." + cuerpo)
static bool IsValidSignature(string rawBody, string signatureHeader, string secret)
{
var parts = signatureHeader.Split(',').Select(p => p.Split('=', 2)).Where(p => p.Length == 2)
.ToDictionary(p => p[0].Trim(), p => p[1].Trim());
if (!parts.TryGetValue("t", out var t) || !parts.TryGetValue("v1", out var v1)) return false;
// Firmas de más de 5 minutos no valen: una entrega capturada no se puede repetir después.
var age = DateTimeOffset.UtcNow - DateTimeOffset.FromUnixTimeSeconds(long.Parse(t));
if (age.Duration() > TimeSpan.FromMinutes(5)) return false;
var hash = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes(t + "." + rawBody));
var expected = Convert.ToHexString(hash).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(v1.ToLowerInvariant()));
}Si tu sistema no responde con un código 2xx, la entrega se reintenta al minuto, a los 5 y 30 minutos, a las 2 y a las 12 horas. Después queda marcada como fallida y se avisa al administrador. Puedes ver las entregas con `GET /api/v1/webhooks/{id}/deliveries` y pedir un reenvío con `POST …/deliveries/{deliveryId}/redeliver` (hasta 60 por hora).
Eventos
6. Errores
Todo error tiene la misma forma. El campo code es estable y es el que tu sistema debe leer; message es para personas.
{
"error": {
"code": "VALIDATION",
"message": "El documento tiene campos que no cumplen el formato.",
"details": [
{ "field": "receiver.rut", "message": "El dígito verificador no corresponde." },
{ "field": "lines[0].name", "message": "Máximo 80 caracteres." }
]
}
}| Código HTTP | Código | Qué significa |
|---|---|---|
| 400 | VALIDATION | Un campo no cumple el formato del SII. details trae la lista de campos. |
| 401 | UNAUTHENTICATED | Falta la llave o no es válida. |
| 403 | FORBIDDEN | La llave no tiene el alcance que la operación requiere. |
| 409 | DUPLICATE_IDEMPOTENCY_KEY_MISMATCH | La clave de idempotencia ya se usó con otro contenido. |
| 422 | TOTALS_MISMATCH | Los totales enviados no coinciden con los recalculados. |
| 422 | FOLIO_EXHAUSTED | No quedan folios para ese tipo de documento. |
| 422 | CERTIFICATE_EXPIRED | El certificado digital de la empresa venció. |
| 422 | PLAN_LIMIT | Se alcanzó el límite del plan y el plan no admite documentos adicionales. |
| 422 | PLAN_FEATURE | La función no está incluida en el plan de la empresa. |
| 422 | LEGAL_ACCEPTANCE_REQUIRED | Falta que el administrador acepte un texto legal de empresa. |
| 429 | RATE_LIMITED | Se superó el límite de solicitudes. Espera lo que indica Retry-After. |
| 500 | INTERNAL | Error de la plataforma. Repetir la llamada es seguro gracias a la idempotencia. |
SDK y colección de Postman
Si tu sistema está en .NET o en Node.js no necesitas armar las llamadas a mano: los clientes oficiales envuelven la API con idempotencia, reintentos y verificación de la firma de los webhooks.
IndeFac.Client es el paquete de NuGet para .NET 8 y .NET 10, sin dependencias.
Instalación
dotnet add package IndeFac.ClientVersión 0.1.0: se incluye en el repositorio del servicio (sdk/dotnet). La publicación en nuget.org está próxima.
Ejemplo de uso
using IndeFac.Client;
var http = new HttpClient { BaseAddress = new Uri("https://api.indefac.com/") };
var indefac = new IndeFacClient(http, new IndeFacClientOptions
{
ApiKey = Environment.GetEnvironmentVariable("INDEFAC_API_KEY")!,
});
var emitted = await indefac.Documents.IssueAsync(new DocumentRequest
{
Type = 33,
ExternalId = "VENTA-000123", // tu identificador: es también la llave de idempotencia
Receiver = new Party
{
Rut = "76192083-9", Name = "Cliente SpA", BusinessLine = "Comercio",
Address = "Av. Siempre Viva 123", Commune = "Santiago", City = "Santiago",
},
Payment = new PaymentInfo { Form = 2 },
Lines = [new DocumentLine { Name = "Servicio mensual", Quantity = 1, UnitPrice = 100000 }],
});
Console.WriteLine($"Folio {emitted.Data.Folio} · {(emitted.Status == 201 ? "emitido" : "ya existía")}");
var pdf = await indefac.Documents.PdfAsync(emitted.Data.Id);
await File.WriteAllBytesAsync(pdf.FileName ?? "documento.pdf", pdf.Content);@indefac/client es el paquete de npm para Node.js 20 o superior. TypeScript estricto, solo ESM (import) y sin dependencias de ejecución.
Instalación
npm install @indefac/clientVersión 0.1.0: se incluye en el repositorio del servicio (sdk/node). La publicación en npm está próxima.
Ejemplo de uso
import { writeFile } from 'node:fs/promises';
import { IndeFacClient } from '@indefac/client';
const indefac = new IndeFacClient({
baseUrl: 'https://api.indefac.com', // origen de la API, sin /api/v1
apiKey: process.env.INDEFAC_API_KEY!, // ifk_<prefijo>_<secreto>; fija empresa y ambiente
});
const emitted = await indefac.documents.issue(
{
type: 33, // factura electrónica
receiver: {
rut: '76192083-9',
name: 'Comercial Los Andes SpA',
businessLine: 'Venta al por mayor de artículos de ferretería',
address: 'Av. Providencia 1234',
commune: 'Providencia',
city: 'Santiago',
email: '[email protected]',
},
lines: [{ name: 'Servicio de mantención mensual', quantity: 1, unitPrice: 100000 }],
},
{ idempotencyKey: 'pedido-1001' },
);
console.log(emitted.status === 201 ? 'emitido' : 'ya existía', emitted.data.folio);
const pdf = await indefac.documents.pdf(emitted.data.id); // data es un Uint8Array
await writeFile('factura.pdf', pdf.data);- Emisión idempotente. El cliente siempre envía una llave de idempotencia: la que le pases, el
externalIddel documento o una generada para esa llamada. Un reintento nunca emite dos veces. - Reintentos con criterio. Ante
429respetaRetry-After; ante502,503,504o una falla de red repite solo lo que es seguro repetir. - Errores con código estable. Cada error trae
code, el detalle por campo y untraceIdpara entregar a soporte. - Webhooks. Ambos clientes verifican la cabecera
X-IndeFac-Signaturesobre el cuerpo crudo de la entrega.
Prueba primero en el sandbox. Con una llave de certificación nada tiene valor tributario: es el ambiente para probar la integración. En el panel, entra a Integraciones y abre la pestaña Sandbox.
Colección de Postman
La colección trae todas las operaciones de /api/v1, generada desde la misma definición OpenAPI que la referencia. Impórtala en Postman y define las variables baseUrl y apiKey (tu llave ifk_…).
Referencia de la API
Todos los endpoints, parámetros y esquemas, generados desde la definición OpenAPI que publica la propia API.
Cargando la referencia…