Skip to main content
Developer Reference

Official SDKs & API Reference

The definitive reference for integrating Scell.io. Type-safe SDKs for TypeScript and PHP, plus an MCP agent for AI-powered workflows. Every resource, every method, fully documented.

TypeScript

TypeScript SDK

TypeScript / JavaScript

v3.5.3

Installation

npm install @scell/sdk

Features

  • Full TypeScript typings (strict mode)
  • Promise-based async/await API
  • 4 client classes (Bearer, API Key, Tenant, Public)
  • B2B + B2C invoices, credit notes, signatures, fiscal, webhooks
  • Invoice Templates (upload logo + colors + mentions)
  • Factur-X banking details (IBAN/BIC) + default payment terms
  • Daily closure auto + email + CSV
  • Nominative ISCA self-attestation (tenant + sub_tenant)
  • Sandbox mode with sk_test_ prefix
  • Online Stripe payment for Scell.io invoices (since v2.2.0)
  • Sub-tenants management: ISCA-safe cascade delete + SuperPDP authorize URL (since v2.9.0)
  • OpenTimestamps `ots_proof_base64` receipt on fiscal closings (since v2.10.0)
  • Signature blocks: auto-paraphe + legal mentions + today date (since v2.12.0)
  • Quotes: CRUD, send by email, signature, deposit/balance conversion (since v2.13.0)
  • Full PaymentSchedule: set/patch/delete/summary/convertLine/presets (since v2.13.0)
  • Quote `callback_url`: redirect buyer to tenant site after accept/refuse (since v2.13.1)
  • `PaymentSummary.lines[]`: full visual tracker without extra request (since v2.14.0)
  • invoices.update() / delete() + creditNotes.update() / delete() — draft only (since v2.16.0)
  • Multi-page initials: `initials_block.positions[]` with per-page placement and style overrides (since v2.17.0)
  • Webhook secret shown once at create / regenerate + `secret_last4` fingerprint (since v2.19.0)
  • `parent_quote_id` on standard invoices: soft link to a source quote for traceability (since v2.21.0)
  • `refunded` / `partially_refunded` statuses set automatically by the backend + `refund_status` and `total_refunded` fields exposed (since v2.22.0)
  • Exhaustive enum coverage: 19 strict union types (`InvoiceType`, `QuoteStatus`, `VatCategory`, `SignatureStatus`, etc.) — no more generic `string` on the SDK surface (since v2.23.0)
  • BT-81 Payment Means Code: Factur-X EN16931 compliance with 11 UN/ECE 4461 codes (since v2.25.0)
  • Suppliers registry (`suppliers`): auto-populated from received invoices — GET + PATCH (email/phone/notes only); no manual create or delete (since v2.26.0 / v3.0.0)
  • `InvoiceStatus` aligned with the backend 16 canonical statuses (since v2.27.1)
  • Country company reference (`reference.countries()` / `.country(code)`): VAT, national registration ID (register + ISO 6523 scheme), legal forms — for country-aware buyer/seller forms (since v2.29.0)
  • Micro-entrepreneur threshold monitoring (`subTenants.getThresholds`): VAT-franchise + micro-ceiling gauges, projected crossing date (dated rules, loi 2025-1044); VAT switch via `updateFiscalStatus` (since v2.30.0)
  • Per-sub-tenant closing download (`fiscal.downloadClosing`, `closing_type`/`sub_tenant_id` filters) + per-sub-tenant FEC (since v2.30.0)
  • Pre-issuance threshold simulator (`subTenants.simulateThresholds`): check a crossing BEFORE issuing an invoice (since v2.31.0)
  • Explicit credit note contract: a partial credit note SELECTS invoice lines (`items[].invoice_line_id`), exact per-line VAT inherited (multi-rate); typed `remainingCreditable` (since v2.32.0)
  • Forced SuperPDP disconnect / reconnect for a sub-tenant (`subTenants.superpdpDisconnect` / `superpdpReconnect`) + signed 24h token for the SuperPDP-only widget (`superpdpWidgetToken`) (since v3.1.0)
  • Live HTML preview of a draft document (`documents.preview`), direct email logo upload (`branding.tenant.uploadLogoFile` / `branding.subTenants.uploadLogoFile`), non-persisted branding preview overrides (`branding.tenant.preview(overrides)`) and template color derivation from the logo (`invoiceTemplates.deriveColorsFromEmailLogo`) (since v3.2.0)
  • Deposit groups (multi-invoice deals) `invoices.depositGroups()` / `depositGroup(id)`, legal-mentions assistant `invoiceMentions.assistant()` / `preview()`, wired quote payment schedule `quotes.paymentSchedule`, colors derived from the invoice logo `invoiceTemplates.deriveColorsFromInvoiceLogo()` + template preview `invoiceTemplates.preview()`; quote fixes `quotes.regeneratePublicLink` / `revokePublicLink` / `preview` (since v3.5.0)
PHP

PHP SDK

PHP 8.2+

v3.5.1

Installation

composer require scell/sdk

Features

  • PHP 8.2+ with strict types (readonly DTOs)
  • PSR-4 autoloading
  • Laravel auto-discovery (config/scell.php)
  • 4 client classes (Bearer, API Key, Tenant, Public)
  • B2B + B2C invoices, credit notes, signatures, fiscal, webhooks
  • Invoice Templates (upload logo + colors + mentions)
  • Factur-X banking details (IBAN/BIC) + default payment terms
  • Nominative ISCA self-attestation (tenant + sub_tenant)
  • Fluent builder (buyerIndividual, asB2c, etc.)
  • Sandbox mode built-in
  • Online Stripe payment for Scell.io invoices (since v2.2.0)
  • Sub-tenants management: ISCA-safe cascade delete + SuperPDP authorize URL (since v2.9.0)
  • OpenTimestamps `ots_proof_base64` receipt on fiscal closings (since v2.10.0)
  • Signature blocks: InitialsBlock + Mention + DateBlock DTOs (since v2.12.0)
  • Quotes: CRUD + fluent QuoteBuilder (since v2.13.0)
  • QuotePaymentScheduleResource: list/set/patch/delete/summary/convertLine/presets (since v2.13.0)
  • QuoteBuilder::callbackUrl(): redirect buyer to tenant site after accept/refuse (since v2.13.1)
  • invoices()->update() / delete() + creditNotes()->update() / delete() — draft only (since v2.14.0)
  • Multi-page initials: `InitialsPosition` DTO + `InitialsBlock::withPositions([...])` (since v2.15.0)
  • DTO `Webhook::$secret_last4` + secret shown once at create (since v2.17.0)
  • `InvoiceBuilder::parentQuoteId()` on standard invoices: soft link to a source quote (since v2.19.0)
  • Complete `InvoiceStatus` enum (refunded, partially_refunded) + `refund_status` and `total_refunded` fields on the Invoice DTO (since v2.20.0)
  • Exhaustive enum coverage: 19 strictly-typed enum classes in `Scell\Sdk\Enums\*` (`InvoiceType`, `QuoteStatus`, `VatCategory`, `SignatureStatus`, etc.) — no more generic `string` on DTOs (since v2.21.0)
  • BT-81 Payment Means Code: `PaymentMeansCode` enum with the 11 UN/ECE 4461 codes for Factur-X EN16931 compliance (since v2.25.0)
  • Suppliers registry (`suppliers()`): auto-populated from received invoices — GET + PATCH (email/phone/notes/metadata only); no manual create or delete (since v2.26.0 / v3.0.0)
  • Country company reference (`reference()->countries()` / `->country($code)`): `CountryReference` DTO (VAT, national registration ID + ISO 6523 scheme, legal forms) — for country-aware buyer/seller forms (since v2.29.0)
  • Micro-entrepreneur threshold monitoring (`subTenants()->getThresholds`) + VAT switch (`updateFiscalStatus`) + pre-issuance simulator (`simulateThresholds`) (since v2.30.0/v2.31.0)
  • Per-sub-tenant closing download (`fiscal()->downloadClosing`) + per-sub-tenant FEC (since v2.30.0)
  • Explicit credit note contract: a partial credit note SELECTS invoice lines (`items[].invoice_line_id`), exact per-line VAT inherited; documented `remainingCreditable()` (since v2.32.0)
  • Forced SuperPDP disconnect / reconnect for a sub-tenant (`subTenants()->superpdpDisconnect` / `superpdpReconnect`) + signed 24h token for the SuperPDP-only widget (`superpdpWidgetToken`) (since v3.1.0)
  • Live HTML document preview (`$client->documents->preview($payload)`), direct email logo upload (`branding->uploadLogoTenant` / `uploadLogoSubTenant`), non-persisted branding preview overrides (`previewTenant($overrides)` / `previewSubTenant`) and template color derivation from the logo (`invoiceTemplates->deriveColorsFromEmailLogo()`) (since v3.4.0)
  • Deposit groups (multi-invoice deals) `invoices()->depositGroups()` / `depositGroup($id)`, legal-mentions assistant `invoiceMentions()->assistant()` / `preview()`, colors derived from the invoice logo `invoiceTemplates()->deriveColorsFromInvoiceLogo()` + template preview `invoiceTemplates()->preview()` (since v3.5.0)
🤖

MCP Agent

Claude Desktop / Cursor / VS Code

v3.5.0

Installation

npx -y mcp-remote https://api.scell.io/api/mcp

Features

  • MCP Protocol compatible
  • Claude Desktop, Cursor, VS Code, other LLMs
  • 148 tools: B2B/B2C invoices, credit notes, quotes, payment schedules, recurring invoices, buyers, products & categories, suppliers, signatures, fiscal, templates, legal mentions, branding, documents, sub-tenants & onboarding, billing, credit packs, stats (since v3.5.0)
  • Invoice templates with logo upload + colors (one-time setup)
  • Nominative ISCA self-attestation (tenant + sub_tenant) in one prompt
  • B2C LLM heuristics (particulier, individual, M./Mme/...)
  • Zero runtime dependencies
  • Sandbox mode support
  • Pay a Scell.io invoice in 1 prompt (since v2.2.0)
  • Signature blocks: initialsBlock + mentions + dateBlock camelCase LLM-friendly (since v2.12.0)
  • scell_mark_invoice_paid: mark an outbound invoice as paid (since v2.15.0)
  • Multi-page initials: `initialsBlock.positions[]` (one entry per page with overrides) LLM-discoverable (since v2.16.0)
  • scell_mark_invoice_paid: mark outgoing invoice as paid (since v2.15.0)
  • `parentQuoteId` on standard invoices: soft link to a source quote for traceability (since v2.20.0)
  • Automatic `refunded` / `partially_refunded` statuses + `refundStatus` and `totalRefunded` fields LLM-discoverable (since v2.21.0)
  • Exhaustive enum coverage: tool descriptions enriched with allowed values for the 19 API enums (the LLM sees the full vocabulary, no more 422 errors from invented values) (since v2.22.0)
  • BT-81 Payment Means Code: Factur-X EN16931 compliance with 11 UN/ECE 4461 codes (since v2.25.0)
  • Country company reference types (`CountryReference`, `LegalForm`…) to adapt buyer/seller input to the country (since v2.28.0)
  • Micro-entrepreneur threshold tools: `scell_get_subtenant_thresholds`, `scell_update_subtenant_fiscal_status` (VAT switch), `scell_simulate_subtenant_thresholds` (pre-issuance simulator) (since v2.29.0/v2.30.0)
  • Fiscal closing tools: `scell_list_fiscal_closings` + `scell_export_fec` (CSV/FEC, tenant and sub-tenant scope) (since v2.29.0)
  • Explicit credit note contract: `scell_create_credit_note` selects invoice lines (`invoice_line_id`, per-line VAT inherited), `scell_get_remaining_creditable` for discovery (since v2.31.0)
  • Branded email preview: `scell_preview_tenant_branding` / `scell_preview_sub_tenant_branding` render the email (HTML or PDF) exactly as it will be received, before any send (since v2.34.0)
  • SuperPDP disconnect / reconnect for a sub-tenant: `scell_disconnect_subtenant_superpdp`, `scell_reconnect_subtenant_superpdp` + `scell_mint_subtenant_widget_token` (signed 24h token for the SuperPDP-only widget) (since v3.1.0)
  • Live HTML document preview: `scell_preview_document` + template color derivation from the email logo: `scell_derive_template_colors_from_email_logo` (since v3.2.0)
  • Tool documentation aligned exactly with the 148 server-side tools (source: backend `config/mcp.php`): added deposit groups (`scell_list_deposit_groups` / `scell_get_deposit_group`), legal-mentions assistant (`scell_suggest_invoice_mentions` / `scell_preview_invoice_mentions`), colors derived from the invoice logo (`scell_derive_template_colors_from_invoice_logo`); phantom names removed / renamed (since v3.5.0)

Installation

Install the official Scell.io SDK for your platform. All three options connect to the same API.

TypeScript / PHP

terminal
typescript
npm install @scell/sdk
# or with yarn
yarn add @scell/sdk
# or with pnpm
pnpm add @scell/sdk

MCP Agent

~/.claude/.mcp.json
json
// Coller ce bloc dans le fichier de config MCP de votre client IA.
// Cree le fichier s'il n'existe pas (chemin selon le client) :
//
//   - Claude Code (CLI)   ~/.claude/.mcp.json
//   - Claude Desktop      ~/Library/Application Support/Claude/claude_desktop_config.json   (macOS)
//                         %APPDATA%\\Claude\\claude_desktop_config.json                  (Windows)
//   - Cursor              ~/.cursor/mcp.json
//   - VS Code (Copilot)   ~/.vscode/mcp.json
//
// Remplacer sk_live_xxxx par votre cle Scell.io (sk_live_* en prod, sk_test_* en sandbox).
// Recuperez vos cles depuis le dashboard : https://app.scell.io/dashboard/api-keys

{
  "mcpServers": {
    "scell": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://api.scell.io/api/mcp",
        "--header",
        "X-Scell-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      ]
    }
  }
}

Authentication

Scell.io supports three authentication modes, each with a dedicated client class.

ModeClient ClassKey PrefixUse Case
Bearer TokenScellClientSanctum tokenDashboard sessions, user-facing operations
Secret API KeyScellApiClient / ScellTenantClientsk_live_* / sk_test_*Server-to-server. Accepted headers: X-API-Key OR X-Tenant-Key (legacy alias).
Publishable KeyScellPublicClientpk_live_* / pk_test_*Public onboarding widget (browser-safe). X-Publishable-Key header.

Bearer Token (Dashboard)

Use ScellClient with a Sanctum bearer token for dashboard user operations. Resources: auth, companies, apiKeys, balance, webhooks, invoices (read), signatures (read).

auth-bearer.ts
typescript
import { ScellClient } from '@scell/sdk';
// Authenticate with a Sanctum bearer token (dashboard sessions)
const client = new ScellClient('your_bearer_token');
// Resources available on ScellClient:
// client.auth — login, register, logout, me
// client.companies — CRUD on companies + KYC
// client.apiKeys — manage API keys
// client.balance — check balance, reload, transactions
// client.webhooks — manage webhooks
// client.invoices — read-only invoice access
// client.signatures — read-only signature access
const me = await client.auth.me();
console.log(me.email);

API Key (Server-to-Server)

Use ScellApiClient with a secret key (sk_live_* or sk_test_*) for full server-to-server operations. Resources: invoices, signatures, creditNotes, subTenants, fiscal, stats, billing, tenantInvoices, tenantCreditNotes, incomingInvoices.

auth-api-key.ts
typescript
import { ScellApiClient } from '@scell/sdk';
// Production
const client = new ScellApiClient('sk_live_your_api_key');
// Sandbox mode (sk_test_ prefix)
const sandbox = new ScellApiClient('sk_test_your_api_key');
// Une cle sk_* appartient au tenant master. Pour cibler un sub-tenant,
// passer sub_tenant_id dans le payload POST (factures, signatures, avoirs).
// Sans sub_tenant_id : action sur le tenant master, company emettrice =
// tenant.default_company_id (configure depuis le dashboard Profil).
// Resources available on ScellApiClient:
// client.invoices — full invoice CRUD + submit + download
// client.signatures — full signature CRUD + cancel + remind
// client.creditNotes — credit note management
// client.subTenants — manage sub-tenants
// client.fiscal — fiscal compliance (ISCA)
// client.stats — overview + monthly statistics
// client.billing — billing, usage, top-up
// client.tenantInvoices — tenant direct invoices (CRUD + submit + download Factur-X/UBL/CII)
// client.tenantCreditNotes — tenant credit notes
// client.incomingInvoices — incoming invoice management

ScellTenantClient — X-Tenant-Key header (legacy alias)

⚠ Important — there is NO separate tk_* key format. ScellTenantClient accepts the exact same secret key sk_live_* / sk_test_* as ScellApiClient. The only difference: it sends the X-Tenant-Key header instead of X-API-Key. Server-side, the TenantApiKeyMiddleware reads X-Tenant-Key ?? X-API-Key and validates the same /^sk_(live|test)_[A-Za-z0-9]{32}$/ regex — so both clients work with any of your sk_* keys. Kept for backwards compatibility; prefer ScellApiClient (using X-API-Key) in new code.

The ScellTenantClient exposes a few extra direct convenience methods: me(), updateProfile(), balance(), quickStats(), regenerateKey(). Everything else (invoices, signatures, sub_tenants…) is strictly identical to ScellApiClient.

auth-tenant.ts
typescript
import { ScellTenantClient } from '@scell/sdk';
// IMPORTANT — meme cle secrete que ScellApiClient (sk_*).
// La SEULE difference : ce client envoie le header X-Tenant-Key
// au lieu de X-API-Key. Il n'existe PAS de format tk_* distinct.
// Production
const client = new ScellTenantClient('sk_live_your_secret_key');
// Sandbox mode
const sandbox = new ScellTenantClient('sk_test_your_secret_key');
// Resources:
// client.directInvoices — create invoices for sub-tenants
// client.directCreditNotes — create credit notes
// client.incomingInvoices — manage incoming invoices
// client.subTenantCreditNotes — sub-tenant credit notes
// client.fiscal — fiscal compliance
// client.billing — billing & usage
// client.stats — statistics
// client.subTenants — manage sub-tenants
// Direct methods on ScellTenantClient:
// client.me() — tenant profile
// client.updateProfile() — update profile
// client.balance() — check balance
// client.quickStats() — summary stats
// client.regenerateKey() — regenerate tenant key

API Reference

Complete method reference for every SDK resource. Code examples in TypeScript and PHP for each resource.

Invoices

13 methods

Create, validate, submit, and download electronic invoices in Factur-X, UBL, and CII formats. Available on ScellApiClient (full CRUD) and ScellClient (read-only).

MethodDescription
list(options?)List invoices with optional pagination and filters
get(id)Get a single invoice by its UUID
create(data)Create a new invoice (Factur-X, UBL, or CII format)
submit(id)Submit an invoice to the PDP network (Peppol / Chorus Pro)
markPaid(id)Mark as paid (manual payment, accepted statuses: validated/transmitted/sent/accepted)
sendByEmail(id, data?)Send the Factur-X invoice by email to the buyer
download(id, format)Download as PDF, XML or Factur-X. format: "pdf" | "xml" | "facturx"
auditTrail(id)Get the full audit trail (creation, validation, submission, etc.)
convert(data)Convert between invoice formats (Factur-X ↔ UBL ↔ CII)
depositGroups(options?)List deposit groups (multi-invoice deals) with progress — filters `has_no_balance`, etc. (since v3.5.0)
depositGroup(id)Detail of a deposit group: deal total, deposits issued, balance, % invoiced (since v3.5.0)
incoming(options?)List incoming invoices (received via PDP)
accept(id, data?)Accept an incoming invoice (PDP)
reject(id, reason, code)Reject an incoming invoice with a reason and code
dispute(id, reason, code)Dispute an incoming invoice

Create an invoice

create.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// ── B2B Invoice (Factur-X) ──────────────────────────────────────────
const { data: invoice } = await scell.invoices.create({
direction: 'outgoing',
output_format: 'facturx', // 'facturx' | 'ubl' | 'cii'
issue_date: '2026-06-01',
due_date: '2026-07-01',
currency: 'EUR',
// Seller (flat fields — server validates per country)
seller_name: 'QR Communication SAS',
seller_siret: '90347842500015', // required if seller_country=FR
seller_vat_number: 'FR42903478425',
seller_country: 'FR',
seller_address: {
line1: '123 Avenue de la République',
postal_code: '75011',
city: 'Paris',
country: 'FR',
},
// Buyer — option 1: flat fields
buyer_name: 'GN IMMO',
buyer_siret: '49438068600076', // required if buyer_country=FR && !buyer_is_individual
buyer_vat_number: 'FR42494380686',
buyer_country: 'FR',
buyer_address: {
line1: '453 Route Nationale 7',
postal_code: '13670',
city: 'Verquières',
country: 'FR',
},
// Amounts (HT + TVA = TTC)
total_ht: 2500.00,
total_tax: 500.00,
total_ttc: 3000.00,
// Lines (min 1)
lines: [
{
description: 'Prestation conseil transformation digitale',
quantity: 10,
unit: 'HUR', // UN/ECE Rec 20 (optional)
unit_price: 250.00,
tax_rate: 20.0, // 0 | 5.5 | 10 | 20
total_ht: 2500.00,
total_ttc: 3000.00,
},
],
});
console.log(invoice.invoice_number); // 'FAC-2026-000042'
console.log(invoice.status); // 'draft'
console.log(invoice.download_url); // presigned S3 URL
// ── B2C Invoice (individual — no SIRET/VAT required) ────────────────
const { data: b2cInvoice } = await scell.invoices.create({
direction: 'outgoing',
output_format: 'facturx',
issue_date: '2026-06-01',
due_date: '2026-06-15',
currency: 'EUR',
seller_name: 'QR Communication SAS',
seller_siret: '90347842500015',
seller_country: 'FR',
seller_address: { line1: '123 Avenue de la République', postal_code: '75011', city: 'Paris', country: 'FR' },
buyer_name: 'Marie Dupont',
buyer_is_individual: true, // BT-46/47/48 omitted, no L441-10
buyer_country: 'FR',
buyer_address: { line1: '12 Rue des Lilas', postal_code: '75011', city: 'Paris', country: 'FR' },
total_ht: 150.00,
total_tax: 30.00,
total_ttc: 180.00,
lines: [{ description: 'Formation individuelle', quantity: 1, unit_price: 150.00, tax_rate: 20.0, total_ht: 150.00, total_ttc: 180.00 }],
});
// ── With buyer_id (registry shortcut) ───────────────────────────────
const { data: fromRegistry } = await scell.invoices.create({
direction: 'outgoing',
output_format: 'facturx',
issue_date: '2026-06-01',
due_date: '2026-07-01',
currency: 'EUR',
seller_name: 'QR Communication SAS',
seller_siret: '90347842500015',
seller_country: 'FR',
seller_address: { line1: '123 Avenue de la République', postal_code: '75011', city: 'Paris', country: 'FR' },
buyer_id: '019e2dbe-362a-7105-87a5-f45fad1382ed', // auto-snapshot
total_ht: 2500.00,
total_tax: 500.00,
total_ttc: 3000.00,
lines: [{ description: 'Consulting', quantity: 10, unit_price: 250.00, tax_rate: 20.0, total_ht: 2500.00, total_ttc: 3000.00 }],
});
// ── Sub-tenant scoping ──────────────────────────────────────────────
const { data: subInvoice } = await scell.invoices.create({
sub_tenant_id: '019d5ea8-0000-0000-0000-000000000000',
direction: 'outgoing',
output_format: 'facturx',
issue_date: '2026-06-01',
due_date: '2026-07-01',
currency: 'EUR',
buyer_id: '019e2dbe-362a-7105-87a5-f45fad1382ed',
total_ht: 1000.00,
total_tax: 200.00,
total_ttc: 1200.00,
lines: [{ description: 'Service', quantity: 1, unit_price: 1000.00, tax_rate: 20.0, total_ht: 1000.00, total_ttc: 1200.00 }],
});
// ── Standard invoice from a quote (parent_quote_id, since v2.21.0) ──
// Lien soft vers un devis source pour tracabilite (devis -> facture).
// IMPORTANT : parent_quote_id est accepte UNIQUEMENT pour invoice_type='standard'
// (ou champ omis). Pour une facture d'acompte ou de solde, utiliser les
// endpoints dedies POST /quotes/{id}/convert-to-deposit / convert-to-balance.
// Backend : 404 PARENT_QUOTE_NOT_FOUND si le devis n'appartient pas au tenant,
// 422 si invoice_type='deposit' ou 'balance' est combine avec parent_quote_id.
const { data: fromQuote } = await scell.invoices.create({
direction: 'outgoing',
output_format: 'facturx',
issue_date: '2026-06-01',
due_date: '2026-07-01',
currency: 'EUR',
parent_quote_id: '019e3f00-7c8d-7000-9000-000000000001', // UUID du devis source
buyer_id: '019e2dbe-362a-7105-87a5-f45fad1382ed',
total_ht: 2500.00,
total_tax: 500.00,
total_ttc: 3000.00,
lines: [{ description: 'Consulting (devis DEV-2026-0042)', quantity: 10, unit_price: 250.00, tax_rate: 20.0, total_ht: 2500.00, total_ttc: 3000.00 }],
});

Update a draft

update.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// Update a draft invoice (only draft status allowed)
// All fields are optional — partial update via PUT /invoices/{id}
const { data: invoice } = await scell.invoices.update(
'019dfae2-abef-73e4-b6b2-e4cdae938f3b',
{
// Change due date
due_date: '2026-08-15',
// Update buyer info
buyer_name: 'GN IMMO — Siège social',
buyer_email: 'comptabilite@gnimmo.com',
// Update buyer address
buyer_address: {
line1: '453 Route Nationale 7',
postal_code: '13670',
city: 'Verquières',
country: 'FR',
},
// Add shipping address (BG-13 EN16931)
buyer_shipping_address: {
name: 'Entrepôt Lyon',
line1: '15 Rue de l\'Industrie',
postal_code: '69003',
city: 'Lyon',
country: 'FR',
},
// Replace lines entirely
lines: [
{
description: 'Audit transformation digitale',
quantity: 5,
unit_price: 300.00,
vat_rate: 20.0,
},
{
description: 'Accompagnement mensuel',
quantity: 3,
unit_price: 500.00,
vat_rate: 20.0,
},
],
}
);
console.log(invoice.status); // still 'draft'

Submit to PDP

submit.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// Submit a draft invoice to the PDP network (Peppol/Chorus Pro)
// Idempotent — calling twice returns the same transmitted_at
const invoice = await scell.invoices.submit('019dfae2-abef-73e4-b6b2-e4cdae938f3b');
console.log(invoice.status); // 'transmitted'
console.log(invoice.transmitted_at); // ISO 8601
// Bulk submit multiple invoices at once
const results = await scell.invoices.bulkSubmit([
'019dfae2-abef-73e4-b6b2-e4cdae938f3b',
'019dfae2-abef-73e4-b6b2-e4cdae938f3c',
]);

Mark as paid

mark-paid.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// Mark an outgoing invoice as paid (manual payment)
// Accepted statuses: validated, transmitted, sent, accepted
// Returns 422 INVOICE_NOT_PAYABLE if status is draft or already paid
const { data: invoice } = await scell.invoices.markPaid(
'019dfae2-abef-73e4-b6b2-e4cdae938f3b'
);
// invoice.status === 'paid'
// invoice.paid_at === '2026-06-15T14:30:00Z'
// invoice.payment_method === 'manual'
// With optional payment details
const { data: detailed } = await scell.invoices.markPaid(
'019dfae2-abef-73e4-b6b2-e4cdae938f3b',
{
payment_reference: 'VIR-2026-0542',
paid_at: '2026-06-14T09:00:00Z',
note: 'Virement reçu — relevé BNP 06/2026',
}
);

Download (PDF / XML / Factur-X)

download.ts
typescript
import { ScellApiClient } from '@scell/sdk';
import { writeFileSync } from 'node:fs';
const scell = new ScellApiClient('sk_live_your_api_key');
// Download as Factur-X (PDF/A-3b + embedded CII XML)
const facturx = await scell.invoices.download(
'019dfae2-abef-73e4-b6b2-e4cdae938f3b',
'facturx'
);
writeFileSync('invoice.pdf', Buffer.from(facturx));
// Download as plain PDF (visual only, no embedded XML)
const pdf = await scell.invoices.download(
'019dfae2-abef-73e4-b6b2-e4cdae938f3b',
'pdf'
);
// Download as raw XML (CII EN16931)
const xml = await scell.invoices.download(
'019dfae2-abef-73e4-b6b2-e4cdae938f3b',
'xml'
);

Delete a draft

delete.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// Delete a draft invoice — only 'draft' status allowed
// Returns 422 if invoice is validated/transmitted/paid (ISCA immutability)
await scell.invoices.delete('019dfae2-abef-73e4-b6b2-e4cdae938f3b');

Default legal mentions & overrides

Scell automatically generates the mandatory legal mentions. Each default is computed if you don't provide a value; a non-empty value overrides it.

  • Footer (legal name, SIRET, VAT, address) — override invoice_footer
  • Payment terms L441-10 (B2B: 3× legal rate penalties + €40) — override payment_terms
  • VAT franchise "TVA non applicable, art. 293 B du CGI" (micro sub-tenant in franchise) — override metadata.vat_exemption_mention
  • Per-line VAT justifications (reverse charge art. 196, exemption art. 261…) — based on the line VAT category

The 293 B mention is legally mandatory while the issuer is in franchise: the override reformulates, it does not remove it. Switch the sub-tenant to vat_status: "liable" via subTenants.updateFiscalStatus(id, …) to move invoicing to VAT.

Document Preview

1 method

Live, non-persisted HTML preview of an invoice, credit note or quote being drafted (POST /api/v1/documents/preview). The rendering uses the real template, branding and legal mentions of the issuing company — ideal for a real-time A4 preview while drafting. Available via client.documents.preview(input) (JS v3.2.0), $client->documents->preview($payload) (PHP v3.4.0) and the MCP tool scell_preview_document (v3.2.0).

MethodDescription
preview(input)Live HTML preview (not persisted) of a document being drafted — required `type` (`invoice` | `credit_note` | `quote`), optional `buyer`, `lines` (max 200), dates, currency, notes, payment terms. Rendered with the real template + branding + legal mentions of the issuing company. Returns the HTML (string). 422 on invalid payload (since v3.2.0 JS / v3.4.0 PHP)
document-preview.ts
typescript
// TypeScript — since v3.2.0
// Live HTML preview of a draft document — nothing is persisted.
// Rendered with the REAL template, branding and legal mentions of the issuer.
const html = await client.documents.preview({
type: 'invoice', // 'invoice' | 'credit_note' | 'quote'
document_number: 'FAC-2026-0042', // optional
buyer: {
name: 'Client SARL',
siret: '98765432109876',
is_individual: false,
address: { line1: '2 av. Lyon', postal_code: '69001', city: 'Lyon', country: 'FR' },
},
lines: [
{ description: 'Prestation conseil', quantity: 2, unit_price: 450.0, tax_rate: 20 },
],
due_date: '2026-07-11', // defaults: due +30d, quote validity +90d
notes: 'Conditions particulières…',
});
// Show it in an iframe for a real-time A4 preview while drafting
// <iframe sandbox="" srcDoc={html} />

Signatures

7 methods

Create simple eIDAS EU-SES electronic signature requests (the only level exposed by Scell.io). Multi-signers (1-10), email/SMS/both authentication, OTP with custom message, percent-or-pixel visual positions, 10-year archiving. The returned signing_url points to the Scell.io wrapper page (https://sign.scell.io/sign/...) which iframes the upstream signature flow with Scell branding by default. Available on ScellApiClient and ScellTenantClient.

MethodDescription
list(options?)List signature requests (filters: status, environment, company_id, sub_tenant_id, per_page max 100). Scoped by tenant via sk_live_* / sk_test_*.
get(id)Get a signature request by ID (strict tenant scope, 404 if belongs to another tenant)
create(data)Create a new eIDAS EU-SES signature request
cancel(id)Cancel a pending signature request
remind(id)Send a reminder to pending signers
download(id, type)Download the signed document or proof file
auditTrail(id)Get the full audit trail for the signature
signatures.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// Read PDF and encode to base64
const pdfBytes = await fs.readFile('contrat.pdf');
const base64Pdf = pdfBytes.toString('base64');
const signature = await scell.signatures.create({
// Required
title: 'Contrat de prestation',
document: base64Pdf,
document_name: 'contrat.pdf',
signers: [
{
first_name: 'Marie',
last_name: 'Dupont',
email: 'marie.dupont@example.com',
auth_method: 'email',
// Custom message with {OTP} placeholder (max 500 chars)
message: 'Bonjour Marie, votre contrat est pret. Code OTP: {OTP}',
},
],
// Optional: where the signature box is dropped on the PDF
signature_positions: [
{ page: 1, x: 70, y: 80, unit: 'percent' }, // unit: 'percent' (default) | 'pixel'
],
// Optional: redirects after signing / cancelling
redirect_complete_url: 'https://yourapp.com/contracts/done',
redirect_cancel_url: 'https://yourapp.com/contracts/cancelled',
// Optional: 10-year eIDAS archiving
archive_enabled: true,
});
console.log(signature.id);
console.log(signature.status); // 'pending' | 'waiting_signers' | ...
console.log(signature.signers[0].signing_url); // https://sign.scell.io/sign/{id}/{signerId}?...&signature=HMAC
// Cancel a signature request
await scell.signatures.cancel(signature.id);
// Send a reminder to pending signers
await scell.signatures.remind(signature.id);
// Download the signed document
const signed = await scell.signatures.download(signature.id, 'signed');
// List signatures — scope automatique au tenant courant via sk_live_* / sk_test_*.
// Filtres disponibles : status, environment ('production' | 'sandbox'), company_id,
// sub_tenant_id (anti-IDOR — limité aux sub_tenants du tenant courant), per_page (max 100).
const pending = await scell.signatures.list({
status: 'pending',
environment: 'production',
sub_tenant_id: '019d5ea8-0000-0000-0000-000000000000',
per_page: 50,
});
// --- Demande de signature au nom d'un sub-tenant (depuis 2026-05-11) ---
// Passer sub_tenant_id dans le payload POST. La company emettrice
// devient la 1re company du sub-tenant. 404 SUB_TENANT_NOT_FOUND si
// le sub_tenant n'appartient pas au tenant courant.
const signatureSub = await scell.signatures.create({
sub_tenant_id: '019d5ea8-0000-0000-0000-000000000000',
title: 'Contrat de prestation',
document: base64Pdf,
document_name: 'contrat.pdf',
signers: [{ first_name: 'Marie', last_name: 'Dupont', email: 'marie@example.com', auth_method: 'email' }],
});

Signature page customization (white-label)

The ui_config payload (21 fields aligned with EU-SES eIDAS v1.0.17 spec) and signature_options (4 behavioural fields) let you fully white-label the page your signers see. Everything is optional: if you omit ui_config, the backend applies the Scell.io palette. If you provide a subset, only the missing fields are filled with defaults — your overrides are preserved. iframe_ancestors is automatically extended with https://sign.scell.io and https://scell.io (capped at 20, deduplicated) so the wrapper can embed the upstream page.

ui_config — 21 fields

FieldTypeDescription
Sidebar (4)
sidebar_logoURLLogo displayed in the sidebar (HTTPS, max 500 chars).
sidebar_background_color#RRGGBBSidebar background color.
sidebar_title_color#RRGGBBTitle color in the sidebar.
sidebar_text_color#RRGGBBText color in the sidebar.
Header (3)
header_background_color#RRGGBBHeader background color.
header_title_color#RRGGBBHeader title color.
header_subtitle_color#RRGGBBHeader subtitle color.
Footer (1)
footer_background_color#RRGGBBFooter background color.
Standard buttons (4)
button_text_color#RRGGBBButton text color.
button_text_color_hover#RRGGBBButton text color on hover.
button_background_color#RRGGBBButton background color.
button_background_color_hover#RRGGBBButton background color on hover.
Sign button (4) — overrides standard buttons
sign_button_text_color#RRGGBBSign button text color.
sign_button_text_color_hover#RRGGBBSign button text color on hover.
sign_button_background_color#RRGGBBSign button background color.
sign_button_background_color_hover#RRGGBBSign button background color on hover.
Visibility toggles (4)
hide_sidebarbooleanHides the sidebar entirely.
hide_headerbooleanHides the header.
hide_download_validatedbooleanHides the download button after OTP validation.
hide_download_signedbooleanHides the download button after signing.
Iframe (1)
iframe_ancestorsURL[]Domains allowed to embed the page (max 20). The backend automatically adds sign.scell.io and scell.io.

signature_options — 4 behavioural fields

FieldTypeDescription
signature_mode'typed' | 'drawn' | 'both'Signature input mode. typed = keyboard, drawn = drawn, both = signer chooses.
signer_must_readbooleanForces the signer to scroll through the entire document before the Sign button activates.
user_editable_data{name?, mobile?, email?}Fields the signer can edit on their own data (per-field booleans).
timezonestringIANA identifier (e.g. Europe/Paris). Affects displayed timestamps and the audit trail PDF.

Other notable fields

  • signers[].message (max 500 chars) — custom message sent by email/SMS to the signer. Supports the {OTP} placeholder, replaced by the 6-digit OTP code.
  • signers[].auth_method = 'email' | 'sms' | 'both' — OTP delivery method. both sends the OTP by email AND SMS (extra security).
  • signers[].order — signing order for sequential mode (1, 2, 3...). If absent, array order is used.
  • signature_positions[].unit = 'percent' (default) or 'pixel'. If pixel, you can provide page_width_px / page_height_px to override the auto-detection via PDF parser (fallback A4 595×842).
  • archive_enabled — enables 10-year eIDAS-compliant archiving.

Signature blocks — initials (paraphe), legal mentions, today date

3 optional blocks that Scell burns onto the PDF before forwarding to the certified EU-SES signature service (which doesn't natively support handwritten mentions). The original PDF is preserved for audit trail.

initials_blockauto-paraphe multi-page(positions[] since v2.17.0 JS / v2.15.0 PHP / v2.16.0 MCP)
FieldTypeDescription
enabledbooleanEnables or disables the initials block.
mode'auto' | 'custom''auto' = initials from 1st signer's name; 'custom' = use custom_text.
custom_textstringCustom text (max 8 chars). Required when source=custom.
positions[]{page, x, y, ...}[]New recommended format. One entry per page with its own position (x, y, unit) and optional overrides (font_size, color, bold). Max 500 entries. When provided, takes precedence over legacy position+pages.
position{x, y, unit}Legacy. Common position applied to all pages from `pages`. Ignored when positions[] is provided.
pages'all' | 'except_last' | number[]Legacy. Page selector for common position. Ignored when positions[] is provided.
font_sizenumber (6-20)Block default, overridable by positions[].font_size.
color#RRGGBBBlock default, overridable per-position.
boldbooleanBlock default, overridable per-position.
  • mentions[] — array of legal mentions (max 20). Each entry: {label, signer_index?, position: {page, x, y}, required?, fallback_text?}. Burned directly by Scell using label or fallback_text.
  • date_block — today's date pre-filled (IANA timezone). position.page accepts a 1-indexed integer OR the string 'last' (last page).
  • Full reference — see scell-api-llms.txt section "Signature blocks (v2.12.0)".

Full example — white-label customization + blocks

signatures-full.ts
typescript
// White-label override with ui_config (21 fields) and signature_options.
// Any field omitted falls back to Scell.io defaults.
const signature = await scell.signatures.create({
title: 'Contrat de prestation',
document: base64Pdf,
document_name: 'contrat.pdf',
signers: [
{ first_name: 'Marie', last_name: 'Dupont', phone: '+33612345678', auth_method: 'sms' },
{ first_name: 'Jean', last_name: 'Martin', email: 'jean@example.com', auth_method: 'email', order: 2 },
],
signature_positions: [
{ page: 1, x: 70, y: 80, width: 20, height: 8, unit: 'percent' },
],
ui_config: {
// Sidebar (4)
sidebar_logo: 'https://yourcdn.com/logo.svg',
sidebar_background_color: '#0F172A',
sidebar_title_color: '#FFFFFF',
sidebar_text_color: '#CBD5E1',
// Header (3)
header_background_color: '#FFFFFF',
header_title_color: '#0F172A',
header_subtitle_color: '#475569',
// Footer (1)
footer_background_color: '#F8FAFC',
// Standard buttons (4)
button_text_color: '#FFFFFF',
button_text_color_hover: '#FFFFFF',
button_background_color: '#6366F1',
button_background_color_hover: '#4F46E5',
// Sign button (4) — overrides standard buttons for the Sign action
sign_button_text_color: '#FFFFFF',
sign_button_text_color_hover: '#FFFFFF',
sign_button_background_color: '#10B981',
sign_button_background_color_hover: '#059669',
// Visibility toggles (4)
hide_sidebar: false,
hide_header: false,
hide_download_validated: false,
hide_download_signed: false,
// Iframe ancestors (max 20). Scell auto-injects sign.scell.io + scell.io.
iframe_ancestors: ['https://app.acme.com', 'https://acme.com'],
},
signature_options: {
// 'typed' (keyboard only) | 'drawn' (mouse/finger only) | 'both' (signer chooses)
signature_mode: 'both',
// Force scrolling through the entire document before sign button enables
signer_must_read: true,
// Per-field control over what the signer can edit on their own data
user_editable_data: { name: false, mobile: true, email: false },
// IANA timezone identifier
timezone: 'Europe/Paris',
},
// Initials block — paraphe burned by Scell on each page BEFORE forwarding
// to the certified EU-SES signature service. Two formats accepted (v2.17.0+):
//
// 1. NEW — positions[] (one entry per page, recommended for multi-page)
// 2. LEGACY — position + pages (same position on all pages)
//
// If both are provided, positions[] wins.
initials_block: {
enabled: true,
mode: 'auto', // 'auto' (initials from signer name) | 'custom'
source: 'signer_name',
font_size: 10, // block default (overridable per-position)
color: '#1a1a1a', // block default (overridable per-position)
bold: false, // block default (overridable per-position)
positions: [
{ page: 1, x: 90, y: 90, unit: 'percent' },
{ page: 2, x: 88, y: 92, unit: 'percent', font_size: 12 },
{ page: 3, x: 85, y: 90, unit: 'percent', color: '#aa0000' },
],
},
// Mentions — legal mentions burned on the PDF (max 20)
mentions: [
{
label: 'Lu et approuvé',
required: true,
signer_index: 0,
position: { page: 1, x: 10, y: 80, unit: 'percent' },
font_size: 9,
color: '#333333',
},
],
// Date block — today's date in the tenant timezone
date_block: {
enabled: true,
format: 'dd/MM/yyyy',
timezone: 'Europe/Paris',
position: { page: 'last', x: 70, y: 88, unit: 'percent' },
font_size: 9,
color: '#555555',
},
redirect_complete_url: 'https://yourapp.com/contracts/done',
redirect_cancel_url: 'https://yourapp.com/contracts/cancelled',
expires_at: '2026-12-31T23:59:59Z',
archive_enabled: true,
external_id: 'CRM-INV-2026-0042',
});

Credit Notes

8 methods

Create credit notes linked to existing invoices. Supports partial and full refunds. Available on ScellApiClient.

MethodDescription
list(options?)List credit notes with pagination and filters
get(id)Get a credit note by UUID
create(data)Create a credit note linked to an invoice. `type` (`partial` | `total`, NOT `full`) required. For a PARTIAL credit note you MUST SELECT lines of the source invoice via `items[].invoice_line_id` — each line's price and EXACT VAT rate are inherited (an invoice may mix 20% / 5.5% / exempt 0%). Call `remainingCreditable` first.
update(id, data)Update a draft credit note
delete(id)Delete a draft credit note (ISCA: draft only)
send(id)Send the credit note to the recipient (irreversible)
download(id)Download credit note as PDF
remainingCreditable(invoiceId)List the lines still creditable (remaining quantity + per-line VAT) — discovery step before a partial credit note. Returns `data.items[]` (invoice_line_id, remaining_quantity, tax_rate, remaining_amount_ht) + `can_be_credited`.
credit-notes.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// ── Total credit note (refund entire invoice) ───────────────────────
const { data: totalCN } = await scell.creditNotes.create({
invoice_id: '019dfae2-abef-73e4-b6b2-e4cdae938f3b',
reason: 'Annulation de la prestation',
type: 'total',
});
// totalCN.status === 'draft'
// totalCN.credit_note_number === 'AVO-2026-000005'
// ── Partial credit note (specific lines) ────────────────────────────
const { data: partialCN } = await scell.creditNotes.create({
invoice_id: '019dfae2-abef-73e4-b6b2-e4cdae938f3b',
reason: 'Remboursement partiel — 2 articles retournés',
type: 'partial',
items: [
{
invoice_line_id: '019dfae2-bbbb-0000-0000-000000000001',
quantity: 2, // credit 2 of original quantity
},
],
});
// ── Check remaining creditable amount ───────────────────────────────
const { data: remaining } = await scell.creditNotes.remainingCreditable(
'019dfae2-abef-73e4-b6b2-e4cdae938f3b'
);
// remaining.total_ht, remaining.total_ttc, remaining.lines[]
// ── Update a draft credit note ──────────────────────────────────────
const { data: updated } = await scell.creditNotes.update(partialCN.id, {
reason: 'Remboursement partiel — 3 articles retournés (corrigé)',
});
// ── Send credit note (irreversible — enters ISCA chain) ─────────────
await scell.creditNotes.send(partialCN.id);
// ── Download as PDF ─────────────────────────────────────────────────
const pdf = await scell.creditNotes.download(partialCN.id);
// ── Delete a draft (only draft status — ISCA compliance) ────────────
await scell.creditNotes.delete(totalCN.id);
// ── List with filters ───────────────────────────────────────────────
const { data: list, meta } = await scell.creditNotes.list({
status: 'draft',
per_page: 25,
});

Companies

7 methods

Manage company entities with legal information (SIREN, VAT number, address). Includes KYC/KYB verification flow. Available on ScellClient (Bearer token).

MethodDescription
list()List all companies for the authenticated user
get(id)Get a company by ID
create(data)Register a new company with legal details
update(id, data)Update company information
delete(id)Delete a company
initiateKyc(id)Start KYC/KYB identity verification process
kycStatus(id)Check the current KYC verification status
companies.ts
typescript
import { ScellClient } from '@scell/sdk';
const client = new ScellClient('your_bearer_token');
// Create a company
const company = await client.companies.create({
name: 'ACME Corp',
siren: '123456789',
vat_number: 'FR12345678901',
address: {
street: '10 Rue de la Paix',
city: 'Paris',
postal_code: '75002',
country: 'FR',
},
});
// List all companies
const companies = await client.companies.list();
// Update a company
await client.companies.update(company.id, { name: 'ACME Corp Updated' });
// Initiate KYC/KYB verification
await client.companies.initiateKyc(company.id);
// Check KYC status
const kyc = await client.companies.kycStatus(company.id);
console.log(kyc.status); // 'pending' | 'approved' | 'rejected'

Country reference

2 methods

Authenticated reference of companies per country: VAT number, national registration identifier (register + format) and known legal forms. Lets you adapt a buyer/seller input form to the selected country. Available on ScellClient, ScellApiClient and ScellPublicClient.

MethodDescription
countries()List all catalogued countries (VAT, national ID, legal forms)
country(code)Get a country reference by ISO 3166-1 alpha-2 code (e.g. FR, DE)
reference.ts
typescript
import { ScellClient } from '@scell/sdk';
const client = new ScellClient(process.env.SCELL_API_KEY!);
// Référentiel sociétés par pays (GET /v1/reference/countries, authentifié).
// Permet d'adapter un formulaire de saisie acheteur/vendeur au pays choisi.
// 1. Lister tous les pays catalogués
const countries = await client.reference.countries();
// 2. Récupérer un pays précis (code ISO 3166-1 alpha-2)
const fr = await client.reference.country('FR');
console.log(fr);
// {
// code: 'FR',
// name: 'France',
// known: true,
// is_eu: true,
// currency: 'EUR',
// vat: {
// label: 'Numéro de TVA intracommunautaire',
// example: 'FR12345678901',
// regex: '^FR[A-Z0-9]{2}\\d{9}$', // utilisable via new RegExp(regex)
// vies_checkable: true, // vérifié contre le registre VIES
// },
// national_id: {
// label: 'SIREN / SIRET',
// scheme: '0002', // code ISO 6523 du registre
// example: '12345678901234',
// regex: '^(\\d{9}|\\d{14})$',
// required_for_b2b: true,
// },
// legal_forms: [
// { code: 'SAS', label: 'SAS — Société par actions simplifiée' },
// { code: 'SARL', label: 'SARL — Société à responsabilité limitée' },
// // ...
// ],
// }
// 3. Construire dynamiquement un formulaire adapté au pays sélectionné
const de = await client.reference.country('DE');
const idLabel = de.national_id.label; // "Handelsregisternummer"
const idRegex = de.national_id.regex; // null => saisie libre acceptée
const legalFormOptions = de.legal_forms; // pour une liste déroulante
const validate = idRegex ? new RegExp(idRegex) : null;
// Un pays non catalogué renvoie known=false + un format permissif (regex null) :
// le client doit alors accepter la saisie libre, sans bloquer.
const xx = await client.reference.country('ZZ');
console.log(xx.known); // false

Response schema

Each country returns the vat block, the national_id block (register + format) and the legal_forms list. The regex values are anchored, directly usable patterns (new RegExp(regex)); a null on national_id.regex means "format unverified" → accept free input. An uncatalogued country returns known: false.

CountryReference
typescript
interface CountryReference {
code: string; // ISO 3166-1 alpha-2 (FR, DE, BE, …)
name: string | null; // nom du pays
known: boolean; // false = pays non catalogué (fallback permissif)
is_eu: boolean; // appartenance à l'Union européenne
currency: string | null; // devise ISO 4217 (EUR, SEK, …)
vat: {
label: string; // libellé localisé ("Numéro de TVA intracommunautaire")
example: string | null; // exemple ("FR12345678901")
regex: string | null; // motif ancré JS-compatible, null si non vérifié
vies_checkable: boolean; // vérifiable contre le registre VIES (UE)
};
national_id: {
label: string; // libellé du registre ("SIREN / SIRET", "KvK-nummer", …)
scheme: string | null; // code ISO 6523 / Peppol EAS ("0002" SIRENE, "0106" KvK)
example: string | null; // exemple ("12345678901234")
regex: string | null; // motif ancré, null = format non vérifié → saisie libre
required_for_b2b: boolean; // identifiant requis en B2B pour ce pays
};
legal_forms: Array<{
code: string; // code stable ("SAS", "GMBH", "LTD", …)
label: string; // libellé pour une liste déroulante
}>;
}

API Keys

4 methods

Create and manage API keys for server-to-server authentication. Keys can be live (sk_live_*) or sandbox (sk_test_*). The secret key value is only shown once at creation time. Available on ScellClient (Bearer token).

MethodDescription
list()List all API keys for the account
get(id)Get an API key by ID (key value is masked)
create(data)Create a new API key (live or test environment)
delete(id)Revoke and delete an API key
api-keys.ts
typescript
import { ScellClient } from '@scell/sdk';
const client = new ScellClient('your_bearer_token');
// Create a new API key
const key = await client.apiKeys.create({
name: 'Production Key',
environment: 'live', // 'live' | 'test'
});
console.log(key.id);
console.log(key.key); // sk_live_... (shown only once)
// List all API keys
const keys = await client.apiKeys.list();
// Delete an API key
await client.apiKeys.delete(key.id);

Webhooks

8 methods

Manage webhook endpoints to receive real-time notifications. Each webhook has a signing secret (wh_sec_*) for HMAC-SHA256 payload verification. Supports event filtering, testing, and delivery logs. Available on ScellClient (Bearer token).

MethodDescription
list()List all webhook endpoints
get(id)Get webhook details by ID
create(data)Create a new webhook endpoint with event subscriptions
update(id, data)Update webhook URL, events, or active status
delete(id)Delete a webhook endpoint
test(id)Send a test event payload to the webhook URL
regenerateSecret(id)Regenerate the webhook signing secret (wh_sec_...)
logs(id)View delivery logs and response codes for a webhook
webhooks.ts
typescript
import { ScellClient } from '@scell/sdk';
const client = new ScellClient('your_bearer_token');
// Create a webhook endpoint
const webhook = await client.webhooks.create({
url: 'https://yourapp.com/webhooks/scell',
events: [
'invoice.created',
'invoice.validated',
'signature.completed',
'balance.low',
],
active: true,
});
console.log(webhook.id);
console.log(webhook.secret); // wh_sec_... (for signature verification)
// List all webhooks
const list = await client.webhooks.list();
// Update a webhook
await client.webhooks.update(webhook.id, {
events: ['invoice.created', 'signature.completed'],
});
// Test a webhook (sends a test payload)
await client.webhooks.test(webhook.id);
// Regenerate the signing secret
const newSecret = await client.webhooks.regenerateSecret(webhook.id);
// View delivery logs
const logs = await client.webhooks.logs(webhook.id);
// Delete a webhook
await client.webhooks.delete(webhook.id);

Fiscal Compliance

26 methods

Full self-certified ISCA fiscal compliance suite (Article 286-I-3° bis French Tax Code). Bulletproof ledger since v0.6.0: ISCA chains isolated per (tenant_id, sub_tenant_id) pair, PostgreSQL triggers as defence in depth, SHA-256 hash chain, daily/monthly/annual closings (1 per tenant + 1 per active sub_tenant), RFC 3161 TSA anchoring + free Bitcoin blockchain anchoring via OpenTimestamps (best effort), FEC export, emergency kill switch, rules engine, integrity health check (gaps, orphans, chain_invalid) and nominative ISCA self-attestation per tenant or sub_tenant. Available on ScellApiClient and ScellTenantClient.

MethodDescription
compliance()Get overall fiscal compliance status (ISCA)
entries()List immutable SHA-256 hash chain ledger entries
closings(options?)List closings. `options.closing_type` (daily | monthly | annual), `options.sub_tenant_id` (a sub-tenant's ISCA chain, anti-IDOR). Each item carries a `download_url`.
downloadClosing(closingId)Download a closing's CSV (market format, daily/monthly/annual incl. sub-tenant). Strict tenant scope (404 cross-tenant).
dailyClosing()Trigger an immediate daily closing
integrity()Run a full hash chain integrity check
integrityHistory()Get history of all integrity verifications
integrityForDate(date)Verify integrity for a specific date
rules()List all fiscal automation rules
createRule(data)Create a new fiscal rule (key, value, description)
updateRule(id, data)Update an existing fiscal rule
ruleDetail(key)Get details of a fiscal rule by key
ruleHistory(key)Get change history for a fiscal rule
exportRules()Export all fiscal rules as a downloadable file
replayRules()Replay all fiscal rules (recompute derived data)
anchors()List RFC 3161 TSA timestamp anchors
killSwitchStatus()Get kill switch status (emergency shutdown)
killSwitchActivate(input)Activate the kill switch — fiscal:admin scope + step-up (reason ≥ 20 chars, email confirmation in prod)
killSwitchDeactivate(input)Deactivate the kill switch — same step-up (reason ≥ 20 chars + prod confirmation)
fecExport()Generate FEC export for French tax authorities
forensicExport()Generate forensic export with complete audit data
attestation(year)Get ISCA compliance attestation for a given year
attestationDownload(year)Download the attestation PDF for a given year
iscaSelfAttestationDownload(subTenantId?)Download the NOMINATIVE ISCA self-attestation (PDF) for the tenant or a specific sub_tenant — since v1.17.0 (JS) / v1.16.0 (PHP)
iscaMeasuresRegisterDownload()Download the ISCA measures register (non-nominative PDF)
iscaTechnicalDossierDownload()Download the ISCA technical dossier compliant with NF Z 42-025 (non-nominative PDF)
integrityStatus()Ledger integrity summary status (gaps, orphans, chain_invalid) — /api/v1/fiscal/integrity endpoint
fiscal.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// ─── Compliance overview ───────────────────────────────
const status = await scell.fiscal.compliance();
console.log(status.is_compliant); // true | false
console.log(status.isca_status);
// ─── Immutable ledger entries ──────────────────────────
const entries = await scell.fiscal.entries();
// ─── Closings ──────────────────────────────────────────
const closings = await scell.fiscal.closings();
await scell.fiscal.dailyClosing(); // Trigger a daily closing
// ─── Integrity verification ───────────────────────────
const integrity = await scell.fiscal.integrity();
const history = await scell.fiscal.integrityHistory();
const forDate = await scell.fiscal.integrityForDate('2026-03-30');
// ─── Fiscal rules engine ──────────────────────────────
const rules = await scell.fiscal.rules();
const newRule = await scell.fiscal.createRule({
key: 'auto_daily_closing',
value: true,
description: 'Automatically trigger daily closings at midnight',
});
await scell.fiscal.updateRule(newRule.id, { value: false });
const detail = await scell.fiscal.ruleDetail('auto_daily_closing');
const ruleHist = await scell.fiscal.ruleHistory('auto_daily_closing');
await scell.fiscal.exportRules();
await scell.fiscal.replayRules();
// ─── TSA Anchors ──────────────────────────────────────
const anchors = await scell.fiscal.anchors();
// ─── Kill switch (emergency halt) ─────────────────────
const ksStatus = await scell.fiscal.killSwitchStatus();
// activate/deactivate require the fiscal:admin scope + a reason >= 20 chars.
// In production, the 1st call returns 403 OOB_CONFIRMATION_REQUIRED and emails
// a confirmation_token — re-call with it to apply.
await scell.fiscal.killSwitchActivate({
reason: 'Incident de securite confirme, gel des emissions',
// confirmation_token: '...', // production only
});
await scell.fiscal.killSwitchDeactivate({
reason: 'Incident resolu, reprise de la facturation normale',
// confirmation_token: '...',
});
// ─── Exports ──────────────────────────────────────────
const fec = await scell.fiscal.fecExport();
const forensic = await scell.fiscal.forensicExport();
const attestation = await scell.fiscal.attestation(2026);
const download = await scell.fiscal.attestationDownload(2026);
// ─── ISCA self-attestation (NOMINATIVE — depuis v1.17.0) ──
// Auto-attestation pour le tenant courant (donnees KYB)
const tenantPdf = await scell.fiscal.iscaSelfAttestationDownload();
fs.writeFileSync('attestation-tenant.pdf', Buffer.from(tenantPdf));
// Auto-attestation pour un sub_tenant specifique
const subPdf = await scell.fiscal.iscaSelfAttestationDownload('019d5ea8-...');
fs.writeFileSync('attestation-sub-tenant.pdf', Buffer.from(subPdf));
// Documents de conformite (non-nominatifs)
const measures = await scell.fiscal.iscaMeasuresRegisterDownload();
const dossier = await scell.fiscal.iscaTechnicalDossierDownload();

Stats

2 methods

Get aggregated and monthly statistics for invoices, signatures, and revenue. Available on ScellApiClient and ScellTenantClient.

MethodDescription
overview()Get aggregated statistics (total invoices, signatures, revenue)
monthly()Get month-by-month breakdown of activity and revenue
stats.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// Get overview statistics
const overview = await scell.stats.overview();
console.log(overview.total_invoices);
console.log(overview.total_signatures);
console.log(overview.total_revenue);
// Get monthly breakdown
const monthly = await scell.stats.monthly();
monthly.forEach(m => {
console.log(m.month, m.invoices_count, m.revenue);
});

Billing, balance & top-up

8 methods

Single entry point for Scell.io billing — since v2.2.0 this resource replaces the old BalanceResource (removed: /api/v1/balance/* endpoints now return 404). Covers current balance (usage()), Stripe top-up (topUp() + confirmTopUp()), transaction history (transactions()), the list of Scell.io invoices issued to the tenant (invoices()) and online invoice payment (payInvoice() → Stripe PaymentIntent). Available on ScellApiClient and ScellTenantClient.

MethodDescription
invoices()List all billing invoices (Scell.io charges to your account)
showInvoice(id)Get details of a specific billing invoice
downloadInvoice(id)Download a billing invoice as PDF
usage()Get current period usage metrics (invoices, signatures, credits)
topUp(data)Initiate a credit top-up
confirmTopUp(data)Confirm a pending top-up with confirmation code
payInvoice(id)Initiate Stripe payment for a Scell.io billing invoice (PaymentIntent + client_secret for Stripe.js)
transactions()List all billing transactions
billing.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// List billing invoices
const invoices = await scell.billing.invoices();
// Get a specific billing invoice
const inv = await scell.billing.showInvoice('billing_inv_id');
// Download billing invoice PDF
const pdf = await scell.billing.downloadInvoice('billing_inv_id');
// Get current usage metrics
const usage = await scell.billing.usage();
console.log(usage.invoices_used);
console.log(usage.signatures_used);
console.log(usage.credits_remaining);
// Top up credits
await scell.billing.topUp({ amount: 100, payment_method: 'card' });
// Confirm a pending top-up
await scell.billing.confirmTopUp({ top_up_id: 'tu_id', confirmation_code: '1234' });
// Pay a Scell.io billing invoice (depuis v2.2.0 - Wave 6 2026-05-10)
// Initiates a Stripe PaymentIntent. Use the client_secret with Stripe.js
// to complete the payment client-side; webhook marks invoice as paid.
const intent = await scell.billing.payInvoice('billing_inv_id');
console.log(intent.clientSecret); // Pass to stripe.confirmCardPayment()
console.log(intent.paymentIntentId); // pi_xxx
console.log(intent.amount); // amount in cents (EUR)
console.log(intent.status); // requires_payment_method | requires_confirmation
// List billing transactions
const transactions = await scell.billing.transactions();

Auth & Tenant Management

15 methods

Dashboard user signup / login AND partner tenant account management. Includes sk_* / pk_* key creation, profile + "self" Company update (logo, IBAN, Factur-X mentions), and S3 presigned logo upload. Available on ScellClient (Sanctum Bearer) and ScellTenantClient.

MethodDescription
register(data)Register a new dashboard user
login(data)Login (Sanctum Bearer token)
logout()Logout + revoke token
me()Get the authenticated user profile
forgotPassword(email)Send a password reset email
resetPassword(token, password)Reset password using the token received by email
tenantLogin(data)Partner tenant login (separate from dashboard user)
tenantRegister(data)Partner tenant registration
tenantMe()Current tenant profile
tenantLogout()Tenant logout + revoke token
tenantKeys()List tenant keys (sk_/pk_)
createSecretKey()Create a new tenant secret key (sk_live_* / sk_test_*)
createPublishableKey()Create a tenant publishable key (pk_live_* / pk_test_*)
updateTenantProfile(data)Update tenant profile + "self" Company (logo, IBAN, Factur-X mentions)
logoUploadUrl()Get a presigned S3 URL to upload the self Company logo
regenerateApiKey()Regenerate the tenant API key

Sub-Tenant Direct Invoices

14 methods

Issue Factur-X / UBL / CII invoices on behalf of a sub_tenant (B2B2B). Includes Schematron validation, PDP/Peppol submission, email send, scoped download, bulk operations, and remaining-creditable lookup. Available on ScellApiClient (sk_*) and ScellTenantClient (tk_*).

MethodDescription
list(subTenantId, options?)List invoices for a sub_tenant
get(id)Get a tenant invoice by UUID
create(subTenantId, data)Create an invoice on behalf of a sub_tenant
update(id, data)Update a draft invoice
delete(id)Delete a draft invoice
submit(id)Submit the invoice to the PDP/Peppol network
validate(id)Validate the invoice without submitting (Factur-X Schematron)
send(id, data?)Send the invoice via email to the buyer
download(id, format?)Download as facturx | pdf | xml
downloadForSubTenant(subTenantId, id, format?)Strict sub_tenant-scoped download
remainingCreditable(id)Remaining creditable amount on the invoice
bulkCreate(subTenantId, data[])Create multiple invoices in bulk
bulkSubmit(ids[])Submit multiple invoices to PDP in bulk
bulkStatus(ids[])Current status of multiple invoices

Incoming Invoices (Sub-Tenant)

8 methods

Manage invoices received by a sub_tenant via the PDP network — full accept / reject / dispute / mark-paid flow. Also supports manually recording invoices received outside PDP (paper, email, etc.).

MethodDescription
create(subTenantId, data)Record an incoming invoice received outside PDP (manual entry)
listForSubTenant(subTenantId, options?)List incoming invoices for a sub_tenant
get(id)Get an incoming invoice by UUID
accept(id, data?)Accept an incoming invoice (PDP)
reject(id, reason)Reject an incoming invoice with a reason
markPaid(id, data?)Mark as paid (date, reference)
delete(id)Delete a draft incoming invoice
download(id, format?)Download incoming invoice (facturx | pdf | xml)

Tenant Credit Notes (multi sub_tenants)

8 methods

Credit notes issued on behalf of a sub_tenant. Strictly inherits buyer/seller fields from the parent invoice (ISCA compliance — no override allowed).

MethodDescription
list(options?)List all credit notes (tenant + sub_tenants)
get(id)Get a credit note by UUID
create(data)Create a credit note linked to an invoice
update(id, data)Update a draft credit note
delete(id)Delete a draft credit note
send(id, data?)Send the credit note via email
download(id, format?)Download as facturx | pdf | xml
remainingCreditable(invoiceId)Remaining creditable amount on an invoice

Invoice Templates

8 methods

Factur-X PDF customization: logo, colors, footer, legal mentions. Explicit cascade > sub_tenant default > tenant default > system default. 5 min Redis cache. New is_enabled field (bool, default true): when false, the template is skipped by the cascade (fallback to the system template). deriveColorsFromEmailLogo() derives primary/accent colors from the email logo (since v3.2.0 JS / v3.4.0 PHP).

MethodDescription
list(options?)List templates (system / tenant / sub_tenant scope)
get(id)Get a template by UUID
create(data)Create a custom template (logo, colors, mentions)
update(id, data)Update a template
delete(id)Delete a template
markDefault(id)Mark a template as default (PUT /:id/default)
uploadLogo(id, file)Upload a logo for the template (multipart)
deriveColorsFromEmailLogo()Derive primary/accent colors from the email logo and apply them to the tenant's default template. 404 if no email logo, 422 if extraction fails (since v3.2.0 JS / v3.4.0 PHP)
deriveColorsFromInvoiceLogo()Extract the `{ primary_color, accent_color }` palette from the invoice (template) logo — returned without persisting (since v3.5.0)
preview(branding?)HTML/PDF preview of a sample invoice with a given branding — omitted fields fall back to the default template (cascade) (since v3.5.0)

Buyers (registry)

5 methods

Reusable buyer registry, strictly scoped per (tenant_id, sub_tenant_id). Reference a buyer via buyer_id in invoice creation to avoid re-entering data. Supports B2B (SIRET/SIREN/VAT) and B2C (individual is_individual=true) + BG-13 ship-to address.

MethodDescription
list(options?)List buyers from the registry (paginated, q / is_individual filters)
get(id)Get a buyer by UUID
create(data)Register a new buyer (B2B SIRET or B2C individual)
update(id, data)Update buyer data (PATCH)
delete(id)Delete a buyer (soft delete)
resolveVatContext(data)Pre-resolve the VAT context of a line (rate, category, EN16931 code, exemption_reason, CGI justification)
buyers.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// Create a B2B buyer (French company — SIRET required)
const buyer = await scell.buyers.create({
name: 'GN IMMO',
is_individual: false,
siret: '49438068600076',
vat_number: 'FR42494380686',
email: 'contact@gnimmo.com',
country: 'FR',
billing_address: {
line1: '453 Route Nationale 7',
postal_code: '13670',
city: 'Verquières',
country: 'FR',
},
});
// Create a B2C buyer (individual — no SIRET/VAT)
const individual = await scell.buyers.create({
name: 'Marie Dupont',
is_individual: true,
email: 'marie.dupont@gmail.com',
country: 'FR',
billing_address: {
line1: '12 Rue des Lilas',
postal_code: '75011',
city: 'Paris',
country: 'FR',
},
});
// Use buyer_id when creating invoices (auto-snapshot)
const invoice = await scell.invoices.create({
buyer_id: buyer.id,
due_date: '2026-07-15',
lines: [
{ description: 'Consulting', quantity: 1, unit_price: 2500, tax_rate: 20 },
],
});

Products (catalogue)

5 methods

Reusable products and services catalogue, scoped per (tenant_id, sub_tenant_id). Reference a product via product_id in an invoice or quote line to auto-fill all fields (description, unit_price_ht, tax_rate, unit, revenue_category). Use save_to_catalog: true on a line to add an item to the catalogue at the same time as the invoice (atomic upsert, deduplicated by SKU then by name).

MethodDescription
list(options?)List products from the catalogue (paginated, q / revenue_category / is_active filters)
get(id)Get a product by UUID
create(data)Create a product in the catalogue (Factur-X fields: revenue_category, unit, unit_price_ht)
update(id, data)Update a product (PATCH)
delete(id)Deactivate a product (is_active = false, soft delete)
products-create.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// Create a service product
const service = await scell.products.create({
name: 'Consulting journalier',
description: 'Accompagnement technique sur site',
revenue_category: 'service',
unit: 'C62',
unit_price_ht: 1200,
default_tax_rate: 20,
sku: 'CONS-JOUR',
is_active: true,
});
// Create a goods product
const goods = await scell.products.create({
name: 'Licence logicielle annuelle',
revenue_category: 'goods',
unit: 'C62',
unit_price_ht: 499,
default_tax_rate: 20,
default_discount_rate: 10,
sku: 'LIC-ANNUAL',
});
// Use product_id when creating a quote line (auto-fill + soft link)
const quote = await scell.quotes.create({
buyer_id: 'buyer_uuid',
lines: [
{
product_id: service.id, // pre-fills all line fields from the product
quantity: 3,
},
],
});
// Save an ad-hoc line back to the catalogue
const invoice = await scell.invoices.create({
buyer_id: 'buyer_uuid',
lines: [
{
description: 'Formation React avancée',
quantity: 1,
unit_price_ht: 800,
tax_rate: 20,
save_to_catalog: true, // atomic backend upsert by name/SKU
product_category_id: 'cat_uuid', // optional: assign to a category
},
],
});

Product Categories

5 methods

Organise the catalogue with coloured and ordered categories. Each category exposes a products_count counter. Assign a product to a category via product_category_id in the products endpoints, or directly from invoice lines via save_to_catalog: true + product_category_id.

MethodDescription
list(options?)List product categories (paginated, q filter)
get(id)Get a category by UUID
create(data)Create a category (name, color #RRGGBB, position)
update(id, data)Update a category (PATCH)
delete(id)Delete a category (no linked products)
products-list.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// List all active products
const { data: products } = await scell.products.list({ is_active: true });
// Filter by revenue category
const { data: services } = await scell.products.list({
revenue_category: 'service',
per_page: 50,
});
// Full-text search across name, description, and SKU
const { data: results } = await scell.products.list({ q: 'consulting' });
// Get a single product
const product = await scell.products.get('product_uuid');
// List product categories
const { data: categories } = await scell.productCategories.list();
// Create a category and assign products to it
const category = await scell.productCategories.create({
name: 'Prestations de service',
color: '#0066FF',
position: 1,
});
// Assign product to category
await scell.products.update(product.id, {
product_category_id: category.id,
});

Auto VAT (vat-context)

1 method

POST /api/v1/tenant/buyers/vat-context pre-resolves the VAT context of an invoice line before issuance: applicable rate, EN16931 category (S, AE, O, Z, E), exemption_reason and CGI justification (art. 259, 259-1, 259-2, 259 A). Covers FR→FR, FR→EU B2B (reverse charge), FR→EU B2C, FR→non-EU (export) and the art. 259 A override via place_of_supply (real-estate, restaurant, events). When line.tax_rate is provided, also returns consistency warnings (mismatch, inconsistent category, etc.).

VAT categoryDefault FR rateEN16931Usage
STANDARD20 %SStandard rate (art. 278 CGI)
INTERMEDIATE10 %SIntermediate rate (food service, transport)
REDUCED5,5 %SReduced rate (food, books)
SUPER_REDUCED2,1 %SSuper-reduced rate (medicines)
ZERO_RATED0 %ZZero-rated (non-EU exports)
EXEMPTEExempt operation (medical, financial)
REVERSE_CHARGE0 %AEIntra-EU B2B services reverse charge (art. 283-2 CGI)
OUT_OF_SCOPE0 %OServices to a recipient outside the EU (art. 259-1 CGI)
INTRACOM_GOODS0 %KIntra-EU supply of goods (art. 262 ter, I CGI)
EXPORT0 %GExport of goods outside the EU (art. 262, I CGI)
FRANCHISE_BASE0 %ESmall-business franchise (art. 293 B CGI)
EXEMPT_TRAINING0 %EVocational training (art. 261-4-4°a CGI)
Resolution rules (cascade)
  • R1FR→FR (company or individual): French VAT per category (art. 278+ CGI).
  • R2FR→EU B2B with vat_number_valid: services → REVERSE_CHARGE (AE, art. 283-2); goods (supply_type="goods") → INTRACOM_GOODS (K, art. 262 ter, I).
  • R3FR→EU company WITHOUT vat_number or individual: French VAT (art. 259-2, provider location).
  • R4FR→non-EU: services → OUT_OF_SCOPE (O, art. 259-1); goods (supply_type="goods") → EXPORT (G, art. 262, I).
  • R0Seller under the small-business franchise (non-liable micro-entrepreneur): FRANCHISE_BASE (E, art. 293 B) — overrides everything.
  • R6Override 259 A: if place_of_supply ≠ buyer country, applies the VAT of the place of supply (real-estate, restaurant, events in France).

CGI articles: art. 259, art. 259-1, art. 259-2, art. 259 A.

Issuance: per-line VAT control fields

On issuance (invoices.create), the server RE-RESOLVES each line's VAT. Four top-level fields (not in metadata) drive the resolution:

FieldRole
vat_categoryExplicit category (else server-resolved).
supply_type`goods` | `services` — discriminates K/G (goods) vs AE/O (services).
place_of_supplyISO-2 place of supply (art. 259-A override).
vat_override_reasonAssume a divergent rate (avoids the 409, fiscal trail).

The builders place these fields correctly: JS createInvoiceLine({ category }).supplyType('goods').overrideReason('…') · PHP (new InvoiceLineBuilder)->withCategory(VatCategory::IntracomGoods)->withSupplyType('goods')->withOverrideReason('…'). The enum has 12 cases (VatCategory) and a bilingual justification($lang) method (exact legal mention).

409 VAT_CORRECTION_REQUIRED — if a rate is inconsistent with the context (VIES) without vat_override_reason, the invoice is NOT persisted. The SDK throws a typed error: JS VatCorrectionRequiredError (.corrections / .hint), PHP VatCorrectionRequiredException (getCorrections() / getHint()). Each correction carries line_index, provided_rate, suggested_rate, suggested_category, en16931_code, mention, rule. Resolve by re-submitting with the suggested values, or by setting vat_override_reason.
vat-context.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// ---------------------------------------------------------------------------
// Mode 1 — Acheteur déjà enregistré dans le registre (buyer_id)
// ---------------------------------------------------------------------------
// Pré-résout le contexte TVA pour une ligne STANDARD (20 % par défaut FR→FR).
// Le sub-tenant (s'il existe) est résolu côté serveur depuis la clé d'API.
const fr = await scell.buyers.resolveVatContext({
buyer_id: '019cb416-b6db-730c-b3a5-f8b7a4512eb1',
line: {
category: 'STANDARD',
place_of_supply: 'FR',
},
});
console.log(fr.resolution);
// {
// rate: 20,
// category: 'STANDARD',
// en16931_code: 'S',
// exemption_reason: null,
// justification: 'TVA française standard (art. 278 CGI)',
// is_auto_resolved: true,
// rule: 'R1_fr_domestic'
// }
// ---------------------------------------------------------------------------
// Mode 2 — Acheteur inline (B2B UE avec numéro de TVA vérifié VIES)
// ---------------------------------------------------------------------------
// Autoliquidation (reverse charge) déclenchée automatiquement par la règle R2.
const ue = await scell.buyers.resolveVatContext({
buyer: {
country: 'DE',
is_individual: false,
vat_number: 'DE123456789',
vat_number_valid: true, // typiquement validé via VIES en amont
},
line: { category: 'STANDARD' },
});
console.log(ue.resolution);
// {
// rate: 0,
// category: 'REVERSE_CHARGE',
// en16931_code: 'AE',
// exemption_reason: 'reverse_charge',
// justification: 'TVA non applicable, art. 259-1 du CGI (autoliquidation)',
// is_auto_resolved: true,
// rule: 'R2_eu_b2b_vat_valid'
// }
// ---------------------------------------------------------------------------
// Mode 3 — B2C UE sans numéro de TVA (TVA française restante)
// ---------------------------------------------------------------------------
// Règle R3 : pas de numéro de TVA valide → TVA du prestataire (art. 259-2 CGI).
const b2cUe = await scell.buyers.resolveVatContext({
buyer: {
country: 'IT',
is_individual: true, // particulier italien
},
line: { category: 'STANDARD' },
});
console.log(b2cUe.resolution.rate); // 20 (TVA française)
console.log(b2cUe.resolution.rule); // 'R3_eu_b2c_no_vat'
// ---------------------------------------------------------------------------
// Mode 4 — Override art. 259 A CGI via place_of_supply
// ---------------------------------------------------------------------------
// Service immobilier rendu en France à un client UE → TVA française même
// si le buyer a un numéro de TVA valide (lieu de prestation = France).
const immo = await scell.buyers.resolveVatContext({
buyer: {
country: 'DE',
is_individual: false,
vat_number: 'DE123456789',
vat_number_valid: true,
},
line: {
category: 'STANDARD',
place_of_supply: 'FR', // override 259 A : lieu du bien immobilier
service_nature: 'real_estate_service',
},
});
console.log(immo.resolution.rate); // 20 (TVA française appliquée)
console.log(immo.resolution.rule); // 'R6_place_of_supply_override'
// ---------------------------------------------------------------------------
// Warnings — incohérence détectée si line.tax_rate est fourni
// ---------------------------------------------------------------------------
const check = await scell.buyers.resolveVatContext({
buyer_id: '019cb416-b6db-730c-b3a5-f8b7a4512eb1',
line: {
category: 'STANDARD',
tax_rate: 10, // valeur saisie par l'utilisateur, à valider
},
});
if (check.warnings.length > 0) {
console.warn('Incohérence TVA détectée :', check.warnings);
// [{ code: 'VAT_RATE_MISMATCH', expected: 20, actual: 10,
// message: 'Le taux saisi diffère du taux résolu (R1_fr_domestic).' }]
}
// ---------------------------------------------------------------------------
// Mode 5 — Livraison intracommunautaire de BIENS (supply_type='goods')
// ---------------------------------------------------------------------------
// supply_type discrimine l'exonération : biens → INTRACOM_GOODS (K, art. 262 ter)
// au lieu de REVERSE_CHARGE (AE, services). À TOUJOURS renseigner pour une vente
// transfrontalière de biens physiques.
const biens = await scell.buyers.resolveVatContext({
buyer: { country: 'DE', is_individual: false, vat_number: 'DE123456789', vat_number_valid: true },
line: { category: 'INTRACOM_GOODS', supply_type: 'goods' },
});
console.log(biens.resolution.en16931_code); // 'K'
console.log(biens.resolution.exemption_reason); // 'intracom_goods'
// ---------------------------------------------------------------------------
// Émission : résolution AUTORITAIRE serveur + 409 VAT_CORRECTION_REQUIRED
// ---------------------------------------------------------------------------
// À la création, le serveur RE-RÉSOUT la TVA de chaque ligne. Si le taux fourni
// est incohérent (ex : 20 % sur une vente intra-UE B2B au lieu de l'autoliquidation)
// ET qu'aucune raison d'override n'est donnée, l'API renvoie un 409 et NE persiste
// PAS la facture. Le builder `createInvoiceLine` émet les champs au bon endroit.
import { createInvoiceLine, VatCorrectionRequiredError } from '@scell/sdk';
try {
await scell.invoices.create({
direction: 'outgoing',
output_format: 'facturx',
issue_date: '2026-06-04',
currency: 'EUR',
// ... seller / buyer (B2B UE avec numéro TVA valide) ...
lines: [
createInvoiceLine({ category: 'REVERSE_CHARGE' })
.description('Prestation de conseil')
.unitPrice(1000)
.supplyType('services') // → AE, art. 283-2 (mention auto sur la facture)
.build(),
],
} as Parameters<typeof scell.invoices.create>[0]);
} catch (e) {
if (e instanceof VatCorrectionRequiredError) {
for (const c of e.corrections) {
console.warn(
`Ligne ${c.line_index} : ${c.provided_rate}% → ${c.suggested_rate}% ` +
`(${c.suggested_category}) — ${c.mention ?? ''}`
);
}
// → Accepter les taux suggérés, OU ajouter .overrideReason('...') sur la ligne.
}
}

Quotes

15 methods

Full cycle: creation, email with signable public link, canvas signature, conversion to deposit invoices (type 386) and balance invoices (type 380 + BG-22). DEV-YYYY-NNNN numbering independent from the ISCA chain. Available on ScellApiClient.

MethodDescription
list(options?)List quotes with pagination and filters
get(id)Get a quote by UUID
create(data)Create a quote (auto-numbering DEV-YYYY-NNNN)
update(id, data)Update a draft quote
delete(id)Delete a draft quote
send(id)Send the quote by email to the buyer (signable public link)
cancel(id)Cancel a sent quote
duplicate(id)Duplicate an existing quote (new draft)
convertToDeposit(id, data)Convert to deposit invoice (type 386, immediate VAT)
convertToBalance(id)Convert to balance invoice (type 380 + auto BG-22)
auditLog(id)Append-only audit log (SHA-256 hash chain)
regeneratePublicLink(id)Regenerate the signable public link (90-day token)
revokePublicLink(id)Revoke the public link
pdf(id)Download the quote as PDF
preview(data)Non-persisted PDF preview from raw data (POST /quotes/preview) — no UUID required
paymentSchedule.*Quote payment schedule wired on the quote (get / set / patch / delete / summary / convertLine / presets) — see dedicated section
quotes.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// ── Create a quote with signature ───────────────────────────────────
const { data: quote } = await scell.quotes.create({
issue_date: '2026-06-01',
expiration_date: '2026-09-01', // optional, defaults to +90 days
currency: 'EUR',
title: 'Proposition commerciale — Audit digital',
description: 'Prestation complète d\'audit et accompagnement.',
// Buyer (flat fields or buyer_id)
buyer_name: 'GN IMMO',
buyer_siret: '49438068600076',
buyer_country: 'FR',
buyer_email: 'contact@gnimmo.com',
buyer_address: {
line1: '453 Route Nationale 7',
postal_code: '13670',
city: 'Verquières',
country: 'FR',
},
// Signature options
signature_required: true,
// Callback URL — buyer is redirected here after accept/refuse
callback_url: 'https://myapp.com/quotes/callback',
// Lines
lines: [
{
description: 'Audit transformation digitale',
detail: 'Analyse SI existant + recommandations (5 jours)',
quantity: 5,
unit: 'jour',
unit_price_ht: 800.00,
tax_rate: 20.0,
},
{
description: 'Accompagnement mensuel',
quantity: 3,
unit: 'mois',
unit_price_ht: 1200.00,
tax_rate: 20.0,
},
],
});
// quote.quote_number === 'DEV-2026-000012'
// quote.status === 'draft'
// quote.public_url === 'https://scell.io/q/{token}'
// ── Send by email (generates public signable link) ──────────────────
await scell.quotes.send(quote.id);
// ── Duplicate a quote ───────────────────────────────────────────────
const { data: copy } = await scell.quotes.duplicate(quote.id);
// ── Convert accepted quote to deposit invoice (type 386) ────────────
const { data: deposit } = await scell.quotes.convertToDeposit(quote.id, {
amount: 2000.00, // or use percent: 30
});
// deposit.invoice_type === 'deposit'
// deposit.invoice_number === 'FAC-2026-000043'
// ── Convert to balance invoice (type 380 + BG-22 auto-deduction) ────
const { data: balance } = await scell.quotes.convertToBalance(quote.id);

Payment Schedule

7 methods

Sub-resource of a quote: manage payment schedule lines (planned deposits). Each line can be individually converted to a deposit invoice. Access via client.quotes.paymentSchedule (JS) or $client->quotes()->paymentSchedule() (PHP).

MethodDescription
get(quoteId)List payment schedule lines for a quote
set(quoteId, lines)Replace the entire schedule (atomic)
patch(quoteId, lines)Partially update (add/modify lines)
delete(quoteId)Delete all lines (blocked if any invoiced)
summary(quoteId)Aggregated summary: total, invoiced, remaining, lines with status
convertLine(quoteId, lineId, data)Invoice a specific line (deposit from schedule)
presets()List predefined schedule templates

Onboarding & Widget

6 methods

Two distinct flows: server-to-server OAuth Authorization Code (create session, fetch SuperPDP authorize URL, exchange code) AND public widget with pk_* key (Sirene lookup, SubTenant creation). The v3 widget inverts the flow Scell-first then SuperPDP — see Swagger UI.

MethodDescription
createSession(data)Create a partner onboarding session (server-to-server mode)
getSession(sessionId)Get the current status of a session
getSuperPDPAuthorizeUrl(sessionId)Get the SuperPDP OAuth URL with PKCE + CSRF state
superpdpCallback(sessionId, code, state)Exchange a SuperPDP authorization code for tenant credentials
lookupSirene(siret)Sirene lookup by SIRET (pk_* auth) — returns CompanyData or manual_entry
createSubTenant(data)Create a sub_tenant from widget data (pk_* auth)

Pricing

3 methods

Functional cascade: global Scell.io rates → tenant override (admin only). Snapshot is frozen in tenant_invoices.metadata['pricing_snapshot'] at invoice generation time.

MethodDescription
getPublic()Get public pricing (no auth) — for marketing display
get()Get the tenant's effective pricing (cascade: global → tenant override)
getForTenant()Tenant-scoped variant — alias of get() on ScellTenantClient

Sub-Tenants

12 methods

Manage sub-tenants for multi-tenant partner platforms. Each sub-tenant gets their own tenant key (tk_live_*) and can be identified by your external ID system. Available on ScellApiClient and ScellTenantClient.

MethodDescription
list(options?)List all sub-tenants (paginated)
get(id)Get sub-tenant details by ID
create(data)Create a new sub-tenant with company details
update(id, data)Update sub-tenant information
delete(id, options?)Delete a sub-tenant. `options.cascade=true` to cascade Companies. Returns 422 SUB_TENANT_HAS_FISCAL_ENTRIES if invoices emitted (ISCA compliance)
findByExternalId(externalId)Look up a sub-tenant by your external ID
statsOverview(id)Get usage statistics for a specific sub-tenant
getThresholds(id)Micro-entrepreneur threshold gauges (VAT franchise + micro-regime ceiling): net revenue per category, %, level reached and projected crossing date. Read-only. Returns `{ data, disclaimer }`.
updateFiscalStatus(id, data)Update fiscal status (fiscal_regime, vat_status, activity_type, activity_start_date). Setting `vat_status: "liable"` (VAT number required) switches invoicing to VAT. The URSSAF/INPI filing remains the AE's responsibility.
getSuperPDPStatus(id)Current SuperPDP status + i18n recommended_action (cached)
refreshSuperPDPStatus(id)Force a fresh poll of SuperPDP status. 422 MISSING_ACCESS_TOKEN includes authorize_url
superpdpAuthorize(id)Generate a SuperPDP OAuth URL (start/restart the tunnel). Returns `{ authorize_url, state }`
getResumeUrl(id)Regenerate a signed 7-day URL to resume the onboarding tunnel
superpdpDisconnect(id)Revoke the sub-tenant's SuperPDP connection and reset `onboarding_status` to `pending_superpdp`. Already-issued invoices stay immutable (ISCA); future B2B invoices fall back to paper mode until reconnection (since v3.1.0)
superpdpReconnect(id)Disconnect then generate a fresh SuperPDP OAuth URL in a single call. Returns `{ authorize_url, state }` (since v3.1.0)
superpdpWidgetToken(id, { reset })Mint a signed 24h token to resume onboarding in the SuperPDP-only widget (`<scell-onboarding mode="superpdp" resume-token="…">`). Scoped to the sub-tenant (anti-IDOR) (since v3.1.0)
sub-tenants.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// Create a sub-tenant
const tenant = await scell.subTenants.create({
name: 'Partner Corp',
external_id: 'partner-001',
email: 'admin@partner-corp.com',
company: {
name: 'Partner Corp',
siren: '111222333',
address: {
street: '20 Boulevard Haussmann',
city: 'Paris',
postal_code: '75009',
country: 'FR',
},
},
});
console.log(tenant.id);
console.log(tenant.tenant_key); // sk_live_... (cle secrete sk_*, pas de format tk_*)
// List all sub-tenants
const list = await scell.subTenants.list();
// Get a specific sub-tenant
const detail = await scell.subTenants.get(tenant.id);
// Find by external ID
const found = await scell.subTenants.findByExternalId('partner-001');
// Update a sub-tenant
await scell.subTenants.update(tenant.id, { name: 'Partner Corp (Updated)' });
// Get sub-tenant stats
const stats = await scell.subTenants.statsOverview(tenant.id);
// Delete a sub-tenant
await scell.subTenants.delete(tenant.id);
// --- Supervision SuperPDP ---
// Path: /api/v1/tenant/sub-tenants/{id}/... (middleware tenant.key).
// Statut OAuth2 + KYB courant (cache 5 min)
const status = await scell.subTenants.getSuperPdpStatus(tenant.id);
// Forcer un poll cote SuperPDP (rate-limit 1/min)
// 422 MISSING_ACCESS_TOKEN -> e.authorize_url a ouvrir dans le navigateur
try {
await scell.subTenants.refreshSuperPdpStatus(tenant.id);
} catch (e) {
if (e.code === 'MISSING_ACCESS_TOKEN') {
window.open(e.authorize_url, '_blank', 'noopener');
}
}
// Generer une URL OAuth fresh (sans passer par refresh)
const { authorize_url, state } = await scell.subTenants.superpdpAuthorize(tenant.id);
// Regenerer une URL signee (7j) pour reprendre le tunnel onboarding
const { resume_url } = await scell.subTenants.getResumeUrl(tenant.id);
// --- Delete avec politique ISCA ---
try {
await scell.subTenants.delete(tenant.id);
} catch (e) {
if (e.code === 'SUB_TENANT_HAS_COMPANIES') {
// Demander confirmation a l'utilisateur, puis cascade
await scell.subTenants.delete(tenant.id, { cascade: true });
} else if (e.code === 'SUB_TENANT_HAS_FISCAL_ENTRIES') {
// Factures emises -> impossible de supprimer. Desactiver a la place.
await scell.subTenants.update(tenant.id, { is_active: false });
}
}
// --- Suivi de seuils micro-entrepreneur (auto-entrepreneur FR) ---
// Jauges de seuils (franchise TVA base/majoree + plafond du regime micro).
// Regles datees (loi 2025-1044). Lecture seule, informational.
const { data, disclaimer } = await scell.subTenants.getThresholds(tenant.id);
for (const g of data.gauges) {
// g.category: 'goods' | 'service' | 'accommodation'
// g.kind: 'vat_franchise_base' | 'vat_franchise_majored' | 'micro_ceiling'
// g.level: null | 'warning_80' | 'warning_90' | 'base_exceeded' | 'majored_exceeded' | 'micro_ceiling_exceeded'
console.log(`${g.category}/${g.kind}: ${g.percent}% (${g.level ?? 'ok'})`);
if (g.actionable) {
console.warn(`Seuil franchi — projection: ${g.projected_crossing_date}`);
}
}
console.info(disclaimer); // toujours afficher le disclaimer (info non contractuelle)
// Declarer le statut fiscal. vat_status='liable' bascule la facturation vers la
// TVA (factures suivantes avec TVA, retrait de la mention art. 293 B).
// vat_number requis (422 sinon). La demarche URSSAF/INPI reste a charge de l'AE.
const res = await scell.subTenants.updateFiscalStatus(tenant.id, {
vat_status: 'liable',
vat_number: 'FR12345678901',
});
console.log(res.message);
// --- Clotures par sous-tenant + telechargement CSV ---
const { data: closings } = await scell.fiscal.closings({
closing_type: 'monthly', // 'daily' | 'monthly' | 'annual'
sub_tenant_id: tenant.id,
});
const csv = await scell.fiscal.downloadClosing(closings[0].id); // ArrayBuffer (CSV)

Fiscal status — payload & options (PATCH /sub-tenants/{id}/fiscal-status)

All fields are optional (partial update). Updates the fiscal state declared in Scell — no administrative filing (URSSAF / INPI) is performed.

FieldTypeOptionsDescription
fiscal_regimestringmicro | reelDeclared fiscal regime. Only `micro` is tracked for ceilings.
vat_statusstringfranchise | liable`liable` switches invoicing to VAT (VAT number required) and removes the 293 B mention.
activity_typestringgoods | service | accommodation | mixedActivity type. Default line classification + tracked gauges. `mixed` tracks both scales.
activity_start_datestring (date)YYYY-MM-DDActivity start date. Enables prorata temporis of the threshold for the first year.
vat_numberstringRequired to switch to `vat_status: "liable"`.
fiscal-status.request.json
json
// PATCH /api/v1/sub-tenants/{id}/fiscal-status
{
"fiscal_regime": "micro",
"vat_status": "liable",
"activity_type": "service",
"activity_start_date": "2026-01-15",
"vat_number": "FR12345678901"
}

Threshold tracking — response schema (GET /sub-tenants/{id}/thresholds)

One gauge per (category × threshold kind) pair. level = highest level reached; projected_crossing_date = projected crossing at the current rate.

thresholds.response.json
json
// GET /api/v1/sub-tenants/{id}/thresholds
{
"data": {
"sub_tenant_id": "st_…",
"fiscal_year": 2026,
"generated_at": "2026-06-04T10:00:00+00:00",
"gauges": [
{
"category": "service", // goods | service | accommodation
"kind": "vat_franchise_base", // vat_franchise_base | vat_franchise_majored | micro_ceiling
"revenue": 40000.00, // CA HT net (factures - avoirs)
"threshold": 37500.00, // seuil applicable (résolu par date, prorata 1re année)
"percent": 106.7,
"level": "base_exceeded", // null | warning_80 | warning_90 | base_exceeded | majored_exceeded | micro_ceiling_exceeded
"actionable": true,
"projected_crossing_date": null // sinon "YYYY-MM-DD" au rythme courant
}
],
"new_alerts": []
},
"disclaimer": "Information non contractuelle, ne constitue pas un conseil fiscal ou comptable. …"
}

Payment Schedule

v2.13.0

Attach a contractualized payment schedule to a quote. Lines by % or TTC amount, by date OR text milestone ("MVP delivery", "Go live"). Auto-generation of deposit invoices on due date. Outstanding balance tracker. Schedule locked at signature (immutable post-acceptance). Available via $client->quotes->paymentSchedule (PHP) or client.quotes.paymentSchedule (JS).

Methods: set, list/get, patch, delete, summary, convertLine, presets

Business rules: sum of lines ≤ TTC total; each milestone independent; immutable post-signature; 1 conversion per line max.

payment-schedule.ts
typescript
// TypeScript — @scell/sdk v2.13.0
import { ScellClient } from '@scell/sdk';
const client = new ScellClient({ apiKey: 'sk_live_...' });
// Créer / remplacer l'échéancier
await client.quotes.paymentSchedule.set('quote-uuid', [
{ amount_type: 'percent', amount_value: 30,
milestone_label: 'À la commande',
auto_generate: true, due_date: '2026-06-01' },
{ amount_type: 'percent', amount_value: 70,
milestone_label: 'À la livraison' },
]);
// Tracker du solde restant
const summary = await client.quotes.paymentSchedule.summary('quote-uuid');
// → { schedule, invoiced: { gross_ttc, net_ttc, remaining_ttc, remaining_pct }, next_due, overdue, superpdp_status }
// Convertir une ligne en facture d'acompte
const invoice = await client.quotes.paymentSchedule.convertLine(
'quote-uuid', 'line-uuid',
{ send_email: true }
);
// 4 presets disponibles
const presets = await client.quotes.paymentSchedule.presets();

Send Invoice by Email

v2.13.0

Send an invoice by generic Scell.io email with the Factur-X PDF attached. Recipient resolution cascade: override → buyer.billing_emailbuyer.emailquote.buyer_email. If the invoice is draft, automatic transition to validated + SUPER PDP submission. Available via client.invoices.sendByEmail().

send-by-email.ts
typescript
// TypeScript
const result = await client.invoices.sendByEmail('invoice-uuid', {
recipient_email: 'compta@buyer.com', // optionnel
cc: ['manager@buyer.com'],
message: 'Merci pour votre confiance',
});
// → { sent_to, sent_at, message_id, cc }

Tenant / Sub-Tenant Branding

v2.35.0

Customize email branding (logo, primary color, footer, signature). By default, Scell.io applies generic branding. If ALL fields (brand_logo_url, brand_primary_color, brand_email_footer) are set, emails use your branding. Available at master tenant and sub-tenant level. Logo upload via S3 presigned URL. preview() returns an HTML preview of the branded email, before sending anything (since v2.35.0). Since v3.2.0 JS / v3.4.0 PHP: direct multipart email logo upload via uploadLogoFile() (JS) / uploadLogoTenant() / uploadLogoSubTenant() (PHP) — jpeg/png/webp/svg, max 2 MB, SVGs normalized; preview(overrides) accepts non-persisted overrides (brand_primary_color, brand_email_footer, brand_email_signature, brand_logo_url) for live previews; new fields brand_email_enabled (when false, emails fall back to the channel's default branding) and computed_email_footer (read-only, footer computed from the company, used when brand_email_footer is empty).

branding.ts
typescript
// TypeScript
// Tenant master
const branding = await client.branding.tenant.get();
// → { is_complete, missing_fields, brand_logo_url, brand_primary_color, ... }
await client.branding.tenant.update({
brand_logo_url: 'https://cdn.scell.io/.../logo.png',
brand_primary_color: '#1A73E8',
brand_email_footer: 'Société XYZ — SIRET 123456789',
});
// Sub-tenant
await client.branding.subTenants.update('sub-tenant-uuid', {
brand_primary_color: '#10B981',
});
// Upload logo (presigned S3)
const signed = await client.branding.tenant.uploadLogo('image/png');
// Then PUT the file to signed.url, then persist signed.public_url
// Preview — render the branded email as HTML (before sending anything)
const html = await client.branding.tenant.preview();
// e.g. show it in <iframe srcDoc={html} /> for a live preview
const subHtml = await client.branding.subTenants.preview('sub-tenant-uuid');
// Since v3.2.0 — direct multipart upload of the email logo (no presign round-trip)
const updated = await client.branding.tenant.uploadLogoFile(file, 'logo.svg');
// jpeg/png/webp/svg, max 2 MB — SVGs are normalized server-side
await client.branding.subTenants.uploadLogoFile('sub-tenant-uuid', file);
// Since v3.2.0 — live preview with NON-persisted overrides (try before saving)
const draft = await client.branding.tenant.preview({
brand_primary_color: '#0066FF',
brand_email_footer: 'Essai de pied de page',
});
// Since v3.2.0 — toggle branding + computed footer
await client.branding.tenant.update({ brand_email_enabled: false });
// branding.computed_email_footer (read-only): footer computed from the company,
// used at render time when brand_email_footer is empty

Post-Signature Callback URL

v2.13.1

Provide a callback_url at quote creation. After accept or refuse via the public viewer, the buyer is redirected to this URL with query string: ?status=signed|refused&quote_id=<UUID>&quote_number=<num>&reason=<txt>. Lets you integrate signature into your own metier flow (thank-you page, client dashboard, automation). Without callback_url, the buyer sees the default Scell.io confirmation page.

Format: Absolute HTTPS URL, max 500 chars

Appended query string: status (signed|refused), quote_id, quote_number, reason (on refusal)

callback-url.ts
typescript
// TypeScript — @scell/sdk v2.13.1
import { ScellClient } from '@scell/sdk';
const client = new ScellClient({ apiKey: 'sk_live_...' });
await client.quotes.create({
issue_date: '2026-05-24',
valid_until: '2026-06-24',
buyer_id: 'buyer-uuid',
lines: [{ description: 'Mission', quantity: 1, unit_price_ht: 12000, tax_rate: 20 }],
total_ht: 12000, total_tax: 2400, total_ttc: 14400,
callback_url: 'https://mon-site.com/devis-signe',
});
// Buyer signe -> redirige vers :
// https://mon-site.com/devis-signe?status=signed&quote_id=...&quote_number=DEV-2026-0042
//
// Buyer refuse avec motif "Prix trop élevé" -> :
// https://mon-site.com/devis-signe?status=refused&quote_id=...&quote_number=...&reason=Prix+trop+%C3%A9lev%C3%A9

Enriched PaymentSummary + SDK parity

v2.14.0

The GET /api/v1/quotes/{id}/payment-summary payload now exposes lines: PaymentScheduleLine[] in addition to aggregates (schedule, invoiced, next_due, overdue, superpdp_status). Lets you render the full visual tracker — highlight the next due line, gray out past ones, red for overdue — without a second request. MCP-side, 11 new tools are documented to reach full backend parity.

New MCP tools: regenerate_quote_public_link, revoke_quote_public_link, get_quote_pdf, preview_quote_pdf, get/set/patch/delete_quote_payment_schedule, get_quote_payment_summary, convert_schedule_line_to_invoice, list_payment_schedule_presets

summary-lines.ts
typescript
// TypeScript — @scell/sdk v2.14.0
const summary = await client.quotes.paymentSchedule.summary('quote-uuid');
// summary.lines est maintenant peuplé : PaymentScheduleLine[]
summary.lines.forEach((line) => {
const isNext = line.id === summary.next_due?.line_id;
const isOverdue = line.is_overdue;
const isPast = line.status === 'invoiced';
// → render tracker visuel avec highlight
});
// summary.invoiced.remaining_ttc = montant restant à facturer
// summary.invoiced.remaining_pct = % restant

Standalone Deposit Invoices (no quote)

v2.15.0

Create deposit invoices directly via POST /invoices without a quote. Specify the deal total (deposit_total_ht) and a free-text reference (deposit_reference_text). Link subsequent deposits via deposit_group_id. When the sum reaches 100%, the last deposit auto-converts to a balance invoice (type 380 + BG-22 Factur-X deductions).

standalone-deposit.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// ── 1. First deposit: create a new group ────────────────────────
const deposit1 = await scell.invoices.create({
invoice_type: 'deposit',
deposit_total_ht: 10000, // Total deal = 10 000 € HT
deposit_reference_text: 'Proposition commerciale signée le 15/05/2026',
direction: 'outgoing',
output_format: 'facturx',
issue_date: '2026-05-15',
due_date: '2026-05-30',
total_ht: 3000, // 30% deposit
total_tax: 600,
total_ttc: 3600,
seller_siret: '12345678901234',
seller_name: 'Ma Société',
seller_address: { line1: '1 rue Example', postal_code: '75001', city: 'Paris', country: 'FR' },
buyer_name: 'Client SA',
buyer_siret: '98765432109876',
buyer_address: { line1: '2 avenue Test', postal_code: '69001', city: 'Lyon', country: 'FR' },
lines: [{ description: 'Acompte 30%', quantity: 1, unit_price: 3000, tax_rate: 20, total_ht: 3000, total_tax: 600, total_ttc: 3600 }],
});
// deposit1.deposit_group_id = deposit1.id (self-reference)
// ── 2. Second deposit: join the group ───────────────────────────
const deposit2 = await scell.invoices.create({
invoice_type: 'deposit',
deposit_group_id: deposit1.deposit_group_id, // Link to group
// deposit_total_ht and deposit_reference_text inherited from group leader
direction: 'outgoing',
output_format: 'facturx',
issue_date: '2026-06-15',
total_ht: 7000, // Remaining 70% → auto-converted to balance
total_tax: 1400,
total_ttc: 8400,
seller_siret: '12345678901234',
seller_name: 'Ma Société',
seller_address: { line1: '1 rue Example', postal_code: '75001', city: 'Paris', country: 'FR' },
buyer_name: 'Client SA',
buyer_siret: '98765432109876',
buyer_address: { line1: '2 avenue Test', postal_code: '69001', city: 'Lyon', country: 'FR' },
lines: [{ description: 'Solde 70%', quantity: 1, unit_price: 7000, tax_rate: 20, total_ht: 7000, total_tax: 1400, total_ttc: 8400 }],
});
// deposit2.invoice_type === 'balance' (auto-converted: sum = 100%)
// deposit2.parent_invoice_ids === [deposit1.id] (BG-22 deductions)
// ── 3. Check group progress ─────────────────────────────────────
const invoice = await scell.invoices.get(deposit1.id);
console.log(invoice.deposit_group_progress);
// { deposit_total_ht: 10000, sum_deposits_ht: 3000, remaining_ht: 7000,
// progress_percent: 30, has_balance: true, invoices_count: 2 }

Recurring Invoices

10 methods

Automate invoice emission on a cadence (subscriptions, rent, maintenance). A recurring profile is an editable template: on each due date, Scell.io snapshots the CURRENT state of the profile into a real invoice (ISCA chain per emitted invoice). In auto_send mode the invoice is submitted to the PDP then emailed to the buyer with the Factur-X PDF; in draft mode a draft is created for review. Bilateral notifications + J-N reminder. A failed cycle is never silently skipped (failed occurrence). Available via client.recurringInvoices.* (JS) or $client->recurringInvoices()->* (PHP).

MethodDescription
list(options?)List recurring profiles (filters: status, sub_tenant_id, per_page max 100)
get(id)Get a recurring profile by UUID
create(data)Create a recurring profile (editable template). Each emission snapshots the current state into a real invoice (ISCA).
update(id, data)Update the template / cadence. Only affects FUTURE emissions.
delete(id)Delete a recurring profile (already-emitted invoices stay untouched)
occurrences(id)Occurrence history (pending | emitted | failed | skipped) + emitted invoice_id
pause(id)Pause (stops future emissions, keeps the schedule)
activate(id)Resume a paused profile
cancel(id)Cancel (terminal — no further emissions, profile kept for audit)
runNow(id)Emit the next occurrence immediately, out of cadence (202 Accepted, async)

Enums

FieldValues
recurrence.interval_unitday | week | month | year
end_modenever | on_date | after_occurrences
emission_modedraft | auto_send (default auto_send)
statusactive | paused | completed | cancelled
occurrence statuspending | emitted | failed | skipped

Create schema (POST /recurring-invoices)

recurring-invoice.create.json
json
{
"title": "Abonnement SaaS — Plan Pro", // requis
// Émetteur : sans sub_tenant_id => tenant master ; sinon le sous-tenant
"sub_tenant_id": "uuid", // optionnel
// Acheteur : buyer_id (registre) OU les champs buyer_* à plat
"buyer_id": "uuid", // optionnel (sinon buyer_*)
"buyer_name": "GN IMMO",
"buyer_country": "FR",
"buyer_is_individual": false, // true => B2C (siret/tva optionnels)
"buyer_siret": "49438068600076",
"buyer_vat_number": "FR42494380686",
"buyer_email": "compta@gnimmo.com",
"buyer_address": { "line1": "…", "postal_code": "…", "city": "…", "country": "FR" },
"buyer_shipping_address": { "name": "…", "line1": "…", "postal_code": "…", "city": "…", "country": "FR" },
"currency": "EUR", // optionnel, défaut "EUR"
"output_format": "facturx", // facturx | ubl | cii
"payment_terms": "Paiement à 30 jours.", // optionnel
// Lignes (requis) — même forme qu'une ligne de facture
"lines": [
{
"description": "Abonnement mensuel Plan Pro",
"quantity": 1,
"unit_price": 49.00,
"vat_rate": 20,
"unit": "mois", // optionnel
"discount": 0, // % remise sur la ligne, optionnel
"category": "subscription" // regroupement libre, optionnel
}
],
// Cadence (requis)
"recurrence": {
"interval_unit": "month", // day | week | month | year
"interval_count": 1, // tous les N (défaut 1)
"day_of_month": 1, // 1..31, clampé à la longueur du mois (31 => 28/29/30)
"day_of_week": 1 // 1..7 ISO (lun..dim) — uniquement si interval_unit "week"
},
"start_date": "2026-07-01", // requis (date ISO) — 1re émission
// Condition d'arrêt
"end_mode": "after_occurrences", // never | on_date | after_occurrences
"end_date": "2027-06-30", // requis si end_mode = "on_date"
"max_occurrences": 12, // requis si end_mode = "after_occurrences"
"emission_mode": "auto_send", // draft | auto_send (défaut auto_send)
"notify_before_days": 3, // 0..30 — rappel J-N avant chaque émission
"metadata": { "plan": "pro" } // optionnel
}

Create a profile

recurring-invoice-create.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
// ── Create a recurring invoice profile (an editable template) ───────
// Each emission snapshots the CURRENT state of this profile into a real
// invoice (ISCA chain per emitted invoice). Editing the profile only
// affects FUTURE emissions, never invoices already issued.
const { data: profile } = await scell.recurringInvoices.create({
title: 'Abonnement SaaS — Plan Pro',
// Buyer: use buyer_id (registry) OR the flat buyer_* fields below.
buyer_id: 'b1f2c3d4-5e6f-7081-92a3-b4c5d6e7f809',
// buyer_name: 'GN IMMO',
// buyer_country: 'FR',
// buyer_is_individual: false,
// buyer_siret: '49438068600076',
// buyer_vat_number: 'FR42494380686',
// buyer_email: 'compta@gnimmo.com',
// buyer_address: { line1: '453 Route Nationale 7', postal_code: '13670', city: 'Verquières', country: 'FR' },
// buyer_shipping_address: { name: 'Entrepôt Lyon', line1: '12 Rue du Dôme', postal_code: '69003', city: 'Lyon', country: 'FR' },
currency: 'EUR', // optional, defaults to 'EUR'
output_format: 'facturx', // 'facturx' | 'ubl' | 'cii'
payment_terms: 'Paiement à 30 jours.',
// Lines — same shape as a standard invoice line
lines: [
{
description: 'Abonnement mensuel Plan Pro',
quantity: 1,
unit_price: 49.0,
vat_rate: 20,
unit: 'mois',
},
{
description: 'Utilisateurs additionnels',
quantity: 5,
unit_price: 9.0,
vat_rate: 20,
discount: 10, // % discount on this line (optional)
category: 'subscription', // free-text grouping (optional)
},
],
// Recurrence cadence
recurrence: {
interval_unit: 'month', // 'day' | 'week' | 'month' | 'year'
interval_count: 1, // every N units (default 1)
day_of_month: 1, // 1..31 — clamped to month length (e.g. 31 → 28/29/30)
// day_of_week: 1, // 1..7 ISO (Mon..Sun) — only for interval_unit 'week'
},
start_date: '2026-07-01', // first emission (ISO date)
// End condition
end_mode: 'after_occurrences', // 'never' | 'on_date' | 'after_occurrences'
max_occurrences: 12, // required when end_mode = 'after_occurrences'
// end_date: '2027-06-30', // required when end_mode = 'on_date'
emission_mode: 'auto_send', // 'draft' | 'auto_send' (default 'auto_send')
notify_before_days: 3, // 0..30 — J-N reminder before each emission
metadata: { plan: 'pro', crm_id: 'sub_9981' },
});
// profile.status === 'active'
// profile.next_occurrence_at === '2026-07-01'
console.log(profile.id, profile.next_occurrence_at);

Manage: pause / activate / cancel / run now + history

recurring-invoice-manage.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
const profileId = 'r1e2c3u4-5r6r-7081-92a3-b4c5d6e7f809';
// ── List recurring invoice profiles ─────────────────────────────────
const { data: profiles } = await scell.recurringInvoices.list({
status: 'active', // 'active' | 'paused' | 'completed' | 'cancelled'
// sub_tenant_id: '…', // scope to one sub-tenant (anti-IDOR: 404 if not yours)
per_page: 25, // default 25, max 100
});
// ── Get one profile + its occurrence history ────────────────────────
const { data: profile } = await scell.recurringInvoices.get(profileId);
// profile.status, profile.next_occurrence_at, profile.occurrences_count
const { data: occurrences } = await scell.recurringInvoices.occurrences(profileId);
for (const occ of occurrences) {
// occ.status: 'pending' | 'emitted' | 'failed' | 'skipped'
// occ.scheduled_for, occ.invoice_id (when emitted), occ.error (when failed)
console.log(occ.scheduled_for, occ.status, occ.invoice_id ?? '—');
}
// ── Pause (stops future emissions, keeps the schedule) ──────────────
await scell.recurringInvoices.pause(profileId);
// ── Activate (resume a paused profile) ──────────────────────────────
await scell.recurringInvoices.activate(profileId);
// ── Run now (emit the next occurrence immediately, out of cadence) ──
// Returns 202 Accepted — the emission is processed asynchronously.
// Honours emission_mode: 'auto_send' submits to the PDP + emails the
// buyer with the Factur-X PDF; 'draft' creates a draft for review.
const { data: run } = await scell.recurringInvoices.runNow(profileId);
// run.occurrence_id, run.status === 'pending'
// ── Cancel (terminal — no further emissions, profile kept for audit) ─
await scell.recurringInvoices.cancel(profileId);

MCP Agent@scell/mcp-client v3.5.0

Use Scell.io directly from your AI assistant. The MCP agent exposes 148 tools that let Claude, Cursor, or VS Code Copilot create invoices, manage signatures, plan payment schedules, and monitor fiscal compliance using natural language. The exhaustive list below uses the canonical names (scell_ prefix) exactly as declared server-side in backend/config/mcp.php.

Setup

~/.claude/.mcp.json
json
// Coller ce bloc dans le fichier de config MCP de votre client IA.
// Cree le fichier s'il n'existe pas (chemin selon le client) :
//
//   - Claude Code (CLI)   ~/.claude/.mcp.json
//   - Claude Desktop      ~/Library/Application Support/Claude/claude_desktop_config.json   (macOS)
//                         %APPDATA%\\Claude\\claude_desktop_config.json                  (Windows)
//   - Cursor              ~/.cursor/mcp.json
//   - VS Code (Copilot)   ~/.vscode/mcp.json
//
// Remplacer sk_live_xxxx par votre cle Scell.io (sk_live_* en prod, sk_test_* en sandbox).
// Recuperez vos cles depuis le dashboard : https://app.scell.io/dashboard/api-keys

{
  "mcpServers": {
    "scell": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://api.scell.io/api/mcp",
        "--header",
        "X-Scell-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      ]
    }
  }
}

Invoices12 tools

ToolDescriptionExample Prompt
scell_create_invoiceCreate a Factur-X/UBL/CII electronic invoice (optional `parentQuoteId` to link a standard invoice to a source quote)"Create a Factur-X invoice from ACME Corp to Client SA for 10 hours of consulting at 150 EUR/h, linked to quote DEV-2026-0042"
scell_get_invoiceGet details of a specific invoice by ID"Get details for invoice inv_abc123"
scell_list_invoicesList all invoices with optional filters"Show me all invoices from this month"
scell_download_invoiceDownload an invoice as PDF or XML"Download the PDF for invoice inv_abc123"
scell_mark_invoice_paidMark an outgoing invoice as paid (manual)"Mark invoice inv_abc123 as paid"
scell_submit_invoiceSubmit an invoice to the PDP network"Submit invoice inv_abc123 to the PDP"
scell_convert_invoiceConvert an invoice between formats (Factur-X ↔ UBL ↔ CII)"Convert invoice inv_abc123 to UBL"
scell_send_invoice_by_emailEmail the Factur-X invoice to the buyer"Email invoice inv_abc123 to the buyer"
scell_list_deposit_groupsList deposit groups (multi-invoice deals) with progress"List my deposit groups still missing a balance invoice"
scell_get_deposit_groupGet a deposit group: deal total, deposits issued, balance, % invoiced"Show the deposit group for the Dubois renovation deal"
scell_suggest_invoice_mentionsLegal-mentions assistant: suggest per-field footer/terms/notes for an invoice"Suggest the legal mentions for a B2B invoice from my company"
scell_preview_invoice_mentionsPreview the rendered legal mentions (footer / payment terms) before issuing"Preview the legal mentions that would appear on this invoice"

Credit Notes7 tools

ToolDescriptionExample Prompt
scell_list_credit_notesList all credit notes"Show all credit notes issued this quarter"
scell_get_credit_noteGet a credit note by ID"Get credit note cn_abc123"
scell_download_credit_noteDownload a credit note as PDF or XML"Download the PDF for credit note cn_abc123"
scell_create_credit_noteCreate a credit note (avoir). `type` partial|total. A PARTIAL credit note SELECTS lines of the source invoice via invoice_line_id (price + exact per-line VAT inherited — never invent amounts). Use scell_get_remaining_creditable first."Credit the consulting line of invoice inv_abc123"
scell_send_credit_noteEmail the credit note to the buyer"Send credit note cn_abc123 to the buyer"
scell_delete_credit_noteDelete a draft credit note"Delete draft credit note cn_abc123"
scell_get_remaining_creditableList the invoice lines still creditable (invoice_line_id + remaining quantity + per-line VAT) — discovery step before a partial credit note."Which lines of invoice inv_abc123 can still be credited?"

Quotes22 tools

ToolDescriptionExample Prompt
scell_create_quoteCreate a quote (auto-numbering DEV-YYYY-NNNN)"Create a quote for Client SA for a 5-day website build at 600 EUR/day"
scell_get_quoteGet a quote by ID"Get quote DEV-2026-0042"
scell_list_quotesList quotes with filters"List all accepted quotes this year"
scell_update_quoteUpdate a draft quote"Add a maintenance line to draft quote qte_abc123"
scell_delete_quoteDelete a draft quote"Delete draft quote qte_abc123"
scell_send_quoteSend the quote by email to the buyer (signable public link)"Send quote qte_abc123 to the client"
scell_cancel_quoteCancel a sent quote"Cancel quote qte_abc123"
scell_duplicate_quoteDuplicate an existing quote (new draft)"Duplicate quote qte_abc123"
scell_convert_quote_to_depositConvert a quote into a deposit invoice (type 386, immediate VAT)"Convert quote qte_abc123 into a 30% deposit invoice"
scell_convert_quote_to_balanceConvert a quote into a balance invoice (type 380 + auto BG-22 deductions)"Issue the balance invoice for quote qte_abc123"
scell_get_quote_pdfDownload the quote as PDF"Download quote qte_abc123 as PDF"
scell_get_quote_audit_logAppend-only audit log of the quote (SHA-256 chain)"Show the audit log for quote qte_abc123"
scell_regenerate_quote_public_linkRegenerate the signable public link (fresh token)"Regenerate the public link for quote qte_abc123"
scell_revoke_quote_public_linkRevoke the public link of a sent quote"Revoke the public link for quote qte_abc123"
scell_preview_quoteNon-persisted PDF preview from raw quote data"Preview a quote PDF for a 3-day audit at 800 EUR/day"
scell_get_quote_payment_scheduleGet the payment schedule lines of a quote"Show the payment schedule for quote qte_abc123"
scell_set_quote_payment_scheduleCreate or replace the full payment schedule (atomic)"Set a 30/40/30 payment schedule on quote qte_abc123"
scell_patch_quote_payment_schedulePartially update the payment schedule (add/update/remove lines)"Push back the second installment of quote qte_abc123 by a month"
scell_delete_quote_payment_scheduleDelete all schedule lines (blocked if any line is invoiced)"Remove the payment schedule on quote qte_abc123"
scell_get_quote_payment_summaryAggregated summary: total, invoiced, remaining, lines with status"How much is left to invoice on quote qte_abc123?"
scell_convert_schedule_line_to_invoiceInvoice a specific schedule line (deposit from the schedule)"Invoice the first milestone of quote qte_abc123"
scell_list_payment_schedule_presetsList predefined payment schedule templates"List the available payment schedule presets"

Recurring Invoices10 tools

ToolDescriptionExample Prompt
scell_create_recurring_invoiceCreate a recurring invoice profile (subscription / rent / maintenance)"Set up a monthly 99 EUR maintenance invoice for Client SA"
scell_get_recurring_invoiceGet a recurring profile by ID"Show recurring profile rec_abc123"
scell_list_recurring_invoicesList recurring profiles"List all my active recurring invoices"
scell_update_recurring_invoiceUpdate a recurring profile (template)"Raise the monthly amount on profile rec_abc123 to 120 EUR"
scell_delete_recurring_invoiceDelete a recurring profile"Delete recurring profile rec_abc123"
scell_list_recurring_invoice_occurrencesList the emitted/pending/failed occurrences of a profile"Show the occurrences of profile rec_abc123"
scell_pause_recurring_invoicePause a recurring profile"Pause recurring profile rec_abc123"
scell_activate_recurring_invoiceResume a paused recurring profile"Reactivate recurring profile rec_abc123"
scell_cancel_recurring_invoiceCancel a recurring profile (final)"Cancel recurring profile rec_abc123"
scell_run_recurring_invoice_nowTrigger the next occurrence immediately"Issue the next occurrence of profile rec_abc123 now"

Buyers & VAT6 tools

ToolDescriptionExample Prompt
scell_create_buyerCreate a reusable buyer in the registry (B2B/B2C, billing + shipping address)"Add buyer Client SA, SIRET 12345678900012, to my registry"
scell_get_buyerGet a buyer by ID"Get buyer buy_abc123"
scell_list_buyersList/search buyers (q on name/siret/email, filter is_individual)"Find buyers matching ACME"
scell_update_buyerUpdate a buyer"Update the billing email for buyer buy_abc123"
scell_delete_buyerDelete (soft) a buyer from the registry"Remove buyer buy_abc123 from my registry"
scell_resolve_vat_contextResolve the authoritative VAT context (intracom autoliquidation, franchise, exemptions)"What VAT applies to a B2B service sold to a German company?"

Products5 tools

ToolDescriptionExample Prompt
scell_create_productCreate a catalog product/service (revenue_category, unit, price, tax/discount defaults)"Add a Consulting day product at 600 EUR HT, 20% VAT, to my catalog"
scell_get_productGet a catalog product by ID"Get product prod_abc123"
scell_list_productsList/search products (q, filter revenue_category / category / is_active)"List my active service products"
scell_update_productUpdate a catalog product"Raise the price of product prod_abc123 to 650 EUR"
scell_delete_productDelete (soft) a catalog product"Delete product prod_abc123"

Product Categories5 tools

ToolDescriptionExample Prompt
scell_create_product_categoryCreate a custom organisation category (name, color, position)"Create a product category called Subscriptions in blue"
scell_get_product_categoryGet a product category by ID"Get category cat_abc123"
scell_list_product_categoriesList product categories"List my product categories"
scell_update_product_categoryUpdate a product category"Rename category cat_abc123 to Recurring"
scell_delete_product_categoryDelete a product category"Delete category cat_abc123"

Suppliers3 tools

ToolDescriptionExample Prompt
scell_list_suppliersList suppliers (auto-derived from received invoices)"List my suppliers"
scell_get_supplierGet a supplier by ID"Get supplier sup_abc123"
scell_update_supplierEnrich a supplier (email / phone / notes / metadata only — identity is read-only)"Add a contact email to supplier sup_abc123"

Invoice Templates9 tools

ToolDescriptionExample Prompt
scell_list_invoice_templatesList templates (system / tenant / sub_tenant scope)"List my invoice templates"
scell_get_invoice_templateGet a template by ID"Get template tpl_abc123"
scell_create_invoice_templateCreate a custom template (logo, colors, footer, mentions)"Create an invoice template with my brand colors"
scell_update_invoice_templateUpdate a template"Change the accent color of template tpl_abc123"
scell_delete_invoice_templateDelete a template"Delete template tpl_abc123"
scell_set_default_invoice_templateMark a template as the default"Make template tpl_abc123 my default"
scell_preview_invoice_templatePreview the PDF render of a template (without persisting)"Preview how invoices look with template tpl_abc123"
scell_derive_template_colors_from_email_logoDerive primary/accent colors from the email logo and apply them to the default template"Use my email logo colors for my invoice template"
scell_derive_template_colors_from_invoice_logoExtract the { primary, accent } palette from the invoice (template) logo (not persisted)"What colors does my invoice logo suggest?"

Branding7 tools

ToolDescriptionExample Prompt
scell_get_tenant_brandingGet the tenant email/branding settings"Show my branding settings"
scell_update_tenant_brandingUpdate tenant branding (logo URL, colors, footer, brand_email_enabled)"Enable email branding and set my primary color to #0066FF"
scell_preview_tenant_brandingRender the branded email (HTML or PDF) as it will be received, with optional overrides"Preview my branded email before I enable it"
scell_get_tenant_branding_logo_upload_urlGet a presigned URL to upload the tenant email logo"Give me an upload URL for my email logo"
scell_get_sub_tenant_brandingGet a sub-tenant's branding settings"Show the branding for sub-tenant sub_abc123"
scell_update_sub_tenant_brandingUpdate a sub-tenant's branding"Set the footer text for sub-tenant sub_abc123"
scell_preview_sub_tenant_brandingPreview a sub-tenant's branded email with optional overrides"Preview the branded email for sub-tenant sub_abc123"

Documents1 tool

ToolDescriptionExample Prompt
scell_preview_documentLive HTML preview of a draft document (invoice / credit_note / quote) with the real template + branding + legal mentions"Preview the HTML of an invoice I'm drafting for Client SA"

Signatures6 tools

ToolDescriptionExample Prompt
scell_create_signatureCreate an eIDAS EU-SES signature request (signature/initials/mentions/date blocks)"Create a signature request for agreement.pdf with 2 signers: Jean Dupont and Marie Martin"
scell_get_signatureGet signature request details"What's the status of signature sig_xyz789?"
scell_list_signaturesList signature requests (tenant scope)"List all pending signature requests"
scell_download_signedDownload the signed document / proof file"Download the signed PDF for signature sig_xyz789"
scell_cancel_signatureCancel a pending signature request"Cancel signature request sig_xyz789"
scell_send_reminderSend a reminder to pending signers"Remind the signers of request sig_xyz789"

Incoming Invoices5 tools

ToolDescriptionExample Prompt
scell_list_incoming_invoicesList invoices received via the PDP"Show my incoming invoices this month"
scell_get_incoming_invoiceGet an incoming invoice by ID"Get incoming invoice in_abc123"
scell_accept_incoming_invoiceAccept an incoming invoice"Accept incoming invoice in_abc123"
scell_reject_incoming_invoiceReject an incoming invoice with a reason + code"Reject incoming invoice in_abc123 — wrong amount"
scell_mark_incoming_invoice_paidMark an incoming invoice as paid"Mark incoming invoice in_abc123 as paid"

Sub-Tenants & Onboarding17 tools

ToolDescriptionExample Prompt
scell_list_sub_tenantsList all sub-tenants"Show all my sub-tenants"
scell_create_sub_tenantCreate a new sub-tenant"Create a sub-tenant for Partner Corp with external ID partner-001"
scell_get_sub_tenantGet a sub-tenant by ID"Get sub-tenant sub_abc123"
scell_update_sub_tenantUpdate a sub-tenant (PUT { is_active } to deactivate)"Deactivate sub-tenant sub_abc123"
scell_delete_sub_tenantDelete a sub-tenant (ISCA policy: refused if fiscal entries exist)"Delete sub-tenant sub_abc123 (no invoices yet)"
scell_find_sub_tenant_by_external_idFind a sub-tenant by your external ID (idempotent lookup)"Find the sub-tenant with external ID partner-001"
scell_get_subtenant_superpdp_statusGet the SuperPDP onboarding status of a sub-tenant"Is sub-tenant sub_abc123 connected to SuperPDP?"
scell_refresh_subtenant_superpdp_statusForce a SuperPDP status poll (rate-limited)"Refresh the SuperPDP status of sub-tenant sub_abc123"
scell_authorize_subtenant_superpdpGet a SuperPDP authorize URL (prefilled) for a sub-tenant"Get the SuperPDP authorize link for sub-tenant sub_abc123"
scell_get_subtenant_resume_urlGet a signed resume URL to reopen the onboarding tunnel (7 days)"Give me a resume link for sub-tenant sub_abc123 onboarding"
scell_disconnect_subtenant_superpdpForce-disconnect a sub-tenant from SuperPDP"Disconnect sub-tenant sub_abc123 from SuperPDP"
scell_reconnect_subtenant_superpdpDisconnect + return a fresh SuperPDP authorize URL in one call"Reconnect sub-tenant sub_abc123 to SuperPDP"
scell_mint_subtenant_widget_tokenMint a signed 24h token for the SuperPDP-only embed widget"Mint a SuperPDP widget token for sub-tenant sub_abc123"
scell_lookup_sireneSirene lookup (Etalab + INSEE fallback) by SIRET/SIREN"Look up SIRET 12345678900012 in Sirene"
scell_create_onboarding_sessionCreate a widget onboarding session (Scell-first flow)"Start an onboarding session for a new partner"
scell_get_onboarding_sessionGet an onboarding session status"What's the status of onboarding session ses_abc123?"
scell_authorize_onboarding_superpdpGet the SuperPDP authorize URL for an onboarding session"Get the SuperPDP link for onboarding session ses_abc123"

Thresholds (Micro-Entrepreneur)3 tools

ToolDescriptionExample Prompt
scell_get_subtenant_thresholdsGet a sub-tenant's VAT-franchise / micro-enterprise threshold tracking"How close is sub-tenant sub_abc123 to the VAT franchise threshold?"
scell_simulate_subtenant_thresholdsPre-issuance simulator: would this invoice cross a threshold?"Would a 5,000 EUR invoice push sub_abc123 over the VAT threshold?"
scell_update_subtenant_fiscal_statusSwitch a sub-tenant's VAT status (franchise ↔ liable)"Switch sub-tenant sub_abc123 to VAT-liable"

Fiscal (ISCA)12 tools

ToolDescriptionExample Prompt
scell_get_fiscal_complianceGet fiscal compliance status"Am I ISCA compliant?"
scell_check_fiscal_integrityVerify the hash-chain integrity"Verify my fiscal data integrity"
scell_list_fiscal_closingsList daily/monthly closings (tenant and sub-tenant scope)"Show all fiscal closings for March 2026"
scell_get_fiscal_attestationGet the ISCA attestation for a year"Get my ISCA attestation for 2025"
scell_list_fiscal_entriesList immutable ledger entries"Show the last 50 fiscal entries"
scell_get_kill_switch_statusGet kill-switch status (read-only)"Is the kill switch active?"
scell_list_fiscal_rulesList fiscal automation rules"Show all fiscal rules"
scell_export_fecExport the FEC file for the tax authorities"Generate the FEC export for 2025"
scell_perform_daily_closingTrigger a daily fiscal closing"Run the daily fiscal closing now"
scell_activate_kill_switchActivate the emergency kill switch (step-up auth, client allowlist required)"Activate the kill switch"
scell_deactivate_kill_switchDeactivate the kill switch (step-up auth, client allowlist required)"Deactivate the kill switch"
scell_export_forensicExport a forensic bundle of the ledger"Export a forensic bundle for 2025"

Billing & Credit Packs8 tools

ToolDescriptionExample Prompt
scell_get_balanceGet the current tenant credit balance"What's my current credit balance?"
scell_list_billing_invoicesList Scell.io billing invoices issued to your account"List my Scell.io billing invoices for this year"
scell_get_billing_usageGet current-period usage metrics with projection"How many invoices did I send this month?"
scell_list_billing_transactionsList billing transactions (debits + credits)"Show my last 10 billing transactions"
scell_list_credit_packsList the available credit packs"What credit packs can I buy?"
scell_checkout_credit_packStart a Stripe checkout for a credit pack"Buy the 1,000-credit pack"
scell_topup_balanceTop up the balance with a custom amount (returns Stripe client_secret)"Top up my balance with 50 EUR"
scell_confirm_topupConfirm a top-up after Stripe payment"Confirm my last top-up"

Tenant & Stats5 tools

ToolDescriptionExample Prompt
scell_get_tenant_profileGet the tenant profile (default company / Factur-X issuer defaults)"Show my tenant profile"
scell_update_tenant_profileUpdate the tenant profile (IBAN, BIC, payment terms, notifications toggle)"Set my default payment terms to 30 days"
scell_get_tenant_statsGet tenant-level statistics"Give me my tenant stats"
scell_get_stats_overviewGet a stats overview (invoices, signatures, revenue)"Show my stats overview"
scell_get_stats_monthlyGet monthly statistics breakdown"Show my monthly stats for 2026"

Validation & System5 tools

ToolDescriptionExample Prompt
scell_validate_siretValidate a SIRET number (Sirene)"Validate SIRET 12345678900012"
scell_validate_vatValidate an intra-EU VAT number (VIES)"Validate VAT number FR12345678901"
scell_get_audit_trailGet the audit trail for a resource"Show the audit trail for invoice inv_abc123"
scell_health_checkCheck the API health"Is the Scell.io API healthy?"
scell_validate_api_keyValidate the current API key and report its scope"Is my API key valid and what can it do?"

Webhook Events

Complete list of webhook events you can subscribe to. Use these event names when creating or updating webhook endpoints.

EventDescription
invoice.createdInvoice has been created
invoice.validatedInvoice validated (Factur-X/UBL generated)
invoice.transmittedInvoice transmitted to the PDP
invoice.acceptedInvoice accepted by recipient
invoice.rejectedInvoice rejected
invoice.errorInvoice processing error
invoice.refund_status_changedRefund status changed (credit note)
signature.createdSignature request created
signature.signedDocument signed by a signer
signature.completedAll signers have signed
signature.refusedSignature refused by a signer
signature.expiredSignature request expired
balance.lowCredit balance is low (alert threshold)
balance.criticalCredit balance is critical

Invoice Statuses

Complete list of values the status field can take on the Invoice payload (PostgreSQL check constraint, backend-side). Statuses marked Auto are set by the system (workers, observers, webhooks) — you don't write them. refunded and partially_refunded are set by the CreditNoteObserver as soon as a validated credit note is attached to an invoice — read-only on the client side.

StatusAutoDescription
draftDraft. Editable and deletable. Not yet validated.
validatingYesFactur-X/UBL validation in progress (PDF/A-3 generation).
validatedValidated. Fiscal hash committed, immutable ISCA ledger. PDP submission allowed.
convertingYesFormat conversion in progress (Factur-X ↔ UBL ↔ CII).
convertedYesTarget format generated, ready for transmission.
transmittingYesTransmission to PDP/PPF in progress.
transmittedYesTransmitted to network, awaiting recipient response.
acceptedYesAccepted by recipient via the PDP.
rejectedYesRejected by recipient or PDP.
disputedDisputed by recipient. Human action required.
paidPaid. Set via mark-paid or banking webhook.
receivedYesIncoming invoice received via the PDP (incoming direction).
completedYesLifecycle complete (payment confirmed + post-processing).
errorYesUnrecoverable technical error. See metadata.last_error.
refundedYesFully refunded. Set automatically by the backend when the sum of validated credit notes ≥ total inc. VAT.
partially_refundedYesPartially refunded. Set automatically when a validated credit note is attached without covering the full total inc. VAT.

Refund-related fields

Two fields accompany the refunded and partially_refunded statuses on every Invoice payload returned by GET /invoices/{id} or GET /invoices.

  • refund_status'none' | 'partial' | 'full'. Fine-grained refund state, independent from the main status (a paid invoice then partially_refunded stays at refund_status='partial').
  • total_refundedFloat, cumulative incl.-VAT amount refunded through validated credit notes. Source of truth for reporting (the sum drives refund_status automatically).
Example — Invoice payload (excerpt)
json
{
  "id": "inv_01HXP3K7M2YQ9V8B5N6Z4A1C2D",
  "invoice_number": "QRCOM-202605-00001",
  "status": "partially_refunded",
  "refund_status": "partial",
  "total_refunded": 250.00,
  "total_amount": 1200.00,
  "currency": "EUR",
  "invoice_type": "standard",
  "buyer": { /* ... */ },
  "lines": [ /* ... */ ],
  "metadata": { /* ... */ }
}

Enums & Statuses

Exhaustive list of the 19 enums exposed by the API beyond InvoiceStatus. The first 8 are PHP BackedEnums synced to TypeScript via php artisan types:generate (back→front codegen). The next 11 are PostgreSQL CHECK constraints exposed by the API JSON payloads.

The TypeScript (@scell/sdk), PHP (scell/sdk) and MCP (@scell/mcp-client) SDKs type these values strictly client-side. The ISCA ledger relies on the raw string values, without aliases.

EnumValuesDescription
InvoiceTemplateKind
App\Enums\Invoice\InvoiceTemplateKind
invoicequoteboth
Determines whether a Factur-X template applies to invoices, quotes, or both.
InvoiceType
App\Enums\Invoice\InvoiceType
standarddepositbalance
Invoice type. `deposit` = down payment (VAT immediately due, CGI 289). `balance` = final settlement (deducts deposits via BG-22 code 80).
PaymentScheduleLineAmountType
App\Enums\Quote\PaymentScheduleLineAmountType
percentamount
Mode of a quote payment schedule line: percent of total inc. VAT, or fixed currency amount.
PaymentScheduleLineStatus
App\Enums\Quote\PaymentScheduleLineStatus
pendinginvoicedcancelled
State of a quote payment schedule line. `invoiced` is set automatically when the corresponding deposit/balance invoice is generated.
QuoteStatus
App\Enums\Quote\QuoteStatus
draftsentviewedacceptedrefusedexpiredconvertedcancelled
Quote lifecycle. `viewed` and `accepted/refused` are set via the signed public URL. `converted` once a balance invoice has been issued.
QuoteAuditAction
App\Enums\Quote\QuoteAuditAction
createdupdatedline_addedline_removedline_updatedbuyer_changedsentresentviewedsignedacceptedrefusedcancelledexpiredconvertedpublic_link_regeneratedpublic_link_revokedduplicateddeposit_generated_from_scheduleschedule_updatedschedule_deleted
21 actions tracked in `quote_audit_logs` (SHA-256 chain separate from the ISCA fiscal ledger).
SubTenantOnboardingStatus
App\Enums\SubTenantOnboardingStatus
pending_superpdpsuperpdp_redirectedsuperpdp_authorizedsuperpdp_pending_reviewactivesuperpdp_failed
SuperPDP onboarding lifecycle of a sub-tenant via the `<scell-onboarding>` widget. `active` = KYB verified, B2B in Peppol mode.
VatCategory
App\Enums\Billing\VatCategory
STANDARDINTERMEDIATEREDUCEDSUPER_REDUCEDZERO_RATEDEXEMPTREVERSE_CHARGEOUT_OF_SCOPEINTRACOM_GOODSEXPORTFRANCHISE_BASEEXEMPT_TRAINING
12 EN16931-aligned categories (codes S/AE/K/G/O/Z/E). `REVERSE_CHARGE` (AE) = intra-EU services reverse charge (art. 283-2). `INTRACOM_GOODS` (K) = intra-EU supply of goods (art. 262 ter I). `EXPORT` (G) = goods outside EU (art. 262 I). `OUT_OF_SCOPE` (O) = services outside EU (art. 259-1). `FRANCHISE_BASE` (E) = small-business art. 293 B. `EXEMPT_TRAINING` (E) = training art. 261-4-4°a. The mention is NOT derived from the code alone: FRANCHISE_BASE/EXEMPT/EXEMPT_TRAINING share E but carry distinct mentions. See the VAT & reverse charge section for details (`supply_type`/`vat_override_reason` fields, 409, builder).
CreditNoteStatus
DB check constraint
draftsent
Credit note state. `draft` = editable/deletable. `sent` = irreversible (ISCA fiscal entry committed).
CreditNoteType
DB check constraint
partialtotal
Credit note type. `total` = full refund, flips the invoice to `refunded`. `partial` = partial refund, triggers `partially_refunded`.
SignatureStatus
DB check constraint
pendingwaiting_signerspartially_signedcompletedrefusedexpirederror
eIDAS EU-SES signature request lifecycle. `completed` = all signers have signed, proof file available.
SignatureArchiveStatus
DB check constraint
pendingarchivedglaciererror
Long-term archive state of a signature (10 years). `glacier` = S3 Glacier cold storage.
InvoiceArchiveStatus
DB check constraint
pendingarchivedglaciererror
Long-term archive state of an invoice (Object Lock COMPLIANCE 11 years, ISCA compliance).
TenantKybStatus
DB check constraint
pendingdocuments_submittedunder_reviewverifiedrejected
Master tenant KYB status. `verified` is required in production to issue B2B invoices.
CompanyStatus
DB check constraint
pending_kycactivesuspended
Company state. `active` is required for a Company to be an invoice issuer.
ApiKeyStatus
DB check constraint
activerevoked
API key state. `revoked` is irreversible (audit trail kept).
TenantInvoiceStatus
DB check constraint
draftsentpaidoverduecancelled
State of a Scell.io billing invoice issued to the tenant (usage, packs, top-up). `overdue` is set by the J+30 cron.
TenantTransactionType
DB check constraint
debitcredit
Type of a tenant billing transaction. `debit` = consumption. `credit` = top-up or pack.
OnboardingSessionStatus
DB check constraint
initiatedsiret_verifiedvat_verifieddocuments_pendingdocuments_submittedunder_reviewcompletedfailedexpired
State of a sub-tenant onboarding session via the public widget (full KYB tunnel lifecycle).

SDK synchronisation

The new v2.21.0 (PHP), v2.23.0 (TypeScript) and v2.22.0 (MCP) versions expose these 19 enums in a strongly-typed way. On the PHP side they live in Scell\\Sdk\\Enums\\*; on the TypeScript side as exported union types; on the MCP side in the tool descriptions consumed by the LLM.

Error Handling

All SDK methods throw typed exceptions with HTTP status codes. Handle errors appropriately based on the status code.

CodeStatusDescription
400Bad RequestMalformed request body or invalid parameters
401UnauthorizedMissing, invalid, or expired authentication credentials
402Payment RequiredInsufficient credit balance to complete the operation
403ForbiddenValid credentials but insufficient permissions for the resource
404Not FoundThe requested resource does not exist
409ConflictResource already exists or state conflict (e.g. duplicate submission)
422Unprocessable EntityValidation error. Check the errors object for field-level details
429Too Many RequestsRate limit exceeded. Check Retry-After header
500Server ErrorInternal server error. Retry with exponential backoff
503Service UnavailableService temporarily unavailable. Retry after a short delay

Retry Strategy

  • 1.429 Rate LimitedRespect the Retry-After header. The SDK auto-retries up to 3 times with the indicated delay.
  • 2.500/503 Server ErrorRetry with exponential backoff: 1s, 2s, 4s. Max 3 retries. The SDK handles this automatically.
  • 3.4xx Client ErrorDo not retry. Fix the request payload or credentials and try again.
error-handling.ts
typescript
import { ScellApiClient } from '@scell/sdk';
const scell = new ScellApiClient('sk_live_your_api_key');
try {
const invoice = await scell.invoices.create({ /* ... */ });
} catch (error) {
if (error.status === 422) {
// Validation error — check error.errors for field-level details
console.error('Validation:', error.errors);
// { "seller.siren": ["The siren must be 9 digits."] }
} else if (error.status === 401) {
// Invalid or expired API key
console.error('Authentication failed');
} else if (error.status === 402) {
// Insufficient credits
console.error('Insufficient balance — top up credits');
} else if (error.status === 404) {
// Resource not found
console.error('Resource not found');
} else if (error.status === 429) {
// Rate limited — retry after error.retryAfter seconds
console.error('Rate limited, retry after', error.retryAfter, 'seconds');
} else if (error.status >= 500) {
// Server error — retry with exponential backoff
console.error('Server error, retrying...');
}
}

Ready to integrate?

Create your account and get your API keys in minutes. Start with 100 free credits.

Your cookie preferences

We use cookies to improve your experience. Essential cookies are always active. Cookie policy.