Documentación de FactuSync API
API REST para emitir comprobantes electrónicos autorizados por el SRI Ecuador. Firma XAdES-BES, cola con reintentos, RIDE PDF y webhooks incluidos.
Autenticación
Todas las solicitudes deben incluir el header X-API-Key: <API_KEY>.
Las API Keys se gestionan desde el dashboard de FactuSync.
documents:write— Crear documentosdocuments:emit— Emitir al SRIdocuments:read— Consultar documentos y RIDEdocuments:ride— Descargar RIDE PDFcancellations:write— Solicitar anulacionescancellations:approve— Aprobar anulacionesvalidation:read— Validar RUCqueue:read— Consultar cola de emisiónqueue:retry— Reintentar jobs fallidos de la colawebhooks:manage— Gestionar webhookssignature:manage— Gestionar la firma electrónica (.p12): subir, rotar y consultar estado del certificado
Ambientes
Las claves de API tienen dos prefijos:
fsk_live_...→ Producción — Emite documentos reales al SRIfsk_test_...→ Sandbox — Ambiente de certificación del SRI
URL base
https://api.factusync.jipsoft.com/
Flujo de emisión
DRAFTAUTHORIZED / REJECTEDCiclo de vida del documento
Estados por los que pasa un comprobante desde su creación hasta la autorización del SRI.
| Estado | Significado |
|---|---|
DRAFT | Creado, aún no enviado al SRI. El payload se puede editar. |
PROCESSING | El trabajo de emisión se está ejecutando (armado de XML y firma). |
VALIDATED | El payload pasó la validación de esquema. |
SIGNED | Se aplicó la firma XAdES-BES. |
SENT_TO_SRI | Enviado al servicio del SRI, esperando respuesta. |
AUTHORIZED | El SRI autorizó el comprobante. La clave de acceso es definitiva. |
REJECTED | El SRI rechazó el comprobante. Se puede reintentar tras corregir el payload. |
CANCELLED | La anulación del comprobante fue confirmada por el SRI. |
Flujo de gestión de firma (.p12)
signature:managePOST /integration/signatureGET /integration/signatureEndpoints
Base: https://api.factusync.jipsoft.com — Toca un endpoint para ver el scope requerido y un ejemplo de request/response.
Documentos
/withholdingsCrear comprobante de retencióndocuments:write/waybillsCrear guía de remisióndocuments:write/purchase-settlementsCrear liquidación de compradocuments:write/documentsListar documentos (filtros: type, status, period)documents:read/documents/:id/retryReintentar emisión tras rechazo del SRI (solo REJECTED; mismo scope que emit)documents:emit/documents/:id/rideDescargar RIDE PDFdocuments:rideValidación
Cola de emisión
/queue/statusEstado de la cola (pendientes, en proceso, DLQ)queue:readWebhooks
/webhooksListar endpoints registradoswebhooks:manage/webhooksRegistrar endpointwebhooks:manage/webhooks/:idDesactivar endpointwebhooks:manage/webhooks/:id/deliveriesHistorial de entregaswebhooks:manageFirma electrónica
/integration/signature/historyListar el historial de certificados (solo metadatos)signature:manageConfiguración del tenant
/configObtener configuración de la empresa/configActualizar datos de la empresa/credentialsSubir certificado P12Catálogos SRI
/catalogs/iva-ratesTarifas IVA vigentes/catalogs/withholdingsPorcentajes de retención/catalogs/payment-methodsFormas de pago/catalogs/document-typesTipos de comprobanteEjemplos de código
Crear y emitir una factura (cURL)
# 1. Create draft
curl -X POST https://api.factusync.jipsoft.com/documents \
-H "X-API-Key: fsk_live_eyJ..." \
-H "Content-Type: application/json" \
-d '{
"documentTypeCode": "01",
"establishment": "001",
"emissionPoint": "001",
"payload": {
"recipientIdentificationType": "04",
"recipientIdentificationNumber": "0992339411001",
"recipientName": "ACME Corp S.A.",
"items": [
{
"mainCode": "PROD-001",
"description": "Tornillo hexagonal 1/4 x 2 galvanizado",
"quantity": 100,
"unitPrice": 0.15,
"discount": 0,
"taxes": [
{ "taxCode": "2", "taxRateCode": "4", "taxableBase": 15.00, "taxRate": 15, "taxAmount": 2.25 }
]
}
],
"totals": { "subtotalTaxable": 15.00, "totalTax": 2.25, "grandTotal": 17.25 },
"payments": [ { "paymentMethodCode": "01", "amount": 17.25 } ]
},
"externalReference": "POS-SALE-12345"
}'
# 2. Emit to SRI (document must be DRAFT; if REJECTED use POST .../retry first)
curl -X POST https://api.factusync.jipsoft.com/documents/{id}/emit \
-H "X-API-Key: fsk_live_eyJ..."
# 3. Check status
curl https://api.factusync.jipsoft.com/documents/{id} \
-H "X-API-Key: fsk_live_eyJ..."Con Node.js / TypeScript
import axios from 'axios';
const client = axios.create({
baseURL: 'https://api.factusync.jipsoft.com',
headers: { 'X-API-Key': 'fsk_live_eyJ...' },
});
// Create draft invoice
const { data: doc } = await client.post('/documents', {
documentTypeCode: '01',
establishment: '001',
emissionPoint: '001',
payload: {
recipientIdentificationType: '04',
recipientIdentificationNumber: '0992339411001',
recipientName: 'ACME Corp S.A.',
items: [
{
mainCode: 'PROD-001',
description: 'Tornillo hexagonal 1/4 x 2 galvanizado',
quantity: 100,
unitPrice: 0.15,
discount: 0,
taxes: [{ taxCode: '2', taxRateCode: '4', taxableBase: 15.0, taxRate: 15, taxAmount: 2.25 }],
},
],
totals: { subtotalTaxable: 15.0, totalTax: 2.25, grandTotal: 17.25 },
payments: [{ paymentMethodCode: '01', amount: 17.25 }],
},
externalReference: 'POS-SALE-12345',
});
// Emit (DRAFT only). After REJECTED: await client.post(`/documents/${doc.id}/retry`)
await client.post(`/documents/${doc.id}/emit`);Verificar estado y obtener RIDE
// Poll until authorized (with timeout)
async function waitForAuth(id: string, maxAttempts = 20): Promise<string> {
for (let i = 0; i < maxAttempts; i++) {
const { data } = await client.get(`/documents/${id}`);
if (data.status === 'AUTHORIZED') return data.accessKey;
if (data.status === 'REJECTED') throw new Error('Rejected by SRI'); // fix payload, POST /documents/:id/retry, then poll again
await new Promise(r => setTimeout(r, 3000));
}
throw new Error('Timeout waiting for authorization');
}
// Download RIDE
const rideResponse = await client.get(`/documents/${id}/ride`, {
responseType: 'arraybuffer',
});
fs.writeFileSync('ride.pdf', rideResponse.data);Códigos de error
Los errores se devuelven con el envoltorio { code, message }. El campo code es estable; úsalo para el manejo programático.
Estados HTTP comunes: 401 UNAUTHORIZED (API Key faltante o inválida), 403 FORBIDDEN (falta el scope), 404 NOT_FOUND, 402 para límites de plan o billing, 409 INVALID_STATE_TRANSITION.
| Código | Categoría | Mensaje |
|---|---|---|
BILLING_OR_PLAN_LIMIT | BILLING | This operation is blocked by billing or plan limits. |
BRANCH_LIMIT_REACHED | BILLING | The branch limit for your plan has been reached. |
CONFLICT | VALIDATION | The request could not be completed because it conflicts with the current data. |
DOCUMENT_QUOTA_EXCEEDED | BILLING | The document quota for your plan has been reached. |
DUPLICATE_WAYBILL_SUPPORT_INVOICE_ACROSS_RECIPIENTS | VALIDATION | The same supporting invoice cannot be referenced for more than one delivery recipient on this waybill. |
FORBIDDEN | HTTP_SECURITY | You do not have permission for this operation. |
INTERNAL_ERROR | SYSTEM | An unexpected error occurred. |
INVALID_P12_FILE | VALIDATION | The uploaded file is not an acceptable P12 certificate. |
INVALID_PDF_HEADER | VALIDATION | The uploaded file is not a valid PDF. |
INVALID_SRI_CATALOG_CODE | VALIDATION | The document uses a tax catalog code that is not valid or not recognized. |
INVALID_STATE_TRANSITION | VALIDATION | This action is not allowed in the current state. |
KMS_DECRYPT_FAILED | SIGNING | Credential decryption failed. |
NOT_FOUND | HTTP_SECURITY | The requested resource was not found. |
P12_EXPIRED | SIGNING | The signing certificate is no longer valid. |
P12_LOAD_FAILED | SIGNING | Signing credentials could not be loaded. |
PDF_CONTAINS_ACTIVE_CONTENT | VALIDATION | The uploaded PDF contains active content (scripts or embedded files) and was rejected for security reasons. |
PDF_CORRUPTED | VALIDATION | The uploaded PDF could not be read. The file may be corrupted. |
PDF_SIGNER_NOT_AVAILABLE | BILLING | The PDF signer is not available on the current plan. |
PDF_TIMESTAMP_FAILED | SIGNING | The PDF was signed but the trusted timestamp could not be applied. |
QUEUE_JOB_NOT_FOUND | SYSTEM | The requested background job was not found. |
RATE_LIMIT_EXCEEDED | BILLING | Too many requests. Try again later. |
RUC_LOOKUP_FAILED | RUC | Taxpayer lookup failed. |
RUC_LOOKUP_TIMEOUT | RUC | Taxpayer lookup timed out. |
SIGNATURE_ROTATION_FAILED | SIGNING | We couldn't complete the signature rotation, please try again later. |
SIGNING_FAILED | SIGNING | The document could not be signed. |
SRI_AUTHORIZATION_FAILED | SRI | Tax authority authorization step failed. |
SRI_RECEPTION_FAILED | SRI | Tax authority reception step failed. |
SRI_TIMEOUT | SRI | The tax authority did not respond in time. |
STORAGE_LIMIT_REACHED | BILLING | The storage limit for your plan has been reached. |
SUBSCRIPTION_READ_ONLY | BILLING | Write operations are not allowed for the current subscription. |
UNAUTHORIZED | HTTP_SECURITY | Authentication is required or has failed. |
VALIDATION_FAILED | VALIDATION | The request could not be validated. |
WAREHOUSE_LIMIT_REACHED | BILLING | The warehouse limit for your plan has been reached. |
Webhooks
FactuSync puede enviar eventos HTTP a tu servidor cuando el estado de un documento cambia.
Eventos disponibles
document.authorized— El SRI autorizó el comprobantedocument.rejected— El SRI rechazó el comprobantedocument.cancelled— La anulación del comprobante fue confirmada por el SRIdocument.failed— El comprobante pasó a Dead-Letter Queue tras 5 reintentosdocument.notification.resent— Se reenvió la notificación del comprobante
Registrar un endpoint
curl -X POST https://api.factusync.jipsoft.com/webhooks \
-H "X-API-Key: fsk_live_eyJ..." \
-d '{
"url": "https://your-system.com/webhooks/factusync",
"events": ["document.authorized", "document.rejected"],
"secret": "s3cr3t_very_long_32_bytes_minimum"
}'Verificar la firma
Cada llamada incluye el header X-FactuSync-Signature: sha256=<hmac>.
import { createHmac } from 'crypto';
function verifySignature(body: string, secret: string, header: string): boolean {
const expected = 'sha256=' + createHmac('sha256', secret).update(body).digest('hex');
return expected === header;
}
// Express.js
app.post('/webhooks/factusync', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-factusync-signature'] as string;
if (!verifySignature(req.body.toString(), process.env.WEBHOOK_SECRET!, sig)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body.toString());
console.log('Event:', event.event, event.documentId);
res.sendStatus(200);
});Reintentos automáticos
- Intento 1 — Inmediato
- Intento 2 — 30 segundos
- Intento 3 — 5 minutos
- Intento 4 — 30 minutos
