Introducción
SharkPOS API recibe solicitudes JSON desde tu sistema (POS, ERP, plataforma SaaS) y las convierte en documentos tributarios electrónicos XML, firmados digitalmente y transmitidos al SII. Tú envías datos de negocio simples — nunca construyes XML, firmas ni te conectas directamente al SII.
La API es multiempresa: cada RUT emisor (tu cliente final) es un tenant aislado, con su propio certificado digital, sus propios folios (CAF) y su propia credencial de acceso.
Autenticación
Todo endpoint privado exige una API Key por cabecera Authorization. La key se emite una vez por RUT emisor (tenant) y tiene el formato prefijo(8) + secreto.
Authorization: Bearer <API_KEY>
Content-Type: application/jsonLa key nunca identifica una empresa por sí sola en la URL ni en el body — el RUT emisor se resuelve siempre desde la credencial, nunca lo envías tú. Esto evita que un integrador pueda emitir a nombre de otro RUT por error.
| Código HTTP | code | Causa |
|---|---|---|
| 401 | AUTHENTICATION_REQUIRED | Falta la cabecera Authorization: Bearer. |
| 401 | INVALID_API_KEY | La key no existe, está mal formada o inactiva. |
| 403 | API_KEY_EXPIRED | La credencial venció (fecha de expiración). |
| 403 | API_ACCESS_DISABLED | El tenant fue suspendido o su contrato venció. |
Producción y sandbox
Producción emite documentos reales y consume folios/CAF reales — está conectada al SII (palena.sii.cl). El sandbox de SharkPOS nunca toca el SII real ni consume folios de verdad, así que puedes integrar y romper cosas con libertad.
| Ambiente | Base URL | Transmite al SII |
|---|---|---|
| Producción | https://api.sharkpos.cl | Sí |
| Sandbox | https://sandbox.sharkpos.cl | No — todo simulado |
El contrato (endpoints, campos, estados) es idéntico entre ambientes. Lo único que cambia es la base URL/host y que el sandbox nunca transmite al SII real.
Certificado y CAF de prueba
El onboarding (ver API de partner) exige subir un certificado digital y un CAF, incluso en sandbox. No nos pidas uno — genera el tuyo en 10 segundos con este script (autofirmado, nunca se valida contra el SII real). Usa tu propio RUT de prueba, no reutilices uno de otro integrador: el CAF queda amarrado a ese RUT, así que compartir el mismo archivo entre varios equipos hace que todos terminen sobre el mismo negocio sintético y el mismo pool de folios.
"""
Genera un certificado PKCS#12 y un CAF sintéticos para probar el onboarding
del sandbox de SharkPOS — autofirmados, nunca tocan el SII real.
Requiere: pip install cryptography
Uso: python generate_sandbox_kit.py <rut> <password>
Ejemplo: python generate_sandbox_kit.py 12345678-5 mi-password
"""
import sys, datetime
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.serialization import pkcs12
from cryptography.x509.oid import NameOID
rut, password = sys.argv[1], sys.argv[2]
# Certificado autofirmado. Su RUT debe coincidir con "signer_rut" en tu
# request de onboarding — puede ser cualquier RUT válido, no tiene que ser
# el mismo que el del negocio.
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
now = datetime.datetime.now(datetime.timezone.utc)
name = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "CL"),
x509.NameAttribute(NameOID.COMMON_NAME, "Firmante Sandbox"),
x509.NameAttribute(NameOID.SERIAL_NUMBER, rut),
])
cert = (x509.CertificateBuilder().subject_name(name).issuer_name(name)
.public_key(key.public_key()).serial_number(x509.random_serial_number())
.not_valid_before(now).not_valid_after(now + datetime.timedelta(days=730))
.sign(key, hashes.SHA256()))
pfx = pkcs12.serialize_key_and_certificates(
name=b"Firmante Sandbox", key=key, cert=cert, cas=None,
encryption_algorithm=serialization.BestAvailableEncryption(password.encode()))
open("certificado.pfx", "wb").write(pfx)
# CAF sintético tipo 39. <RE> debe coincidir con el RUT que das de alta
# ("company_rut" en /api/v1/onboarding/complete/).
caf_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
pem = caf_key.private_bytes(serialization.Encoding.PEM,
serialization.PrivateFormat.TraditionalOpenSSL, serialization.NoEncryption()).decode()
xml = f'''<?xml version="1.0" encoding="ISO-8859-1"?>
<AUTORIZACION>
<CAF version="1.0"><DA><RE>{rut}</RE><RS>EMPRESA DE PRUEBA SPA</RS><TD>39</TD><RNG><D>1</D><H>100</H></RNG><FA>2026-01-01</FA><RSAPK><M>QUE=</M><E>AQAB</E></RSAPK><IDK>100</IDK></DA><FRMA algoritmo="SHA1withRSA">QUE=</FRMA></CAF>
<RSASK><![CDATA[{pem}]]></RSASK>
</AUTORIZACION>'''
open("caf39.xml", "w", encoding="iso-8859-1").write(xml)
print("Listo: certificado.pfx y caf39.xml")/api/v1/onboarding/complete/ solo recibe CAF de boleta (caf_39 obligatorio, caf_41 opcional) en el alta inicial. Para Factura/Nota de Crédito/Guía de Despacho, genera el CAF cambiando <TD>39</TD> por 33, 61 o 52, y súbelo después con POST /api/v1/partner/tenants/<id>/caf/ (campo caf) sobre el RUT ya dado de alta.
Idempotencia
El endpoint de emisión de boletas exige la cabecera Idempotency-Key (8 a 128 caracteres). Úsala para identificar unívocamente cada venta de tu sistema — nunca el mismo ID de venta con dos payloads distintos.
- Un reintento con la misma
Idempotency-Keyy el mismo payload devuelve el mismo resultado (idempotency_replayed: true), sin duplicar folios. - La misma key con un payload distinto responde
409conIDEMPOTENCY_CONFLICT. - Ante cualquier error de red o timeout, reintenta con la misma key — nunca generes una key nueva para el mismo intento de venta.
/boleta/emit/. Factura, Nota de Crédito y Guía de Despacho todavía no la exigen — si tu integración reintenta esos endpoints, deduplica del lado de tu sistema antes de reenviar.
Límites y cuotas
Cada tenant tiene límites configurables por SharkPOS según su plan:
| Límite | Default | Descripción |
|---|---|---|
requests_per_minute | 60 | Solicitudes por minuto a cualquier endpoint privado. |
emissions_per_minute | 10 | Emisiones (endpoints /emit/) por minuto. |
monthly_emission_limit | 0 (ilimitado) | Cuota mensual dura de documentos emitidos. |
Además hay un límite por IP que protege incluso a solicitudes sin credenciales válidas. Todo límite excedido responde 429 con cabecera Retry-After.
| code | Significado |
|---|---|
RATE_LIMIT_EXCEEDED | Límite general de solicitudes por minuto. |
EMISSION_RATE_LIMIT_EXCEEDED | Límite de emisiones por minuto. |
MONTHLY_QUOTA_EXCEEDED | Cuota mensual de emisiones alcanzada (Retry-After: 86400). |
AUTH_RATE_LIMIT_EXCEEDED | Demasiados intentos con credenciales inválidas desde tu IP. |
Errores comunes y reintentos
Toda respuesta de error trae status: "error" y, en la mayoría de los casos, un code estable que puedes usar para lógica de reintento automática.
| HTTP | code | Qué hacer |
|---|---|---|
| 400 | (validación) | Corrige el payload. No reintentar sin cambios. |
| 403 | EMISSION_NOT_CONTRACTED | Tu plan no incluye este tipo de documento (hoy aplica a Boleta y Factura). Contacta a soporte para activarlo — no reintentar. |
| 409 | FOLIOS_EXHAUSTED | Sin folios disponibles en el CAF activo. Avisa para renovar CAF — no reintentar. |
| 409 | IDEMPOTENCY_CONFLICT | Misma key, payload distinto. Revisa tu lógica de venta. |
| 503 | EMISSION_DISABLED | Emisión bloqueada por mantenimiento o configuración. Reintenta con backoff. |
| 503 | SECURITY_SERVICE_UNAVAILABLE | Redis/caché no disponible. Reintenta con backoff. |
| 500 | — | Error interno. La venta puede o no haberse registrado — consulta estado con la misma Idempotency-Key antes de reintentar. |
Una vez firmado el documento, el envío y confirmación ante el SII ocurre de forma asíncrona (workers en cola con reintento automático). Nunca reintentes creando una venta nueva por una boleta que quedó en queued o sent — consulta su estado.
Boleta Electrónica (39 / 41)
Emite una boleta afecta (document_type: 39) o exenta (41). El emisor se resuelve desde la API Key — nunca se envía el RUT emisor en el body.
// Headers
Authorization: Bearer <API_KEY>
Idempotency-Key: venta-pos-20260713-000184
Content-Type: application/json
// Body
{
"document_type": 39,
"amount_total": 15000,
"medio_pago": 3,
"receptor_rut": "66666666-6",
"receptor_name": "CONSUMIDOR FINAL",
"items": [
{ "name": "Venta de mercadería", "qty": 1, "price": 15000 }
]
}
// 201 Created
{
"status": "success",
"dte_id": "6c94d40a-97e5-48a1-ae2e-f63c4aa90b72",
"folio": 105,
"document_type": 39,
"dte_status": "SIGNED",
"idempotency_replayed": false,
"pdf_url": "/api/v1/dte/boleta/6c94.../pdf/",
"xml_data": "<XML_FIRMADO_BASE64>"
}Campos del body
| Campo | Tipo | Notas |
|---|---|---|
document_type | int | 39 (afecta) o 41 (exenta). Default 39. |
amount_total | int | Monto total en CLP, número, no string. |
medio_pago | int | Opcional. Catálogo SII: 1 efectivo, 2 cheque, 3 tarjeta de crédito, 4 tarjeta de débito, 5 otro (transferencia, etc.). Omítelo si no aplica. |
receptor_rut | string | Con o sin puntos, con guion. Se valida el dígito verificador. |
receptor_name | string | Usa "CONSUMIDOR FINAL" para venta anónima (RUT 66666666-6). |
items | array | Cada línea: name, qty, price. |
La respuesta trae la boleta ya firmada, pero la aceptación del SII es asíncrona — consulta estado.
Estado de una boleta
Nunca expone el XML de respuesta crudo del SII — entrega un estado público normalizado, estable entre versiones.
{
"status": "ok",
"dte_id": "6c94d40a-...",
"document_type": 39,
"folio": 105,
"dte_status": "ACCEPTED",
"public_status": "accepted",
"code": "SII_ACCEPTED",
"message": "Boleta aceptada por el SII.",
"solution": "",
"is_final": true,
"requires_attention": false,
"track_id": "123456789",
"sii_response_status": "0",
"updated_at": "2026-09-09T14:02:11Z"
}dte_status es el estado interno crudo (útil para soporte); construye tu lógica siempre sobre public_status y is_final, que son estables entre versiones. sii_response_status es el código de respuesta crudo del SII cuando existe, o null mientras no lo haya.
Valores de public_status
| public_status | is_final | Significado |
|---|---|---|
preparing | No | Folio reservado, boleta preparándose. |
queued | No | Firmada, en cola de envío al SII. |
sent | No | Enviada; el SII aún procesa la respuesta. |
accepted | Sí | Aceptada por el SII sin reparos. |
accepted_with_repair | Sí | Aceptada con reparos — revisar en el panel. |
rejected | Sí | Rechazada por el SII. No reemitir la misma venta sin soporte. |
review_required | Sí | Respuesta del SII ambigua — requiere revisión manual de SharkPOS. |
failed | Sí | Falló antes de completar el envío. |
Conserva siempre la Idempotency-Key original de la venta para cualquier reintento — nunca crear otra venta ni usar una key nueva mientras el estado no sea final.
PDF de boleta (formato térmico)
Devuelve el PDF térmico listo para impresora (paper_width: 58 o 80 mm). Solo disponible cuando la boleta está SIGNED, SENT_PENDING, ACCEPTED o ACCEPTED_WITH_REPAIR.
Factura Electrónica (33)
Documento B2B. Requiere datos completos del receptor (giro, dirección, comuna) y bloquea explícitamente el RUT de Consumidor Final (66666666-6).
// Body — campos obligatorios
{
"receptor_rut": "76123456-7",
"receptor_name": "Comercial Ejemplo SpA",
"receptor_giro": "Venta al por menor",
"receptor_direccion": "Av. Siempre Viva 123",
"receptor_comuna": "Providencia",
"item_name": "Servicio de consultoría",
// opcionales
"receptor_ciudad": "Santiago",
"receptor_email": "[email protected]",
"item_qty": 1,
"amount_net": 100000,
"fecha_emision": "2026-09-09",
"auto_send_email": false
}
// 201 Created — envía amount_net O amount_total, no ambos
{
"status": "success",
"folio": 48,
"document_type": 33,
"xml_data": "<XML_FIRMADO_BASE64>",
"pdf_data": "<PDF_BASE64>"
}true y enviaste receptor_email, SharkPOS despacha el documento por correo una vez que el SII lo marca ACCEPTED — no es instantáneo, un worker revisa documentos aceptados pendientes de envío cada 2 minutos. La respuesta del emit no incluye pdf_data en ese caso. Si es false (default), el PDF vuelve embebido en base64 en la misma respuesta — no hay pdf_url como en boletas.
Nota de Crédito Electrónica (61)
Mismo body que Factura, más los campos de referencia obligatorios al documento que corrige:
| Campo | Tipo | Notas |
|---|---|---|
ref_document_type | int | Tipo del documento referenciado (ej. 33). |
ref_folio | int | Folio del documento referenciado. |
ref_date | string | Fecha del documento referenciado, YYYY-MM-DD. |
ref_code | int | 1 anula, 2 corrige texto (monto puede ser 0), 3 corrige montos. |
ref_reason | string | Motivo de la corrección. |
Respuesta: mismo formato que Factura (folio, document_type: 61, xml_data, pdf_data opcional).
Guía de Despacho Electrónica (52)
Mismo body B2B que Factura (bloquea Consumidor Final), más ind_traslado: entero entre 1 y 7 según el motivo del traslado definido por el SII (venta, traslado interno, consignación, etc.).
PDF tamaño carta (Factura / Nota de Crédito)
Representación en tamaño carta para Facturas, Notas de Crédito y Guías de Despacho — a diferencia de boletas, que usan el formato térmico.
Recepción de DTE de proveedores
Registra un DTE que tu proveedor te emitió, para dar cumplimiento a la Ley 19.983 (acuse de recibo). Envía el sobre EnvioDTE completo en base64.
{ "xml_data": "<ENVIODTE_XML_BASE64>" }
// 201 Created
{
"status": "success",
"id": "a1b2c3d4-...",
"supplier_rut": "76123456-7",
"document_type": 33,
"folio": 1024,
"amount_total": 59500
}Reenviar el mismo documento responde 409 — ya fue registrado para ese tenant.
Acuse, Aceptación o Reclamo (Ley 19.983)
Genera y firma la respuesta comercial sobre un documento recibido.
// Body
{
"action": "ACUSE", // ACUSE | ACEPTA | RECLAMO
"cert_password": "••••••••"
}
// 200 OK
{
"status": "success",
"message": "Acuse XML for action 'ACUSE' generated successfully and queued for transmission.",
"id": "a1b2c3d4-...",
"acuse_status": "GENERATED"
}Saldo de folios (CAF)
Consulta de solo lectura — no reserva ni consume folios. Útil para mostrar alertas propias antes de que se agote un rango.
{
"status": "ok",
"document_type": 39,
"total_remaining": 184,
"alert_level": "warning", // ok | notice | warning | urgent | critical | exhausted
"ranges": [{ "folio_start": 1, "folio_end": 500, "remaining": 184 }],
"certificate": {
"valid_to": "2027-03-01T00:00:00Z",
"days_remaining": 173,
"alert_level": "ok"
}
}Umbrales por defecto: 500 (notice), 200 (warning), 100 (urgent), 20 (critical) folios restantes — configurables por SharkPOS.
Health check
Endpoint de monitoreo, protegido por la cabecera X-Health-Check-Token (no tu API Key de tenant). Pensado para tus propios sistemas de uptime, no forma parte del flujo de negocio.
Alta de emisores para plataformas multiempresa
Si tu plataforma opera múltiples RUT emisores (por ejemplo, un SaaS donde cada cliente factura con su propio RUT), tienes una cuenta de partner con panel propio en /partner/, separado del panel interno de SharkPOS. SharkPOS crea tu acceso y define cuántas empresas puedes dar de alta; desde ahí administras todo tú mismo:
- Desde tu panel, indicas el RUT esperado del nuevo negocio y generas un enlace seguro de un solo uso (vence en 30 minutos).
- Le compartes ese enlace al negocio; completa sus datos y sube su certificado digital y su CAF directamente — SharkPOS nunca ve la contraseña del certificado.
- Al completarse, el RUT queda vinculado a tu cuenta automáticamente, junto con su API Key — la ves de inmediato en tu panel, sin pasar por soporte.
- Desde el mismo panel ves el saldo de folios y vigencia del certificado de cada RUT, y puedes cargar un CAF o certificado nuevo tú mismo cuando corresponda.
API de partner (alternativa al panel)
Todo lo del panel /partner/ también existe como API JSON, para que tu backend automatice altas de RUT sin un humano haciendo clic. Se autentica con una API Key de partner (distinta a la API Key de cada RUT) — pídesela a SharkPOS junto con tu cuenta de partner.
El paso 2 (completar los datos del negocio y subir certificado/CAF) también tiene equivalente API — si tu plataforma ya reúne esos datos en tu propia interfaz, no necesitas mandar a nadie a una página de SharkPOS. El onboarding_token del enlace es la única credencial que exige; es el mismo mecanismo que usa el formulario web, no uno nuevo.
Crea el enlace de onboarding de un solo uso para un RUT nuevo (paso 1 del panel).
// Body
{
"expected_rut": "76123456-7",
"external_reference": "tu-id-interno-opcional"
}
// 201 Created
{
"status": "success",
"expected_rut": "76123456-7",
"onboarding_url": "https://api.sharkpos.cl/onboarding/#...",
"expires_at": "2026-09-12T05:31:09Z"
}Lista tus RUT con saldo de folios y vigencia del certificado — equivalente al panel (paso 3 y 4).
{
"status": "ok",
"tenants": [
{
"id": "6c94d40a-97e5-48a1-ae2e-f63c4aa90b72",
"rut": "76123456-7",
"razon_social": "Comercial Ejemplo SpA",
"api_enabled": true,
"boleta_39": { "document_type": 39, "total_remaining": 184, "alert_level": "ok", "ranges": [] },
"boleta_41": { "document_type": 41, "total_remaining": 0, "alert_level": "exhausted", "ranges": [] },
"certificate": { "subject_name": "Firmante Demo", "subject_rut": "22228791-K", "valid_to": "2027-03-01T00:00:00Z", "days_remaining": 173, "alert_level": "ok" }
}
]
}El mismo resumen de un elemento de tenants arriba, para un solo RUT tuyo: {"status": "ok", "tenant": {...}}. 404 si el RUT no es tuyo.
Agrega un rango de folios (campo caf, archivo XML) a un RUT tuyo ya dado de alta.
{ "status": "success", "document_type": 39, "folio_start": 501, "folio_end": 1000 }Reemplaza el certificado digital (campos certificate y certificate_password).
{ "status": "success", "valid_to": "2027-03-01T00:00:00Z" }Errores
| HTTP | code | Causa |
|---|---|---|
| 401 | AUTHENTICATION_REQUIRED | Falta la cabecera Authorization: Bearer. |
| 401 | INVALID_API_KEY | La key de partner no existe, está mal formada o inactiva. |
| 429 | AUTH_RATE_LIMIT_EXCEEDED | Demasiados intentos con credenciales inválidas desde tu IP. |
| 403 | API_KEY_EXPIRED | La credencial de partner venció. |
| 403 | PARTNER_ACCESS_DISABLED | Tu cuenta de partner fue suspendida. |
| 400 | (validación) | RUT inválido, ya asignado a otro partner, o alcanzaste tu max_sub_tenants. |
| 404 | — | El <id> de tenant no existe o no es tuyo — nunca distinguimos ambos casos. |
Comparte el mismo límite general de solicitudes por minuto que el resto de la API (ver Límites y cuotas).
Completar el paso 2 (sin página web)
Estos tres endpoints no usan tu API Key de partner — el onboarding_token del enlace (la parte después del # en onboarding_url) ya es la credencial completa, igual que en el formulario web que reemplazan. Envíalos como multipart/form-data, no JSON, porque incluyen archivos.
Completa el alta inicial de un RUT. Equivalente API de /onboarding/.
// Texto
onboarding_token, company_rut, business_name, business_activity,
address, location_region, commune, city, sii_office,
resolution_number, resolution_date, signer_name, signer_rut,
certificate_password, authorization_confirmed="yes"
// Archivos
certificate // .pfx o .p12, obligatorio
caf_39 // .xml, obligatorio
caf_41 // .xml, opcional
// 201 Created
{
"status": "completed",
"tenant_id": "6c94d40a-97e5-48a1-ae2e-f63c4aa90b72",
"caf_ranges": [{ "document_type": 39, "folio_start": 1, "folio_end": 50 }],
"api_key": "a4f82690hWCh8xr7qJWZcGb-bY1RcdZJnJeAaPNZiL5lqMtw7JY",
"certificate_valid_to": "2027-09-12T14:36:15Z"
}api_key ahora. Es la única vez que viaja en texto plano — se almacena hasheada y no hay forma de recuperarla después. location_region/commune/city deben coincidir exactamente con el catálogo de regiones de Chile (mismo que usa el formulario web).
Agrega un CAF nuevo usando un enlace de renovación. Campos: onboarding_token + archivo caf. Responde el mismo formato que /complete/ sin api_key (la key del tenant no cambia).
Reemplaza el certificado usando un enlace de renovación. Campos: onboarding_token, certificate_password, authorization_confirmed=yes + archivo certificate. Responde certificate_valid_to.
Soporte
¿Dudas de integración, acceso a sandbox o un caso de uso particular? Escríbenos desde el formulario de contacto o por WhatsApp.