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

v2.25.0

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)
PHP

PHP SDK

PHP 8.2+

v2.25.0

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)
🤖

MCP Agent

Claude Desktop / Cursor / VS Code

v2.25.0

Installation

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

Features

  • MCP Protocol compatible
  • Claude Desktop, Cursor, VS Code, other LLMs
  • 98 tools: B2B/B2C invoices, credit notes, signatures, fiscal, templates, sub-tenants, buyers, branding, billing, credit packs, mark-paid BT-81 (since v2.25.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)

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)
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'
invoice_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_tva: 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_ht: 250.00,
tva_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',
invoice_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_tva: 30.00,
total_ttc: 180.00,
lines: [{ description: 'Formation individuelle', quantity: 1, unit_price_ht: 150.00, tva_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',
invoice_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_tva: 500.00,
total_ttc: 3000.00,
lines: [{ description: 'Consulting', quantity: 10, unit_price_ht: 250.00, tva_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',
invoice_date: '2026-06-01',
due_date: '2026-07-01',
currency: 'EUR',
buyer_id: '019e2dbe-362a-7105-87a5-f45fad1382ed',
total_ht: 1000.00,
total_tva: 200.00,
total_ttc: 1200.00,
lines: [{ description: 'Service', quantity: 1, unit_price_ht: 1000.00, tva_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',
invoice_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_tva: 500.00,
total_ttc: 3000.00,
lines: [{ description: 'Consulting (devis DEV-2026-0042)', quantity: 10, unit_price_ht: 250.00, tva_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');

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
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)Remaining creditable amount on an invoice
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'

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()List daily and monthly fiscal closings
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)
activateKillSwitch()Activate kill switch (halts all fiscal operations)
deactivateKillSwitch()Deactivate kill switch (resume operations)
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 ──────────────────────────────────────
const ksStatus = await scell.fiscal.killSwitchStatus();
await scell.fiscal.activateKillSwitch();
await scell.fiscal.deactivateKillSwitch();
// ─── 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

7 methods

Factur-X PDF customization: logo, colors, footer, legal mentions. Explicit cascade > sub_tenant default > tenant default > system default. 5 min Redis cache.

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)

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 },
],
});

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 %AEEU B2B reverse charge (art. 259-1 CGI)
OUT_OF_SCOPEOOut of scope (non-EU export)
Resolution rules (cascade)
  • R1FR→FR (company or individual): French VAT per category (art. 278+ CGI).
  • R2FR→EU company with vat_number_valid: REVERSE_CHARGE (art. 259-1 CGI, reverse charge).
  • R3FR→EU company WITHOUT vat_number or individual: French VAT (art. 259-2, provider location).
  • R4FR→non-EU: OUT_OF_SCOPE (export, outside VAT scope).
  • 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.

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).' }]
}

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(id)Preview PDF render (without persisting)
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

7 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
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
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 });
}
}

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.13.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.

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

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 }

MCP Agent@scell/mcp-client v2.14.0

Use Scell.io directly from your AI assistant. The MCP agent exposes 40+ tools that let Claude, Cursor, or VS Code Copilot create invoices, manage signatures, plan payment schedules, and monitor fiscal compliance using natural language.

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"
      ]
    }
  }
}

Invoices6 tools

ToolDescriptionExample Prompt
create_invoiceCreate a Factur-X/UBL/CII electronic invoice (optional `parentQuoteId` to link a standard invoice to a source quote, since v2.20.0)"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"
list_invoicesList all invoices with optional filters"Show me all invoices from this month"
get_invoiceGet details of a specific invoice by ID"Get details for invoice inv_abc123"
submit_invoiceSubmit an invoice to the PDP network"Submit invoice inv_abc123 to the PDP"
download_invoiceDownload an invoice as PDF or XML"Download the PDF for invoice inv_abc123"
mark_invoice_paidMark an outgoing invoice as paid (manual)"Mark invoice inv_abc123 as paid"

Signatures4 tools

ToolDescriptionExample Prompt
create_signatureCreate an eIDAS EU-SES signature request"Create a signature request for agreement.pdf with 2 signers: Jean Dupont and Marie Martin"
list_signaturesList all signature requests"List all pending signature requests"
get_signatureGet signature request details"What's the status of signature sig_xyz789?"
cancel_signatureCancel a pending signature request"Cancel signature request sig_xyz789"

Credit Notes2 tools

ToolDescriptionExample Prompt
create_credit_noteCreate a credit note linked to an invoice"Create a credit note for invoice inv_abc123 with a 150 EUR refund"
list_credit_notesList all credit notes"Show all credit notes issued this quarter"

Companies3 tools

ToolDescriptionExample Prompt
get_companyGet company details"Show me the details for company comp_id"
list_companiesList all registered companies"List all my companies"
create_companyRegister a new company"Register company ACME Corp with SIREN 123456789"

Billing5 tools

ToolDescriptionExample Prompt
get_balanceGet current tenant credit balance"What's my current credit balance?"
list_billing_transactionsList billing transactions (debits + credits)"Show my last 10 billing transactions"
list_billing_invoicesList Scell.io billing invoices issued to your account"List my Scell.io billing invoices for this year"
get_billing_usageGet current period usage metrics with projection"How many invoices did I send this month?"
scell_pay_billing_invoiceInitiate Stripe payment for a Scell.io billing invoice (returns client_secret)"Pay my latest Scell.io billing invoice"

Webhooks2 tools

ToolDescriptionExample Prompt
list_webhooksList all webhook endpoints"Show all my webhook endpoints"
create_webhookCreate a new webhook endpoint"Create a webhook at https://myapp.com/hook for invoice.created events"

Fiscal12 tools

ToolDescriptionExample Prompt
get_fiscal_complianceGet fiscal compliance status"Am I ISCA compliant?"
get_fiscal_entriesGet immutable ledger entries"Show the last 50 fiscal entries"
get_fiscal_closingsList daily/monthly closings"Show all fiscal closings for March 2026"
fiscal_daily_closingTrigger a daily fiscal closing"Run the daily fiscal closing now"
get_fiscal_integrityVerify hash chain integrity"Verify fiscal data integrity"
get_fec_exportExport FEC file for tax authorities"Generate the FEC export for 2025"
get_attestationGet ISCA attestation for a year"Get my ISCA attestation for 2025"
get_kill_switch_statusGet kill switch status"Is the kill switch active?"
activate_kill_switchActivate emergency kill switch"Activate the kill switch immediately"
deactivate_kill_switchDeactivate kill switch"Deactivate the kill switch"
get_fiscal_rulesList fiscal automation rules"Show all fiscal rules"
create_fiscal_ruleCreate a fiscal automation rule"Create a rule to auto-close daily at midnight"

Sub-Tenants2 tools

ToolDescriptionExample Prompt
list_sub_tenantsList all sub-tenants"Show all my sub-tenants"
create_sub_tenantCreate a new sub-tenant"Create a sub-tenant for Partner Corp with external ID partner-001"

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_SCOPE
EN16931 VAT category. `REVERSE_CHARGE` = reverse-charge (intra-EU B2B). `OUT_OF_SCOPE` = out of scope (CGI 259 A, non-EU services).
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.