npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@scell/sdk

v2.27.1

Published

Official TypeScript SDK for Scell.io - Electronic invoicing (Factur-X) and signatures (eIDAS) API

Downloads

2,664

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_individual flag, 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/sdk

Quick 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 management

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.tenantSignatures  // Tenant signature operations (read-only, URL-nested)
apiClient.incomingInvoices  // Incoming invoice operations

ScellApiClient 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-secret

Requirements

  • Node.js 18.0.0 or higher (for native fetch)
  • TypeScript 5.0 or higher (for development)

License

MIT