@scell/sdk
v2.27.1
Published
Official TypeScript SDK for Scell.io - Electronic invoicing (Factur-X) and signatures (eIDAS) API
Downloads
2,664
Maintainers
Readme
@scell/sdk
Official TypeScript SDK for Scell.io - Electronic invoicing (Factur-X/UBL/CII) and eIDAS-compliant electronic signatures API.
Features
- Full TypeScript support with strict types
- B2B and B2C invoicing (private individuals —
buyer_is_individualflag, BR-CO-26 EN16931 compliant) - Two authentication modes: Bearer token (dashboard) and API key (external API)
- Automatic retries with exponential backoff and jitter
- Webhook signature verification
- Zero external dependencies (uses native fetch)
- ESM and CommonJS support
- Node.js 18+ and browser compatible
Installation
npm install @scell/sdk
# or
yarn add @scell/sdk
# or
pnpm add @scell/sdkQuick Start
Authentication
import { ScellClient, ScellApiClient, ScellAuth } from '@scell/sdk';
// 1. Login to get a token
const auth = await ScellAuth.login({
email: '[email protected]',
password: 'your-password',
});
// 2. Dashboard operations (Bearer token)
const client = new ScellClient(auth.token);
// 3. API operations (X-API-Key)
const apiClient = new ScellApiClient('your-api-key');Create an Invoice
Note: Invoice numbers are automatically generated by Scell.io. Draft invoices receive a temporary number, and the definitive fiscal number is assigned when the invoice is submitted.
const { data: invoice } = await apiClient.invoices.create({
direction: 'outgoing',
output_format: 'facturx',
issue_date: '2024-01-15',
due_date: '2024-02-15',
currency: 'EUR',
total_ht: 1000.0,
total_tax: 200.0,
total_ttc: 1200.0,
seller_siret: '12345678901234',
seller_name: 'My Company SAS',
seller_address: {
line1: '1 Rue de la Paix',
postal_code: '75001',
city: 'Paris',
country: 'FR',
},
buyer_siret: '98765432109876',
buyer_name: 'Client Company',
buyer_address: {
line1: '2 Avenue des Champs',
postal_code: '75008',
city: 'Paris',
country: 'FR',
},
lines: [
{
description: 'Consulting services - January 2024',
quantity: 10,
unit_price: 100.0,
tax_rate: 20.0,
total_ht: 1000.0,
total_tax: 200.0,
total_ttc: 1200.0,
},
],
});
console.log('Invoice created:', invoice.id);International Invoicing
For non-French parties, SIRET is not required. Use VAT numbers for EU businesses and legal_id with a scheme code for non-EU businesses.
Invoice with Belgian Buyer (EU)
const { data: invoice } = await apiClient.invoices.create({
issue_date: '2026-03-29',
due_date: '2026-04-28',
currency: 'EUR',
// French seller (SIRET required)
seller_siret: '12345678901234',
seller_name: 'Ma Société SAS',
seller_country: 'FR',
seller_vat_number: 'FR12345678901',
seller_address: { line1: '10 rue de Paris', postal_code: '75001', city: 'Paris', country: 'FR' },
// Belgian buyer (no SIRET, VAT number instead)
buyer_name: 'Entreprise Belge SPRL',
buyer_country: 'BE',
buyer_vat_number: 'BE0123456789',
buyer_address: { line1: '15 Avenue Louise', postal_code: '1050', city: 'Bruxelles', country: 'BE' },
lines: [
{ description: 'Consulting services', quantity: 10, unit_price: 150.00, vat_rate: 0 },
],
output_format: 'ubl',
});Note: For intra-community B2B transactions (e.g. FR -> BE, FR -> DE), VAT rate is typically 0% under the reverse charge mechanism. The buyer accounts for VAT in their own country.
VAT Context Resolution (since v2.20.0)
The buyers.vatContext() method resolves the correct TVA rate and EN16931 regime for a buyer + line combination, following French CGI rules (reverse-charge, art. 259-A, out-of-scope).
Mode 1 — Registered buyer (recommended)
import { ScellApiClient } from '@scell/sdk';
const client = new ScellApiClient('sk_live_xxx');
// Resolve the VAT context for a registered buyer
const vat = await client.buyers.vatContext(
'019cb416-b6db-730c-b3a5-f8b7a4512eb1', // buyer UUID from registry
{ category: 'STANDARD' } // desired starting category (optional)
);
console.log(vat.resolution.rate); // 0 (if DE B2B with valid VAT)
console.log(vat.resolution.category); // 'REVERSE_CHARGE'
console.log(vat.resolution.en16931_code); // 'AE'
console.log(vat.resolution.justification);
// "Auto-liquidation intracommunautaire (art. 283-2 CGI) — TVA due par l'acheteur assujetti."
// Apply to invoice line
await client.invoices.create({
// ...
lines: [{ description: 'Conseil', quantity: 1, unit_price: 1000, tax_rate: vat.resolution.rate }],
});Mode 2 — Inline buyer context (quote preview / one-off)
// No registry lookup — pass buyer details inline
const vat = await client.buyers.vatContext(
{
country: 'DE',
is_individual: false,
vat_number: 'DE123456789',
vat_number_valid: true,
},
{
category: 'STANDARD',
place_of_supply: 'FR', // art. 259-A CGI override (digital services)
service_nature: 'digital_service',
}
);Fluent line builder (since v2.20.0)
import { createInvoiceLine, VAT_DEFAULT_RATES } from '@scell/sdk';
// Resolve via API, then build the line with the correct rate
const vat = await client.buyers.vatContext(buyerId, { category: 'STANDARD' });
const line = createInvoiceLine({ category: vat.resolution.category })
.description('Conseil en stratégie')
.unitPrice(1000)
.quantity(2)
.placeOfSupply('DE') // forwarded in Factur-X BT-157
.build();
// → {
// description: 'Conseil en stratégie', unit_price: 1000, quantity: 2,
// tax_rate: 0, total_ht: 2000, total_tax: 0, total_ttc: 2000,
// metadata: { category: 'REVERSE_CHARGE', exemption_reason: 'reverse_charge', place_of_supply: 'DE' }
// }
// Override the resolved rate if needed (e.g. partial SUPER_REDUCED regime)
const customLine = createInvoiceLine()
.description('Médicaments remboursables')
.unitPrice(50)
.taxRate(vat.resolution.rate) // exact rate from server
.build();Available VAT categories:
STANDARD(20%),INTERMEDIATE(10%),REDUCED(5.5%),SUPER_REDUCED(2.1%),ZERO_RATED(0%),EXEMPT(0%),REVERSE_CHARGE(0%),OUT_OF_SCOPE(0%).Warnings (non-blocking) are returned alongside the resolution for UI display.
Invoice with UK Buyer (Non-EU)
For non-EU buyers, use buyer_legal_id and buyer_legal_id_scheme in addition to the VAT number:
const { data: invoice } = await apiClient.invoices.create({
issue_date: '2026-03-29',
due_date: '2026-04-28',
currency: 'GBP',
seller_siret: '12345678901234',
seller_name: 'Ma Société SAS',
seller_country: 'FR',
seller_vat_number: 'FR12345678901',
seller_address: { line1: '10 rue de Paris', postal_code: '75001', city: 'Paris', country: 'FR' },
// UK buyer — legal_id with scheme
buyer_name: 'British Ltd',
buyer_country: 'GB',
buyer_vat_number: 'GB123456789',
buyer_legal_id: '12345678',
buyer_legal_id_scheme: '0088', // UK Company Number scheme
buyer_address: { line1: '20 Baker Street', postal_code: 'W1U 3BW', city: 'London', country: 'GB' },
lines: [
{ description: 'Design services', quantity: 5, unit_price: 200.00, vat_rate: 0 },
],
output_format: 'ubl',
});Create a Signature Request
import { readFileSync } from 'fs';
const pdfContent = readFileSync('contract.pdf');
const { data: signature } = await apiClient.signatures.create({
title: 'Service Agreement 2024',
description: 'Annual service contract',
document: pdfContent.toString('base64'),
document_name: 'contract.pdf',
signers: [
{
first_name: 'John',
last_name: 'Doe',
email: '[email protected]',
auth_method: 'email',
message: 'Bonjour John, merci de signer ce contrat. Code OTP : {OTP}',
},
{
first_name: 'Jane',
last_name: 'Smith',
phone: '+33612345678',
auth_method: 'sms',
},
],
// signer_index (0-base) cible chaque position au bon signataire.
// Un signataire peut avoir plusieurs positions (capacite EU-SES).
signature_positions: [
{ signer_index: 0, page: 1, x: 70, y: 85, width: 20, height: 5, unit: 'percent' }, // John — page 1
{ signer_index: 0, page: 2, x: 70, y: 85, width: 20, height: 5, unit: 'percent' }, // John — page 2
{ signer_index: 1, page: 2, x: 20, y: 85, width: 20, height: 5, unit: 'percent' }, // Jane — page 2
],
ui_config: {
// White-label : 21 champs alignés sur la spec EU-SES certifiée
sidebar_logo: 'https://mycompany.com/logo.png',
sidebar_background_color: '#3b82f6',
sidebar_title_color: '#ffffff',
sign_button_background_color: '#10b981',
sign_button_background_color_hover: '#059669',
hide_download_validated: false,
iframe_ancestors: ['https://myapp.com'],
},
signature_options: {
signature_mode: 'both', // 'typed' | 'drawn' | 'both'
signer_must_read: true, // force le signataire a parcourir le document
user_editable_data: { name: false, mobile: true, email: false },
timezone: 'Europe/Paris',
},
redirect_complete_url: 'https://myapp.com/signed',
redirect_cancel_url: 'https://myapp.com/cancelled',
// v2.11.0 — Signature blocks (all optional, backward-compatible)
// Bloc paraphe : initiales apposees automatiquement sur chaque page sauf la derniere
initials_block: {
enabled: true,
mode: 'auto',
source: 'signer_name', // derive depuis first_name + last_name du signataire
pages: 'except_last',
position: { x: 5, y: 90, unit: 'percent' },
font_size: 10,
color: '#1a1a1a',
},
// Mentions legales : l'utilisateur coche ou signe chaque mention
mentions: [
{
label: "J'accepte les conditions generales d'utilisation",
required: true,
signer_index: 0, // cible le premier signataire uniquement (0-base)
position: { page: 2, x: 10, y: 80, unit: 'percent' },
font_size: 9,
color: '#333333',
},
{
label: 'Lu et approuve par toutes les parties',
required: true,
position: { page: 3, x: 10, y: 90, unit: 'percent' },
// Sans signer_index : la mention s'applique a tous les signataires
},
],
// Bloc date : date du jour de signature apposee sur la derniere page
date_block: {
enabled: true,
format: 'dd/MM/yyyy', // tokens date-fns
timezone: 'Europe/Paris', // IANA
position: { page: 'last', x: 70, y: 88, unit: 'percent' },
font_size: 9,
color: '#555555',
},
});
// Send signing URLs to signers
signature.signers?.forEach((signer) => {
console.log(`${signer.email}: ${signer.signing_url}`);
});Webhook Verification
import { ScellWebhooks } from '@scell/sdk';
// Express.js example
app.post('/webhooks/scell', async (req, res) => {
const signature = req.headers['x-scell-signature'] as string;
const payload = JSON.stringify(req.body);
// Verify the webhook signature
const isValid = await ScellWebhooks.verifySignature(
payload,
signature,
process.env.WEBHOOK_SECRET!
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Parse and handle the event
const event = ScellWebhooks.parsePayload(payload);
switch (event.event) {
case 'invoice.validated':
console.log('Invoice validated:', event.data);
break;
case 'signature.completed':
console.log('Signature completed:', event.data);
break;
case 'balance.low':
console.log('Low balance alert!');
break;
}
res.status(200).send('OK');
});API Reference
ScellClient (Dashboard)
For user/dashboard operations with Bearer token authentication.
const client = new ScellClient(token, {
baseUrl: 'https://api.scell.io/api/v1', // optional
timeout: 30000, // optional, in ms
retry: { maxRetries: 3 }, // optional
});
// Resources
client.auth // User authentication
client.companies // Company management
client.apiKeys // API key management
client.balance // Balance and transactions
client.webhooks // Webhook management
client.invoices // Invoice listing (read-only)
client.signatures // Signature listing (read-only)
client.creditNotes // Credit notes managementScellApiClient (External API)
For creating invoices and signatures with X-API-Key authentication.
const apiClient = new ScellApiClient(apiKey, {
baseUrl: 'https://api.scell.io/api/v1', // optional
timeout: 30000, // optional
});
// Resources
apiClient.invoices // Create, download, convert invoices
apiClient.signatures // Create, download, remind, cancel signatures
apiClient.creditNotes // Create, send, download tenant credit notes
apiClient.subTenants // Sub-tenant management
apiClient.fiscal // ISCA fiscal compliance
apiClient.stats // Platform statistics
apiClient.billing // Usage, top-up, transactions
apiClient.tenantInvoices // Tenant invoice operations
apiClient.tenantSignatures // Tenant signature operations (read-only, URL-nested)
apiClient.incomingInvoices // Incoming invoice operationsScellApiClient API Reference
| Resource | Methods |
|----------|---------|
| .invoices | create(params), list(filters?), get(id), download(id, format?), auditTrail(id), convert(params), incoming(filters?), accept(id, input), reject(id, input), dispute(id, input), markPaid(id, input), downloadFile(id, format?) |
| .signatures | create(params), list(filters?), get(id), download(id, type), remind(id), cancel(id) |
| .creditNotes | list(subTenantId, options?), create(subTenantId, input), get(id), send(id), download(id), delete(id), remainingCreditable(invoiceId) |
| .subTenants | list(), create(input), get(id), update(id, input), delete(id), findByExternalId(externalId), getSuperPDPStatus(id) (v2), refreshSuperPDPStatus(id) (v2, rate-limited 1/min), getResumeUrl(id) (v2) |
| .fiscal | 26 methods (see ScellTenantClient reference) |
| .stats | overview(options?), monthly(options?), subTenantOverview(subTenantId, options?) |
| .billing | invoices(options?), showInvoice(id), downloadInvoice(id), usage(options?), topUp(input), confirmTopUp(input), transactions(options?) |
| .tenantInvoices | create(params), list(filters?), get(id), update(id, params), delete(id), validate(id), send(id), download(id), downloadXml(id), bulkCreate(invoices), bulkSubmit(ids), bulkStatus(ids) |
| .tenantSignatures | list(options?), get(id), listForSubTenant(subTenantId, options?), getForSubTenant(subTenantId, id) (read-only, URL-nested under /tenant/signatures and /tenant/sub-tenants/{id}/signatures) |
| .incomingInvoices | create(subTenantId, params), listForSubTenant(subTenantId), get(id), accept(id), reject(id, reason), markPaid(id), download(id) |
Onboarding
The onboarding flow lets a partner embed Scell's SuperPDP-powered signup inside a popup. SuperPDP handles company registration, KYB, and identity verification. Once complete, a Scell tenant is provisioned for the end-user.
Your publishable key (pk_live_*) is safe to use client-side and powers the onboarding resource. Retrieve yours from the Scell dashboard.
import { ScellPublicClient } from '@scell/sdk';
const client = new ScellPublicClient('pk_live_...');
// Step 1: Create a session for the end-user
const { data: session } = await client.onboarding.createSession();
// Step 2: Get the SuperPDP authorize URL
const { authorize_url, state } = await client.onboarding.getSuperPDPAuthorizeUrl(session.id);
// Step 3: Open popup — SuperPDP handles signup, KYB, and identity verification
const popup = window.open(authorize_url, 'superpdp-onboarding', 'width=800,height=700');
// Step 4: Poll or listen for completion, then call the callback
// Your backend receives the code from SuperPDP and calls:
const result = await client.onboarding.superpdpCallback(
session.id,
'auth_code_from_superpdp',
state
);
if (result.success) {
console.log('Tenant enrolled:', result.tenant.name, '—', result.tenant.siret);
// result.tenant: { id, name, siret, environment }
}Available methods on client.onboarding:
| Method | Description |
|--------|-------------|
| createSession() | Create an onboarding session — POST /onboarding/sessions |
| getSession(sessionId) | Retrieve session status — GET /onboarding/sessions/:sessionId |
| getSuperPDPAuthorizeUrl(sessionId) | Get the SuperPDP OAuth2 popup URL — POST /onboarding/superpdp/authorize |
| superpdpCallback(sessionId, code, state) | Complete enrollment after SuperPDP redirect — POST /onboarding/superpdp/callback |
Companies
// List companies
const { data: companies } = await client.companies.list();
// Create company
const { data: company } = await client.companies.create({
name: 'My Company',
siret: '12345678901234',
address_line1: '1 Rue Example',
postal_code: '75001',
city: 'Paris',
});
// Update company
await client.companies.update(companyId, { email: '[email protected]' });
// Delete company
await client.companies.delete(companyId);
// KYC
const kyc = await client.companies.initiateKyc(companyId);
const status = await client.companies.kycStatus(companyId);Invoices
// List invoices (dashboard)
const { data, meta } = await client.invoices.list({
direction: 'outgoing',
status: 'validated',
from: '2024-01-01',
per_page: 50,
});
// Get invoice
const { data: invoice } = await client.invoices.get(invoiceId);
// Create invoice (API key required)
const { data: newInvoice } = await apiClient.invoices.create({...});
// Download
const { url, expires_at } = await apiClient.invoices.download(invoiceId, 'pdf');
// Audit trail
const { data: trail, integrity_valid } = await apiClient.invoices.auditTrail(invoiceId);
// Convert format
await apiClient.invoices.convert({ invoice_id: invoiceId, target_format: 'ubl' });Incoming Invoices (Supplier Invoices)
// List incoming invoices
const { data: incoming, meta } = await apiClient.invoices.incoming({
status: 'pending',
seller_siret: '12345678901234',
from: '2024-01-01',
min_amount: 100,
per_page: 50,
});
console.log(`Found ${meta.total} incoming invoices`);
// Accept an incoming invoice
const { data: accepted } = await apiClient.invoices.accept(invoiceId, {
payment_date: '2024-02-15',
note: 'Approved by accounting department',
});
// Reject an incoming invoice
const { data: rejected } = await apiClient.invoices.reject(invoiceId, {
reason: 'Invoice amount does not match purchase order #PO-2024-001',
reason_code: 'incorrect_amount',
});
// Dispute an incoming invoice
const { data: disputed } = await apiClient.invoices.dispute(invoiceId, {
reason: 'Billed amount exceeds agreed price by 50 EUR',
dispute_type: 'amount_dispute',
expected_amount: 950.00,
});
// Mark an invoice as paid (mandatory in French e-invoicing lifecycle)
const { data: paidInvoice } = await apiClient.invoices.markPaid(invoiceId, {
payment_reference: 'VIR-2026-0124',
paid_at: '2026-01-24T10:30:00Z',
note: 'Payment received via bank transfer',
});
// Download invoice file as PDF (Factur-X with embedded XML)
const pdfBuffer = await apiClient.invoices.downloadFile(invoiceId);
// In Node.js:
import { writeFileSync } from 'fs';
writeFileSync('invoice.pdf', Buffer.from(pdfBuffer));
// Download XML version (UBL/CII standalone)
const xmlBuffer = await apiClient.invoices.downloadFile(invoiceId, 'xml');
writeFileSync('invoice.xml', Buffer.from(xmlBuffer));Signatures
// List signatures (works under Sanctum dashboard AND sk_*/api key)
// Scoped to the authenticated tenant. Filters: status, environment,
// company_id, sub_tenant_id (restrict to one sub-tenant), per_page (max 100).
const { data, meta } = await client.signatures.list({
status: 'pending',
sub_tenant_id: 'optional-sub-tenant-uuid',
per_page: 25,
});
// Get signature
const { data: signature } = await client.signatures.get(signatureId);
// Create signature (API key required)
const { data: newSignature } = await apiClient.signatures.create({...});
// Download files
const { url: signedUrl } = await apiClient.signatures.download(signatureId, 'signed');
const { url: auditUrl } = await apiClient.signatures.download(signatureId, 'audit_trail');
// Send reminder
const { signers_reminded } = await apiClient.signatures.remind(signatureId);
// Cancel
await apiClient.signatures.cancel(signatureId);
// Get audit trail
const { data: trail } = await apiClient.signatures.auditTrail(signatureId);Tenant Credit Notes
// List credit notes for a sub-tenant
const { data, meta } = await apiClient.creditNotes.list('sub-tenant-uuid', {
status: 'sent',
date_from: '2024-01-01',
per_page: 50,
});
console.log(`Found ${meta.total} credit notes`);
// Check remaining creditable amount for an invoice
const remaining = await apiClient.creditNotes.remainingCreditable('invoice-uuid');
console.log('Remaining to credit:', remaining.remaining_total);
remaining.lines.forEach(line => {
console.log(`${line.description}: ${line.remaining_quantity} items remaining`);
});
// Create a partial credit note
const { data: creditNote } = await apiClient.creditNotes.create('sub-tenant-uuid', {
invoice_id: 'invoice-uuid',
reason: 'Product returned - damaged item',
type: 'partial',
items: [
{ invoice_line_id: 'line-uuid-1', quantity: 2 }
]
});
// Create a total credit note
const { data: totalCreditNote } = await apiClient.creditNotes.create('sub-tenant-uuid', {
invoice_id: 'invoice-uuid',
reason: 'Order cancelled',
type: 'total'
});
// Get credit note details
const { data: details } = await apiClient.creditNotes.get('credit-note-uuid');
console.log('Credit note number:', details.credit_note_number);
// Send a credit note (changes status from draft to sent)
const { data: sent } = await apiClient.creditNotes.send('credit-note-uuid');
// Download credit note as PDF
const pdfBuffer = await apiClient.creditNotes.download('credit-note-uuid');
// In Node.js:
import { writeFileSync } from 'fs';
writeFileSync('credit-note.pdf', Buffer.from(pdfBuffer));
// Delete a draft credit note
await apiClient.creditNotes.delete('credit-note-uuid');Balance
// Get balance
const { data: balance } = await client.balance.get();
console.log(`${balance.amount} ${balance.currency}`);
// Reload balance
const { transaction } = await client.balance.reload({ amount: 100 });
// Update settings
await client.balance.updateSettings({
auto_reload_enabled: true,
auto_reload_threshold: 50,
auto_reload_amount: 200,
low_balance_alert_threshold: 100,
});
// List transactions
const { data: transactions } = await client.balance.transactions({
type: 'debit',
service: 'invoice',
from: '2024-01-01',
});Webhooks
// List webhooks
const { data: webhooks } = await client.webhooks.list();
// Create webhook
const { data: webhook } = await client.webhooks.create({
url: 'https://myapp.com/webhooks/scell',
events: ['invoice.validated', 'signature.completed', 'balance.low'],
environment: 'production',
});
// IMPORTANT: Store webhook.secret securely!
// Update webhook
await client.webhooks.update(webhookId, { is_active: false });
// Test webhook
const result = await client.webhooks.test(webhookId);
console.log('Test successful:', result.success);
// Regenerate secret
const { data: updated } = await client.webhooks.regenerateSecret(webhookId);
// Update your stored secret!
// View logs
const { data: logs } = await client.webhooks.logs(webhookId);
// Delete webhook
await client.webhooks.delete(webhookId);ScellTenantClient (Multi-Tenant Partner)
For multi-tenant operations with X-Tenant-Key authentication.
import { ScellTenantClient } from '@scell/sdk';
const tenant = new ScellTenantClient({
tenantKey: 'tk_live_...',
baseUrl: 'https://api.scell.io/api/v1', // optional
});
// Profile
const profile = await tenant.me();
await tenant.updateProfile({ company_name: 'New Name' });
const balance = await tenant.balance();
const stats = await tenant.quickStats();
await tenant.regenerateKey();
// Sub-Tenants
const subTenants = await tenant.subTenants.list();
const sub = await tenant.subTenants.create({ ... });
// Direct Invoices (without sub-tenant)
const invoices = await tenant.directInvoices.list();
const invoice = await tenant.directInvoices.create({ ... });
await tenant.directInvoices.bulkCreate([...]);
await tenant.directInvoices.bulkSubmit([id1, id2]);
// Direct Credit Notes
const notes = await tenant.directCreditNotes.list();
const note = await tenant.directCreditNotes.create({ ... });
// Incoming Invoices
const incoming = await tenant.incomingInvoices.listForSubTenant(subId);
await tenant.incomingInvoices.accept(invoiceId);
// Signatures (read-only, URL-nested)
const sigs = await tenant.signatures.list({ status: 'completed' });
const sig = await tenant.signatures.get('sig-uuid');
const subSigs = await tenant.signatures.listForSubTenant(subId, { status: 'pending' });
const subSig = await tenant.signatures.getForSubTenant(subId, 'sig-uuid');
// Fiscal Compliance
const compliance = await tenant.fiscal.compliance();
const integrity = await tenant.fiscal.integrity();
### ISCA Compliance Documents
Download the three mandatory ISCA compliance documents as PDF:
```typescript
// Measures register (registre des mesures)
const registerPdf = await client.fiscal.downloadMeasuresRegister();
// Technical dossier (dossier technique)
const dossierPdf = await client.fiscal.downloadTechnicalDossier();
// Self-attestation (auto-attestation ISCA)
const attestationPdf = await client.fiscal.downloadSelfAttestation();// Billing const billingInvoices = await tenant.billing.invoices(); const usage = await tenant.billing.usage();
// Stats const overview = await tenant.stats.overview();
#### ScellTenantClient API Reference
| Resource | Methods |
|----------|---------|
| Direct methods | `me()`, `updateProfile(input)`, `balance()`, `quickStats()`, `regenerateKey()` |
| `.subTenants` | `list()`, `create(input)`, `get(id)`, `update(id, input)`, `delete(id)`, `findByExternalId(externalId)`, `getSuperPDPStatus(id)` (v2), `refreshSuperPDPStatus(id)` (v2, rate-limited 1/min), `getResumeUrl(id)` (v2) |
| `.directInvoices` | `create(params)`, `list(filters?)`, `get(id)`, `update(id, params)`, `delete(id)`, `validate(id)`, `send(id)`, `download(id)`, `downloadXml(id)`, `bulkCreate(invoices)`, `bulkSubmit(ids)`, `bulkStatus(ids)` |
| `.directCreditNotes` | `create(params)`, `list(filters?)`, `get(id)`, `update(id, params)`, `send(id)`, `download(id)`, `remainingCreditable(invoiceId)` |
| `.subTenantCreditNotes` | `list(subTenantId, options?)`, `create(subTenantId, input)`, `get(id)`, `update(id, input)`, `send(id)`, `download(id)`, `remainingCreditable(invoiceId)` |
| `.incomingInvoices` | `create(subTenantId, params)`, `listForSubTenant(subTenantId, filters?)`, `get(id)`, `accept(id, input?)`, `reject(id, reason, code?)`, `markPaid(id, input?)`, `download(id)` |
| `.fiscal` | `compliance()`, `integrity()`, `integrityHistory()`, `integrityForDate(date)`, `closings()`, `performDailyClosing(input?)`, `fecExport(options)`, `fecDownload(options)`, `attestation(year)`, `attestationDownload(year)`, `entries()`, `killSwitchStatus()`, `killSwitchActivate(input)`, `killSwitchDeactivate()`, `anchors()`, `rules()`, `ruleDetail(key)`, `ruleHistory(key)`, `createRule(input)`, `updateRule(id, input)`, `exportRules(options)`, `replayRules(input)`, `forensicExport(options)` |
| `.billing` | `invoices(options?)`, `showInvoice(id)`, `downloadInvoice(id)`, `usage(options?)`, `topUp(input)`, `confirmTopUp(input)`, `transactions(options?)` |
| `.stats` | `overview(options?)`, `monthly(options?)`, `subTenantOverview(subTenantId, options?)` |
## Error Handling
```typescript
import {
ScellError,
ScellAuthenticationError,
ScellValidationError,
ScellRateLimitError,
ScellNotFoundError,
ScellInsufficientBalanceError,
} from '@scell/sdk';
try {
await apiClient.invoices.create(data);
} catch (error) {
if (error instanceof ScellValidationError) {
console.log('Validation errors:', error.errors);
error.getAllMessages().forEach(msg => console.log(msg));
} else if (error instanceof ScellAuthenticationError) {
console.log('Invalid credentials');
} else if (error instanceof ScellRateLimitError) {
console.log(`Rate limited. Retry after ${error.retryAfter}s`);
} else if (error instanceof ScellInsufficientBalanceError) {
console.log('Insufficient balance, please reload');
} else if (error instanceof ScellNotFoundError) {
console.log('Resource not found');
} else if (error instanceof ScellError) {
console.log(`API error: ${error.message} (${error.status})`);
}
}Retry Configuration
The SDK automatically retries failed requests for rate limits (429) and server errors (5xx).
import { withRetry, createRetryWrapper } from '@scell/sdk';
// Custom retry options
const client = new ScellClient(token, {
retry: {
maxRetries: 5,
baseDelay: 1000,
maxDelay: 30000,
jitterFactor: 0.1,
},
});
// Disable retry for specific request
const result = await client.companies.list({ skipRetry: true });
// Manual retry wrapper
const result = await withRetry(
() => apiClient.invoices.create(data),
{ maxRetries: 5 }
);Webhook Events
| Event | Description |
|-------|-------------|
| invoice.created | Invoice created |
| invoice.validated | Invoice validated |
| invoice.transmitted | Invoice transmitted to recipient |
| invoice.accepted | Invoice accepted |
| invoice.rejected | Invoice rejected |
| invoice.error | Invoice processing error |
| invoice.incoming.received | Incoming invoice received from supplier |
| invoice.incoming.validated | Incoming invoice validated |
| invoice.incoming.accepted | Incoming invoice accepted |
| invoice.incoming.rejected | Incoming invoice rejected |
| invoice.incoming.disputed | Incoming invoice disputed |
| invoice.incoming.paid | Incoming invoice marked as paid |
| signature.created | Signature request created |
| signature.waiting | Waiting for signers |
| signature.signed | A signer has signed |
| signature.completed | All signers have signed |
| signature.refused | Signature refused |
| signature.expired | Signature expired |
| signature.error | Signature processing error |
| balance.low | Balance below low threshold |
| balance.critical | Balance below critical threshold |
TypeScript Types
All types are exported and can be imported:
import type {
Invoice,
InvoiceStatus,
InvoiceDirection,
InvoiceFileFormat,
CreateInvoiceInput,
// Incoming invoices
IncomingInvoiceParams,
AcceptInvoiceInput,
RejectInvoiceInput,
DisputeInvoiceInput,
MarkPaidInput,
RejectionCode,
DisputeType,
// Signatures
Signature,
SignatureStatus,
CreateSignatureInput,
SignatureUIConfig,
SignatureOptions,
SignatureMode,
SignaturePosition,
SignaturePositionUnit,
SignerEditableData,
Signer,
// Tenant Credit Notes
TenantCreditNote,
TenantCreditNoteStatus,
TenantCreditNoteType,
CreateTenantCreditNoteInput,
RemainingCreditable,
// Webhooks
Webhook,
WebhookEvent,
WebhookPayload,
InvoiceIncomingPaidPayload,
// Other
Company,
Balance,
Transaction,
} from '@scell/sdk';Environment Variables
# API Configuration
SCELL_API_URL=https://api.scell.io/api/v1
SCELL_API_KEY=your-api-key
SCELL_WEBHOOK_SECRET=whsec_your-webhook-secretRequirements
- Node.js 18.0.0 or higher (for native fetch)
- TypeScript 5.0 or higher (for development)
License
MIT
