FactuSync API Documentation
REST API to emit electronic vouchers authorized by Ecuador's SRI. XAdES-BES signature, retry queue, RIDE PDF and webhooks included.
Authentication
All requests must include the header X-API-Key: <API_KEY>.
API Keys are managed from the FactuSync dashboard.
documents:write— Create documentsdocuments:emit— Emit to SRIdocuments:read— Query documents and RIDEdocuments:ride— Download RIDE PDFcancellations:write— Request cancellationscancellations:approve— Approve cancellationsvalidation:read— Validate RUCqueue:read— Query emission queuequeue:retry— Retry failed queue jobswebhooks:manage— Manage webhookssignature:manage— Manage the electronic signature (.p12): upload, rotate, and check certificate status
Environments
API keys use two prefixes:
fsk_live_...→ Production — Emits real documents to SRIfsk_test_...→ Sandbox — SRI certification environment
Base URL
https://api.factusync.jipsoft.com/
Emission flow
DRAFTAUTHORIZED / REJECTEDDocument lifecycle
The states a document moves through from creation to SRI authorization.
| State | Meaning |
|---|---|
DRAFT | Created, not yet sent to SRI. The payload can be edited. |
PROCESSING | The emission job is running (XML build and signing). |
VALIDATED | The payload passed schema validation. |
SIGNED | The XAdES-BES signature was applied. |
SENT_TO_SRI | Submitted to the SRI web service, awaiting response. |
AUTHORIZED | SRI authorized the document. The access key is final. |
REJECTED | SRI rejected the document. It can be retried after fixing the payload. |
CANCELLED | The document annulment was confirmed by SRI. |
Signature management flow (.p12)
signature:managePOST /integration/signatureGET /integration/signatureEndpoints
Base: https://api.factusync.jipsoft.com — Tap an endpoint to see its required scope and a request/response example.
Documents
/withholdingsCreate withholding receiptdocuments:write/waybillsCreate waybill (guía de remisión)documents:write/purchase-settlementsCreate purchase settlementdocuments:write/documentsList documents (filters: type, status, period)documents:read/documents/:id/retryRetry emission after SRI rejection (REJECTED only; same scope as emit)documents:emit/documents/:id/rideDownload RIDE PDFdocuments:rideValidation
Emission queue
/queue/statusQueue status (pending, processing, DLQ)queue:readWebhooks
/webhooksList registered endpointswebhooks:manage/webhooksRegister endpointwebhooks:manage/webhooks/:idDeactivate endpointwebhooks:manage/webhooks/:id/deliveriesDelivery historywebhooks:manageElectronic signature
/integration/signature/historyList the certificate history (metadata only)signature:manageTenant configuration
/configGet company configuration/configUpdate company data/credentialsUpload P12 certificateSRI Catalogs
/catalogs/iva-ratesCurrent IVA rates/catalogs/withholdingsWithholding percentages/catalogs/payment-methodsPayment methods/catalogs/document-typesDocument typesCode examples
Create and emit an invoice (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..."With 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`);Check status and download 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);Error codes
Errors are returned with the { code, message } envelope. The code field is stable; use it for programmatic handling.
Common HTTP statuses: 401 UNAUTHORIZED (missing or invalid API Key), 403 FORBIDDEN (missing scope), 404 NOT_FOUND, 402 for plan or billing limits, 409 INVALID_STATE_TRANSITION.
| Code | Category | Message |
|---|---|---|
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 can send HTTP events to your server when a document status changes.
Available events
document.authorized— SRI authorized the voucherdocument.rejected— SRI rejected the voucherdocument.cancelled— The voucher annulment was confirmed by SRIdocument.failed— Voucher moved to Dead-Letter Queue after 5 retriesdocument.notification.resent— The voucher notification was resent
Register an 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"
}'Verify the signature
Each call includes the 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);
});Automatic retries
- Attempt 1 — Immediate
- Attempt 2 — 30 seconds
- Attempt 3 — 5 minutes
- Attempt 4 — 30 minutes
