Package Exports
- @scell/sdk
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
- 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: 'user@example.com',
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 },
],
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.
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 },
],
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: 'john.doe@example.com',
auth_method: 'email',
},
{
first_name: 'Jane',
last_name: 'Smith',
phone: '+33612345678',
auth_method: 'sms',
},
],
ui_config: {
logo_url: 'https://mycompany.com/logo.png',
primary_color: '#3b82f6',
company_name: 'My Company',
},
redirect_complete_url: 'https://myapp.com/signed',
redirect_cancel_url: 'https://myapp.com/cancelled',
});
// 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)ScellApiClient (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.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) |
.fiscal |
23 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) |
.incomingInvoices |
create(subTenantId, params), listForSubTenant(subTenantId), get(id), accept(id), reject(id, reason), markPaid(id), download(id) |
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: 'new@example.com' });
// 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 (dashboard)
const { data, meta } = await client.signatures.list({
status: 'pending',
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);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);
// 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)` |
| `.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,
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