Developer documentation
Complete guide to the Scell.io API: electronic invoicing (Factur-X / UBL / CII), intra-EU VAT & reverse charge, eIDAS signatures, quotes, credit notes, recurring invoices, ISCA fiscal compliance — plus the no-code onboarding widget. curl and SDK examples (PHP, JS, MCP).
<!-- 1. Charger le script (un seul include, n'importe ou dans <head> ou <body>) -->
<script src="https://cdn.scell.io/widget/v1/onboarding.js"></script>
<!-- 2. Poser le widget la ou il doit s'afficher -->
<scell-onboarding
publishable-key="pk_test_xxxxxxxx"
external-id="user_42"
callback-url="https://votre-app.com/onboarding/done"
></scell-onboarding>
<!-- 3. Ecouter l'evenement de fin pour recuperer sub_tenant + credentials -->
<script>
document.querySelector('scell-onboarding')
.addEventListener('onboarding:completed', (e) => {
const { subTenant, credentials } = e.detail;
console.log('Sub-tenant cree :', subTenant.id, subTenant.name);
console.log('A utiliser pour facturer :', credentials);
});
</script>Introduction
Scell.io is an API platform for electronic invoicing (Factur-X / UBL / CII, EN16931-compliant and aligned with the French e-invoicing reform) and simple electronic signatures (eIDAS EU-SES). This guide covers the entire API by functional domain, with curl and SDK examples. For the exhaustive endpoint-by-endpoint reference, see the OpenAPI reference.
Invoicing
Factur-X / UBL / CII, SUPER PDP / PEPPOL transmission, lifecycle, credit notes, quotes, recurring.
eIDAS signatures
Simple electronic signatures via OpenAPI.com, multi-signer, OTP, time-stamped proof.
Fiscal compliance
Immutable ISCA chain (SHA-256), closings, OpenTimestamps anchoring, FEC export.
Base URL & environments
A single host for production AND sandbox. The API key prefix selects the environment (and the isolated database).
# Base URL unique (production ET sandbox)
https://api.scell.io/api/v1
# Le préfixe de la clé détermine l'environnement automatiquement :
# sk_live_… / pk_live_… -> base de données production
# sk_test_… / pk_test_… -> base de données sandbox (données de test isolées)
# Il n'existe PAS de host api-sandbox.scell.io — tout passe par api.scell.io.Authentication
Four authentication modes depending on context. Golden rule: secret keys sk_* are SERVER-SIDE only (never in a browser or mobile app).
| Key / mode | Header | Usage |
|---|---|---|
| sk_live_* / sk_test_* | X-API-Key | Server-side. Issue invoices, credit notes, signatures, webhooks. Full tenant scope. |
| pk_live_* / pk_test_* | X-Publishable-Key | Front / widget. Public widget endpoints only. Safe in the browser. |
| Bearer (Sanctum) | Authorization: Bearer | Dashboard SPA app.scell.io (HttpOnly cookies) or API clients. |
| X-Tenant-Key (legacy) | X-Tenant-Key | Legacy multi-tenant. Prefer sk_*/pk_* for new projects. |
# 1) Secret key (server-side uniquement) — émission factures, signatures, avoirs
curl https://api.scell.io/api/v1/invoices \
-H 'X-API-Key: sk_live_xxxxxxxx'
# 2) Publishable key (front/widget) — endpoints widget publics uniquement
curl -X POST https://api.scell.io/api/v1/widget/onboarding/sirene/lookup \
-H 'X-Publishable-Key: pk_live_xxxxxxxx' -H 'Content-Type: application/json' \
-d '{ "siret": "12345678901234" }'
# 3) Bearer (dashboard SPA, Sanctum) — cookies HttpOnly côté app.scell.io
curl https://api.scell.io/api/v1/auth/me -H 'Authorization: Bearer <token>'
# 4) X-Tenant-Key (legacy multi-tenant) — préférer sk_*/pk_* pour tout nouveau projetCreate an account & keys
# Créer un compte (tenant) — retourne la 1re clé API
curl -X POST https://api.scell.io/api/v1/register \
-H 'Content-Type: application/json' \
-d '{
"name": "ACME SAS",
"email": "dev@acme.fr",
"password": "••••••••••••",
"password_confirmation": "••••••••••••"
}'
# Émettre une clé secrète server-side supplémentaire (Bearer requis)
curl -X POST https://api.scell.io/api/v1/auth/me/api-key/rotate -H 'Authorization: Bearer <token>'Key concepts
Multi-tenant model
TenantThe master account (yours). The sk_* key belongs to the tenant.Sub-tenantAn end client you invoice white-label (created via the onboarding widget). Target it via "sub_tenant_id" in the request body.CompanyThe issuing entity (SIRET, IBAN, Factur-X mentions). Resolved automatically by Scell.io.Buyer / SupplierRegistries scoped by (tenant, sub_tenant). Buyers: created manually or upserted from invoice. Suppliers: auto-derived from received invoices — no manual create, enrichment of email/phone/notes/metadata only.ProductReusable product/service catalogue scoped by (tenant, sub_tenant). Pre-fills a line via "product_id"; the "save_to_catalog" flag stores a typed line. Fiscal categorisation (revenue_category) + custom categories.
Numbering & immutability
Never pass "invoice_number": Scell.io generates it. On creation the invoice is a draft (DRAFT-… number); on submit it receives a definitive gap-free sequential number (French CGI art. 242 nonies A & 289) and becomes immutable (ISCA SHA-256 chain). Any correction goes through a credit note.
Electronic invoicing (Factur-X)
Issue Factur-X (PDF/A-3 + embedded EN16931 XML), UBL or CII compliant invoices. Lifecycle: create (draft) → submit (seal the number + transmit via SUPER PDP / PEPPOL) → track (transmitted, accepted, rejected, paid).
# Créer une facture Factur-X (B2B FR). invoice_number est généré par Scell.io.
curl -X POST https://api.scell.io/api/v1/invoices \
-H 'X-API-Key: sk_live_xxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{
"direction": "outgoing",
"output_format": "facturx", // facturx | ubl | cii
"issue_date": "2026-06-05",
"currency": "EUR",
"seller_siret": "12345678901234",
"seller_name": "ACME SAS",
"seller_address": { "line1": "1 rue de Paris", "postal_code": "75001", "city": "Paris", "country": "FR" },
"buyer_name": "Client SARL",
"buyer_siret": "98765432109876",
"buyer_address": { "line1": "2 av. Lyon", "postal_code": "69001", "city": "Lyon", "country": "FR" },
"lines": [
{ "description": "Prestation de conseil", "quantity": 1, "unit_price": 1000, "tax_rate": 20,
"total_ht": 1000, "total_tax": 200, "total_ttc": 1200 }
]
}'
# Réponse 201 — la facture est en brouillon (status="draft", numéro "DRAFT-…")
# { "data": { "id": "019df46f-…", "invoice_number": "DRAFT-2026-0001", "status": "draft", … } }
# Émettre (scelle le numéro définitif + transmet à SUPER PDP / PEPPOL)
curl -X POST https://api.scell.io/api/v1/invoices/{id}/submit -H 'X-API-Key: sk_live_xxxxxxxx'
# Télécharger le Factur-X (PDF/A-3 + XML embarqué) ou le XML seul
curl https://api.scell.io/api/v1/invoices/{id}/download/pdf -H 'X-API-Key: sk_live_xxxxxxxx' -o facture.pdf
curl https://api.scell.io/api/v1/invoices/{id}/download/xml -H 'X-API-Key: sk_live_xxxxxxxx' -o facture.xmlLifecycle & endpoints
| Action | Endpoint | Effect |
|---|---|---|
| Create | POST /invoices | Draft, DRAFT-… number |
| Update | PUT /invoices/{id} | While draft |
| Submit | POST /invoices/{id}/submit | Seal number + transmit (immutable) |
| Mark paid | POST /invoices/{id}/mark-paid | BT-81 payment means required |
| Download | GET /invoices/{id}/download/{pdf|xml} | Factur-X / XML |
| Audit trail | GET /invoices/{id}/audit-trail | Time-stamped history |
| Bulk | POST /invoices/bulk-submit · bulk-status | Mass processing |
B2C (individual)
# B2C (particulier) : buyer_is_individual=true -> SIRET/TVA acheteur optionnels,
# mentions L441-10 (pénalités) et BT-46/47/48 omises (BR-CO-26 EN16931).
curl -X POST https://api.scell.io/api/v1/invoices \
-H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' \
-d '{
"direction": "outgoing", "output_format": "facturx", "issue_date": "2026-06-05",
"buyer_is_individual": true,
"buyer_name": "Jean Dupont",
"buyer_address": { "line1": "5 rue Bleue", "postal_code": "13001", "city": "Marseille", "country": "FR" },
"lines": [ { "description": "Abonnement", "quantity": 1, "unit_price": 50, "tax_rate": 20 } ]
}'Related: incoming invoices (GET /tenant/invoices/incoming, accept/reject/dispute/mark-paid), invoice templates (/invoice-templates, including POST /invoice-templates/derive-colors-from-email-logo and the is_enabled field), live non-persisted HTML preview (POST /documents/preview), deposits & balances (see Quotes). For intra-EU VAT and reverse charge, see the next section.
VAT & intra-EU reverse charge
Scell.io resolves VAT AUTHORITATIVELY: on submit, the server recomputes each line’s expected EN16931 category from the context (seller, buyer, country, VIES validity of the VAT number, goods/services nature) and emits the code + exact legal mention in the Factur-X. This protects against tax reassessment (joint liability if 0% is applied without checks).
The 12 VAT categories
| Category | EN16931 | FR rate | Legal mention (CGI) |
|---|---|---|---|
| STANDARD | S | 20 % | — (art. 278) |
| INTERMEDIATE | S | 10 % | — |
| REDUCED | S | 5,5 % | — |
| SUPER_REDUCED | S | 2,1 % | — |
| ZERO_RATED | Z | 0 % | — (no BT-120 mention) |
| EXEMPT | E | 0 % | Exempt — art. 261 |
| REVERSE_CHARGE | AE | 0 % | Reverse charge — art. 283-2 |
| OUT_OF_SCOPE | O | 0 % | Out of scope — art. 259-1 |
| INTRACOM_GOODS | K | 0 % | Intra-EU supply of goods — art. 262 ter, I |
| EXPORT | G | 0 % | Export of goods — art. 262, I |
| FRANCHISE_BASE | E | 0 % | Small-business franchise — art. 293 B |
| EXEMPT_TRAINING | E | 0 % | Vocational training — art. 261-4-4°a |
The mention is NOT derived from the EN16931 code alone: FRANCHISE_BASE, EXEMPT and EXEMPT_TRAINING share code E but carry distinct mentions (293 B / 261 / 261-4-4°a). The applicative category is the source of truth for the mention.
Goods vs services — the supply_type field
| Destination | supply_type='services' | supply_type='goods' |
|---|---|---|
| FR → FR | STANDARD (S) | STANDARD (S) |
| FR → EU B2B (valid VAT) | REVERSE_CHARGE (AE) | INTRACOM_GOODS (K) |
| FR → non-EU | OUT_OF_SCOPE (O) | EXPORT (G) |
| FR → EU B2C / no VAT | FR VAT (art. 259-2) | FR VAT |
A line defaults to a service. Always set supply_type="goods" for a cross-border sale of physical goods.
VAT control fields (per line)
| Field | Description |
|---|---|
| vat_category | Explicit category (otherwise server-resolved). |
| supply_type | 'goods' | 'services' — discriminates K/G vs AE/O. |
| place_of_supply | ISO-2 country of place of supply (art. 259-A override). |
| vat_override_reason | Assume a divergent rate (avoids the 409, fiscal trail). |
Examples
# Prestation de SERVICES intra-UE B2B (numéro TVA valide VIES)
# -> REVERSE_CHARGE (AE), TVA 0 %, mention « Autoliquidation - Article 283-2 du CGI »
curl -X POST https://api.scell.io/api/v1/invoices \
-H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' \
-d '{
"direction": "outgoing", "output_format": "facturx", "issue_date": "2026-06-05",
"seller_siret": "12345678901234", "seller_name": "ACME SAS",
"buyer_name": "Müller GmbH", "buyer_country": "DE", "buyer_vat_number": "DE123456789",
"buyer_address": { "line1": "Hauptstr. 1", "postal_code": "10115", "city": "Berlin", "country": "DE" },
"lines": [
{ "description": "Conseil SaaS", "quantity": 1, "unit_price": 1000, "tax_rate": 0,
"vat_category": "REVERSE_CHARGE", "supply_type": "services" }
]
}'VIES check & 409 response
# Le serveur RE-RÉSOUT la TVA de chaque ligne (résolution autoritaire).
# Si le taux est incohérent (ex. 20 % sur une vente intra-UE B2B avec n° TVA valide)
# ET qu'aucune raison d'override n'est fournie -> 409, facture NON persistée :
HTTP/1.1 409 Conflict
{
"error": "VAT_CORRECTION_REQUIRED",
"message": "Le taux de TVA d'une ou plusieurs lignes est incohérent avec le contexte…",
"corrections": [
{
"line_index": 0,
"provided_rate": 20,
"suggested_rate": 0,
"suggested_category": "REVERSE_CHARGE",
"en16931_code": "AE",
"mention": "Autoliquidation - Article 283-2 du CGI",
"rule": "R2_eu_b2b_vat_valid"
}
],
"hint": "Acceptez les taux suggérés, ou renseignez vat_override_reason sur la ligne."
}
# Deux issues : (1) re-soumettre avec suggested_rate/suggested_category,
# (2) assumer votre taux en ajoutant "vat_override_reason" sur la ligne.In the SDKs, this 409 is typed: VatCorrectionRequiredException (PHP, getCorrections()/getHint()) and VatCorrectionRequiredError (JS, .corrections / .hint).
Electronic signatures (eIDAS)
Simple electronic signatures (EU-SES, eIDAS-compliant) on your PDFs, via OpenAPI.com. Multi-signer, SMS/email OTP, signature positions (in %, or pixels), UI customization, and download of the signed document + time-stamped proof. Built-in DOCX/DOC → PDF conversion (POST /signatures/convert-document).
# Demande de signature électronique simple (EU-SES, eIDAS) sur un PDF
curl -X POST https://api.scell.io/api/v1/signatures \
-H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' \
-d '{
"document_url": "https://votre-app.com/contrat.pdf",
"document_name": "Contrat de prestation",
"signers": [
{ "name": "Jean Dupont", "email": "jean@client.fr", "phone": "+33600000000",
"message": "Merci de signer (code: {OTP})" }
],
"signature_positions": [ { "page": 1, "x": 70, "y": 85, "unit": "percent" } ],
"signature_options": { "signature_mode": "both", "signer_must_read": true, "timezone": "Europe/Paris" }
}'
# Relancer / annuler / télécharger le document signé + la preuve
curl -X POST https://api.scell.io/api/v1/signatures/{id}/remind -H 'X-API-Key: sk_live_xxxxxxxx'
curl -X POST https://api.scell.io/api/v1/signatures/{id}/cancel -H 'X-API-Key: sk_live_xxxxxxxx'
curl https://api.scell.io/api/v1/signatures/{id}/download/signed -H 'X-API-Key: sk_live_xxxxxxxx' -o signed.pdf
curl https://api.scell.io/api/v1/signatures/{id}/download/proof -H 'X-API-Key: sk_live_xxxxxxxx' -o proof.pdfQuotes, deposits & balances
Create signable quotes (public link, accept/refuse without an account), with a payment schedule, then convert them into deposit invoices (VAT due, Factur-X type 386) and balance invoices (type 380, automatic deduction of deposits). The quote audit trail is outside the ISCA chain (separate SHA-256 chain).
# Créer un devis, l'envoyer, puis le convertir en acompte / solde
curl -X POST https://api.scell.io/api/v1/quotes \
-H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' \
-d '{
"buyer_name": "Client SARL", "buyer_siret": "98765432109876",
"lines": [ { "description": "Projet web", "quantity": 1, "unit_price": 10000, "tax_rate": 20 } ]
}'
curl -X POST https://api.scell.io/api/v1/quotes/{id}/send -H 'X-API-Key: sk_live_xxxxxxxx'
# Conversion en facture d'acompte (TVA exigible) puis solde (déduit les acomptes)
curl -X POST https://api.scell.io/api/v1/quotes/{id}/convert-to-deposit \
-H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' -d '{ "percent": 30 }'
curl -X POST https://api.scell.io/api/v1/quotes/{id}/convert-to-balance -H 'X-API-Key: sk_live_xxxxxxxx'
# Lien public signable (le client accepte/refuse sans compte) :
# GET /api/v1/public/quotes/{token} POST …/accept POST …/refuse GET …/pdfPayment schedule
Split a quote’s settlement into schedule lines (as a percentage of the total or a fixed amount, with a due date and milestone label). Each line can be converted into an invoice (deposit/balance), or auto-emitted at its due date via "auto_generate". POST replaces the whole schedule (max 50 lines), PATCH applies an add/update/remove batch.
# Échéancier de paiement d'un devis : définir des lignes en % OU en montant.
# POST remplace l'INTÉGRALITÉ de l'échéancier (max 50 lignes).
curl -X POST https://api.scell.io/api/v1/quotes/{quoteId}/payment-schedule \
-H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' \
-d '{
"lines": [
{ "amount_type": "percent", "amount_value": 30, "due_date": "2026-07-01",
"milestone_label": "Acompte à la commande", "auto_generate": true },
{ "amount_type": "percent", "amount_value": 40, "due_date": "2026-08-15",
"milestone_label": "Livraison V1" },
{ "amount_type": "amount", "amount_value": 3000.00, "due_date": "2026-09-30",
"milestone_label": "Solde", "description": "Réception définitive" }
]
}'
# amount_type : "percent" (% du total TTC du devis) | "amount" (montant fixe TTC)
# auto_generate : émet automatiquement la facture (acompte/solde) à l'échéance.
# Consulter, modifier par lot (add/update/remove), tout effacer
curl https://api.scell.io/api/v1/quotes/{quoteId}/payment-schedule -H 'X-API-Key: sk_live_xxxxxxxx'
curl -X PATCH https://api.scell.io/api/v1/quotes/{quoteId}/payment-schedule -H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' -d '{ "add": [ … ], "update": [ … ], "remove": [ "lineId" ] }'
curl -X DELETE https://api.scell.io/api/v1/quotes/{quoteId}/payment-schedule -H 'X-API-Key: sk_live_xxxxxxxx'
# Convertir une ligne d'échéance en facture (acompte/solde) + résumé agrégé
curl -X POST https://api.scell.io/api/v1/quotes/{quoteId}/payment-schedule/lines/{lineId}/convert -H 'X-API-Key: sk_live_xxxxxxxx'
curl https://api.scell.io/api/v1/quotes/{quoteId}/payment-summary -H 'X-API-Key: sk_live_xxxxxxxx'Quote preview (non-persisted)
POST /api/v1/quotes/preview renders the PDF of an in-progress quote without persisting anything or consuming a number. For a live A4 HTML preview (branding + template), use POST /api/v1/documents/preview with type:"quote".
# Aperçu PDF d'un devis EN COURS de saisie (rien n'est persisté, aucun numéro émis).
# Même corps qu'une création de devis ; renvoie le PDF directement.
curl -X POST https://api.scell.io/api/v1/quotes/preview \
-H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' \
-d '{
"buyer_name": "Client SARL", "buyer_siret": "98765432109876",
"currency": "EUR",
"lines": [ { "description": "Projet web", "quantity": 1, "unit_price": 10000, "tax_rate": 20 } ],
"notes": "Devis valable 30 jours."
}' -o devis-preview.pdf
# Astuce : POST /api/v1/documents/preview (type:"quote") rend l'aperçu HTML A4 live
# (avec branding + modèle), utilisé par l'écran de création du dashboard.Credit notes
A credit note ALWAYS targets an existing invoice. Total (full cancellation) or partial: a partial credit note selects lines from the source invoice (each line’s unit price and EXACT VAT rate are inherited — an invoice may mix 20% / 5.5% / exempt 0%). After issuing, the credit note is immutable (ISCA chain).
# 1) Découvrir les lignes encore créditables d'une facture
curl https://api.scell.io/api/v1/invoices/{invoiceId}/remaining-creditable -H 'X-API-Key: sk_live_xxxxxxxx'
# -> { "data": { "items": [ { "invoice_line_id": "…", "remaining_quantity": 2, "tax_rate": 20 } ],
# "can_be_credited": true } }
# 2) Avoir PARTIEL : sélectionner des lignes de la facture (prix + taux hérités par ligne)
curl -X POST https://api.scell.io/api/v1/credit-notes \
-H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' \
-d '{ "invoice_id": "019df46f-…", "reason": "Retour partiel", "type": "partial",
"items": [ { "invoice_line_id": "…", "quantity": 1 } ] }'
# Avoir TOTAL : { "invoice_id": "…", "reason": "Annulation", "type": "total" }
# 3) Émettre (immuable après — chaîne ISCA)
curl -X POST https://api.scell.io/api/v1/credit-notes/{id}/send -H 'X-API-Key: sk_live_xxxxxxxx'Buyers, suppliers & catalogue
Registries strictly scoped by (tenant, sub_tenant) — anti-IDOR. Idempotent on SIRET (B2B) or email (B2C). Once created, reuse a buyer via "buyer_id" on your invoices; its data is then frozen onto the issued invoice (fiscal immutability).
Suppliers are auto-derived from received invoices (POST /incoming-invoices) — no manual creation. Only email, phone, notes and metadata are editable via PATCH /api/v1/suppliers/:id. Identity fields (name, SIRET, address, etc.) come from the source invoice and are read-only.
The product/service catalogue (GET/POST /api/v1/products, GET/POST /api/v1/product-categories) lets you reuse line items: a quote, invoice or credit-note line can be pre-filled via "product_id", and the "save_to_catalog" flag stores a typed line into the catalogue (atomic server-side upsert, dedup by SKU then name). The "product_id" link on the line stays soft — the issued price/description remain frozen (ISCA immutability).
# Acheteur réutilisable (idempotent sur SIRET en B2B / email en B2C)
curl -X POST https://api.scell.io/api/v1/buyers \
-H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' \
-d '{
"name": "Client SARL", "is_individual": false, "country": "FR",
"siret": "98765432109876", "vat_number": "FR12987654321", "email": "compta@client.fr",
"billing_address": { "line1": "2 av. Lyon", "postal_code": "69001", "city": "Lyon", "country": "FR" }
}'
# Puis réutiliser sur une facture : { "buyer_id": "<uuid>", "lines": [ … ] }
# Fournisseurs : dérivés automatiquement des factures reçues (POST /api/v1/incoming-invoices).
# GET /api/v1/suppliers → lister | GET /api/v1/suppliers/:id → détail
# PATCH /api/v1/suppliers/:id → enrichir uniquement email / phone / notes / metadata
# ⚠ Pas de création manuelle (POST) ni suppression (DELETE) — identité figée depuis la facture source.Product / service catalogue
A product carries a default HT price, VAT rate and discount, a unit (UN/ECE code), a fiscal category (revenue_category: goods / service / accommodation) and, optionally, a custom organisation category. On an invoice, quote or credit-note line: "product_id" pre-fills the line; "save_to_catalog": true stores a typed line into the catalogue.
# Catégorie d'organisation (custom, distincte de la catégorie fiscale)
curl -X POST https://api.scell.io/api/v1/product-categories \
-H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' \
-d '{ "name": "Prestations web", "color": "#0066FF", "position": 1 }'
# Produit / service réutilisable
curl -X POST https://api.scell.io/api/v1/products \
-H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' \
-d '{
"name": "Forfait audit SEO",
"description": "Audit technique + plan d'\''action sur 3 mois",
"sku": "SEO-AUDIT-3M",
"revenue_category": "service", // goods | service | accommodation (fiscal)
"product_category_id": "<uuid catégorie>",
"unit": "C62", // code UN/ECE (C62 = unité)
"unit_price_ht": 1500.00,
"default_tax_rate": 20,
"default_discount_rate": 0,
"currency": "EUR",
"is_active": true
}'
# Lister (recherche q + filtres), détail, mise à jour, suppression
curl 'https://api.scell.io/api/v1/products?q=audit&revenue_category=service&is_active=true' -H 'X-API-Key: sk_live_xxxxxxxx'
curl https://api.scell.io/api/v1/products/{id} -H 'X-API-Key: sk_live_xxxxxxxx'
curl -X PATCH https://api.scell.io/api/v1/products/{id} -H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' -d '{ "unit_price_ht": 1600 }'
# Réutiliser un produit sur une ligne de facture/devis/avoir : "product_id" pré-remplit
# description/prix/TVA/unité. "save_to_catalog": true enregistre une ligne saisie (upsert).
curl -X POST https://api.scell.io/api/v1/invoices \
-H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' \
-d '{
"direction": "outgoing", "output_format": "facturx", "issue_date": "2026-06-05",
"buyer_name": "Client SARL", "buyer_siret": "98765432109876",
"lines": [
{ "product_id": "<uuid produit>", "quantity": 1 },
{ "description": "Maintenance mensuelle", "quantity": 1, "unit_price": 200, "tax_rate": 20,
"save_to_catalog": true, "product_category_id": "<uuid catégorie>" }
]
}'Branding & live previews
Email branding (GET/PATCH /api/v1/branding/tenant, /branding/sub-tenants/:id variant) carries the logo, primary color, footer and signature of your transactional emails. The "brand_email_enabled" field toggles customization (false = default channel branding); "computed_email_footer" (read-only) exposes the footer computed from your company identity, used at render time when "brand_email_footer" is empty. The logo is uploaded either via presigned S3 (POST …/logo-upload-url) or directly as multipart (POST …/logo, "logo" field, jpeg/png/webp/svg, max 2 MB — SVGs are normalized automatically).
On the invoice side, templates (/api/v1/invoice-templates) gain the "is_enabled" field (false = the template is skipped by the resolution cascade, falling back to the system template, without losing its configuration) and the POST /invoice-templates/derive-colors-from-email-logo endpoint, which extracts the dominant colors of your email logo and applies them to the default template (404 if no logo, 422 if the hues are too neutral).
Finally, two live HTML previews: GET /branding/tenant/preview accepts query overrides (brand_primary_color, brand_email_footer, brand_email_signature, brand_logo_url) to try a setting before saving it, and POST /api/v1/documents/preview renders an in-progress document (invoice, credit note or quote) with the real template, branding and legal mentions of the issuing company — nothing is persisted (Cache-Control: no-store). This endpoint powers the real-time A4 preview on the dashboard creation screens.
# Branding e-mail : lire / modifier (brand_email_enabled, couleurs, pied de page…)
curl https://api.scell.io/api/v1/branding/tenant -H 'X-API-Key: sk_live_xxxxxxxx'
curl -X PATCH https://api.scell.io/api/v1/branding/tenant \
-H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' \
-d '{ "brand_primary_color": "#0066FF", "brand_email_enabled": true }'
# Logo e-mail : upload direct multipart (jpeg/png/webp/svg, max 2 Mo)
curl -X POST https://api.scell.io/api/v1/branding/tenant/logo \
-H 'X-API-Key: sk_live_xxxxxxxx' -F 'logo=@logo.svg'
# Aperçu HTML de l'e-mail brandé, avec overrides non persistés (essai avant enregistrement)
curl 'https://api.scell.io/api/v1/branding/tenant/preview?brand_primary_color=%230066FF' \
-H 'X-API-Key: sk_live_xxxxxxxx'
# Appliquer au modèle de facture les couleurs extraites du logo e-mail
curl -X POST https://api.scell.io/api/v1/invoice-templates/derive-colors-from-email-logo \
-H 'X-API-Key: sk_live_xxxxxxxx'
# Aperçu HTML live d'un document en cours de saisie (rien n'est persisté)
curl -X POST https://api.scell.io/api/v1/documents/preview \
-H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' \
-d '{
"type": "invoice",
"buyer": { "name": "Client SARL", "siret": "98765432109876" },
"lines": [{ "description": "Prestation conseil", "quantity": 2, "unit_price": 450.00, "tax_rate": 20 }]
}'Credits & Scell.io billing
Scell.io is prepaid: you consume credits on each issuance. Track consumption and balance, top up (Stripe) or buy packs. Your consumption statements and Scell.io invoices are available via the API.
# Consommation et solde du compte Scell.io (modèle prépayé)
curl https://api.scell.io/api/v1/tenant/billing/usage -H 'X-API-Key: sk_live_xxxxxxxx'
curl https://api.scell.io/api/v1/tenant/balance -H 'X-API-Key: sk_live_xxxxxxxx'
# Recharger des crédits (Stripe PaymentIntent) ou acheter un pack
curl -X POST https://api.scell.io/api/v1/tenant/billing/top-up \
-H 'X-API-Key: sk_live_xxxxxxxx' -H 'Content-Type: application/json' -d '{ "amount": 5000 }'
curl -X POST https://api.scell.io/api/v1/tenant/billing/packs/{packSlug}/checkout -H 'X-API-Key: sk_live_xxxxxxxx'Error handling
| HTTP | Meaning |
|---|---|
| 200 / 201 | Success |
| 401 | Unauthenticated (TENANT_NOT_RESOLVED if key has no tenant) |
| 402 | Insufficient balance (credits) |
| 403 | KYB/KYC required, sub-tenant not ready (production) |
| 404 | Not found / out of scope (SUB_TENANT_NOT_FOUND, anti-IDOR) |
| 409 | Business conflict (VAT_CORRECTION_REQUIRED, QUOTE_NOT_EDITABLE) |
| 422 | Validation (per-field) or fiscal rule (NO_ISSUER_COMPANY, SUB_TENANT_HAS_FISCAL_ENTRIES) |
| 429 | Rate limit exceeded (Retry-After header) |
| 5xx | Server error |
// Corps d'erreur normalisé. La validation (422) détaille par champ ;
// les erreurs métier portent un code dans "error".
{ "message": "…", "errors": { "lines.0.tax_rate": ["…"] } } // 422 validation
{ "error": "VAT_CORRECTION_REQUIRED", "message": "…", "corrections": [ … ] } // 409 métier
// Codes métier utiles à intercepter :
// 401 TENANT_NOT_RESOLVED clé sans tenant rattaché
// 404 SUB_TENANT_NOT_FOUND sub_tenant_id hors scope (anti-IDOR)
// 422 NO_ISSUER_COMPANY aucune company émettrice résolue
// 403 KYB_REQUIRED / KYC_REQUIRED production : vérification incomplète
// 403 SUB_TENANT_NOT_READY onboarding sous-tenant non vérifié (prod)
// 409 VAT_CORRECTION_REQUIRED taux TVA incohérent (voir section TVA)
// 409 QUOTE_NOT_EDITABLE devis verrouillé (accepté/signé)
// 422 SUB_TENANT_HAS_FISCAL_ENTRIES suppression refusée (ledger ISCA)
// 429 rate limit dépassé (header Retry-After)SDKs
Three official SDKs (v3.5.0) expose the whole API with strict typing, builders (invoice lines, quotes) and typed business exceptions (including VAT_CORRECTION_REQUIRED). The MCP server gives AI agents access to 148 tools (all prefixed scell_). Details and examples on the SDK page.
TypeScript / JS
@scell/sdk
v3.5.0
npm i @scell/sdkPHP
scell/sdk
v3.5.0
composer require scell/sdkMCP (agents IA)
@scell/mcp-client
v3.5.0 · 148 tools
npx @scell/mcp-clientQuick Start
pk_test_* (from your dashboard) to test the component in sandbox mode. Test data is not persisted.The Scell.io onboarding component handles the entire company verification flow: SIREN/SIRET lookup, VAT number validation, address verification, and legal representative confirmation.
Step 1: Include the script
<script src="https://cdn.scell.io/widget/v1/onboarding.js"></script>Step 2: Add the component
<scell-onboarding
publishable-key="pk_live_xxxxxxxx"
external-id="user_42"
callback-url="https://votre-app.com/onboarding/done"
theme="auto"
locale="fr"
></scell-onboarding>Step 3: Handle events
const widget = document.querySelector('scell-onboarding');
widget.addEventListener('onboarding:completed', (event) => {
// event.detail respecte le type OnboardingCompletedPayload
const { subTenant, credentials, externalId } = event.detail;
// subTenant : { id, tenant_id, name, siret, vat_number, onboarding_status, is_active }
// onboarding_status : pending_superpdp | superpdp_redirected | superpdp_authorized
// | superpdp_pending_review | active | superpdp_failed
// credentials : { api_endpoint, parent_tenant_id, sub_tenant_id, external_id, superpdp_company_id }
// A partir d'ici votre backend peut emettre des factures via :
// POST {credentials.api_endpoint}/invoices
// X-API-Key: <votre sk_live_*> (cle parent — securisee, JAMAIS dans le navigateur)
// body: { "sub_tenant_id": subTenant.id, ... } (scope sur le sub-tenant)
});
widget.addEventListener('onboarding:error', (event) => {
console.error(event.detail.code, event.detail.message);
});Live Demo
This demo uses a test publishable key. Create your account to get your own pk_test_* key and test the widget with your data.
Test the Scell.io Onboarding component directly below. This widget allows your users to verify their company (SIREN, VAT, legal representative) and complete their registration.
This widget runs in sandbox mode. To integrate it, see the Quick Start guide above.
API Reference
Attributes
Configure the component behavior using HTML attributes.
| Attribute | Type | Required | Description |
|---|---|---|---|
publishable-key | string | Yes | Your publishable key (pk_live_xxx or pk_test_xxx) |
external-id | string | No | Your internal identifier for this sub-tenant (link with your system) |
callback-url | string | No | Full-page redirect URL after onboarding (used when widget is NOT inside a popup, e.g. embedded iframe without opener). Receives ?onboarding=success&sub_tenant_id=…&external_id=… |
theme | "light" | "dark" | "auto" | No | Color theme. Default: "light" |
locale | "fr" | "en" | No | Widget language. Default: "fr" |
width | string | No | Widget width. Default: "100%" |
height | string | No | Widget height. Default: "600px" |
white-label | boolean (attribut HTML) | No | White-label mode: hides the header with the Scell.io logo. The progress tracker and business content remain visible. Bare attribute (<scell-onboarding white-label>) is enough, or use "true" / "1" / "on" / "yes". Default: Scell.io header visible. |
White-label mode
The white-label attribute hides the header with the Scell.io logo. The progress tracker and all business content (SIRET form, SUPER PDP redirect, completion screen) stay visible. Useful when you embed the widget without any visible Scell.io mention.
Accepted values: bare attribute (<scell-onboarding white-label>), or "true" / "1" / "on" / "yes". Any other value (including "false" and a missing attribute) keeps the header visible.
<!-- Mode marque blanche (attribut nu, ou white-label="true") -->
<scell-onboarding
publishable-key="pk_live_xxxxxxxx"
external-id="user_42"
callback-url="https://votre-app.com/onboarding/done"
theme="light"
locale="fr"
white-label
></scell-onboarding>Events
Listen to component events to react to user actions and state changes.
| Event | Payload | Description |
|---|---|---|
onboarding:started | { sessionId: string } | An onboarding session has been created on Scell.io. No SUPER PDP call yet. |
onboarding:step | { step: "connect" | "redirect" | "complete", progress: number } | The user moved to a new step (0% → 50% → 100%). |
onboarding:completed | { subTenant: SubTenantSummary, credentials: OnboardingCredentials, externalId?: string } | The Scell.io sub-tenant is created and ready for invoicing. SUPER PDP OAuth 2.1 tokens are encrypted at rest and used automatically by the backend to transmit invoices. |
onboarding:error | { code: string, message: string } | An error occurred (popup closed, invalid CSRF state, SUPER PDP KYB failure, etc.). |
How it works
The <scell-onboarding> widget is the only official integration method. It drives an OAuth 2.1 + PKCE flow that creates both a Scell.io sub-tenant and the SUPER PDP registration in the same user session.
- 1The widget calls POST /onboarding/sessions with your publishable key (pk_*) to create a session linked to your parent tenant.
- 2The widget calls POST /onboarding/superpdp/authorize: the Scell.io backend generates PKCE (code_verifier/code_challenge S256), a CSRF state, and returns the SUPER PDP authorize URL.
- 3The widget opens a popup to that URL. The end user authenticates with SUPER PDP and completes KYB (SIREN, VAT, legal representative).
- 4SUPER PDP redirects to POST /onboarding/superpdp/callback with a code and the state.
- 5The Scell.io backend exchanges code+verifier for tokens (access + refresh), fetches the company info, and creates a sub-tenant linked to your parent tenant (with its own sk_/pk_ keys, invoice numbering, balance).
- 6The widget receives the scell:onboarding:complete event with the sub_tenant_id and initial credentials.
Why a single mode?
Since May 5th 2026, the 'Redirect Flow' and 'API Only' modes are removed. The widget covers every use case while guaranteeing OAuth 2.1 + PKCE compliance and parent/sub-tenant isolation. OAuth code exchange, refresh token rotation, and SUPER PDP KYB are handled by the Scell.io backend — you only embed one line of code.
Widget: SUPER PDP reconnect
Advanced widget variant. The mode="superpdp" mode exposes only the SUPER PDP step for a sub-tenant that is already created and validated. Use case: reconnect — or force the reconnection of — an existing sub-tenant, without going through identity, Sirene lookup or creation again. Requires the widget v3.2.0+ (CDN https://cdn.scell.io/widget/v1/onboarding.js).
2 steps: (1) server-side, your backend mints a signed token scoped to a sub-tenant using your sk_* key; (2) client-side, you embed the widget with that token. The resume token is an HMAC-signed URL bound to the targeted sub-tenant — a publishable pk_* key alone CANNOT target an arbitrary sub-tenant (anti-IDOR).
Step 1 — Mint the token (server-side)
Call POST /tenant/sub-tenants/{id}/superpdp-widget-token with your sk_* key (never exposed to the browser). The optional body { "reset": true } disconnects (revokes + resets) the sub-tenant before minting, to force a clean reconnection. The response returns a resume_token (signed URL, 24h TTL).
# 1) COTE SERVEUR — emettre un jeton signe scope a UN sub-tenant existant.# La cle sk_* appartient au tenant parent : JAMAIS exposee au navigateur.# body { "reset": true } deconnecte (revoque + reset) le sub-tenant AVANT# d'emettre le jeton, pour FORCER une reconnexion SUPER PDP propre.curl -X POST https://api.scell.io/api/v1/tenant/sub-tenants/{sub_tenant_id}/superpdp-widget-token \ -H 'X-API-Key: sk_live_xxxxxxxx' \ -H 'Content-Type: application/json' \ -d '{ "reset": true }'# Reponse 200 :# {# "resume_token": "https://api.scell.io/api/v1/widget/onboarding/sub-tenant/{id}/superpdp-resume?expires=1750000000&signature=abcd...",# "expires_at": "2026-06-09T12:00:00Z" <- TTL 24h# }## Securite : 'resume_token' est une URL signee HMAC, scopee a CE sub-tenant# (anti-IDOR). Une cle publishable pk_* seule ne permet PAS de cibler un# sub-tenant arbitraire — seul le serveur (cle sk_*) peut emettre ce jeton.Step 2 — Embed the widget (client-side)
Pass the resume_token obtained in step 1 into the resume-token attribute. The widget opens the SUPER PDP OAuth popup directly (single step) and emits the same events as the full flow (onboarding:completed, onboarding:error).
<!-- 2) COTE CLIENT — embarquer le widget en mode "superpdp". N'expose QUE l'etape SUPER PDP : ouvre directement la popup OAuth, sans repasser par identite / Sirene / creation du sub-tenant. Requiert le widget v3.2.0+. --><script src="https://cdn.scell.io/widget/v1/onboarding.js"></script><scell-onboarding mode="superpdp" resume-token="https://api.scell.io/api/v1/widget/onboarding/sub-tenant/{id}/superpdp-resume?expires=...&signature=..."></scell-onboarding><script> document.querySelector('scell-onboarding') .addEventListener('onboarding:completed', (e) => { // Memes events que le flux complet : { subTenant, credentials } console.log('SUPER PDP reconnecte :', e.detail.subTenant.id); }); document.querySelector('scell-onboarding') .addEventListener('onboarding:error', (e) => { console.error(e.detail.code, e.detail.message); });</script>| Attribute | Required | Description |
|---|---|---|
mode | Yes | Must be "superpdp" to expose only the SUPER PDP step. |
resume-token | Yes | Signed URL minted in step 1 (POST .../superpdp-widget-token). Scoped to the targeted sub-tenant, 24h TTL. |
Sandbox vs Production
Scell.io provides two fully isolated environments. The mode is automatically determined by the prefix of the publishable key you pass to the widget — no extra configuration needed.
| Criterion | Sandbox | Production |
|---|---|---|
| Publishable key prefix | pk_test_* | pk_live_* |
| Secret key prefix | sk_test_* | sk_live_* |
| Database | Isolated. Test data, no fiscal value. | Real data, retained 10 years (ISCA self-certification). |
| SUPER PDP | SUPER PDP sandbox account. Invoices are NOT transmitted to the real Peppol/PPF network. | SUPER PDP production account. Real transmission on the French electronic network (PPF). |
| SUPER PDP KYB | Simplified KYB — any test SIREN works. No real verification. | Full KYB (SIREN, VAT, legal representative verified by SUPER PDP). |
| Sub-tenants | Created in the sandbox database, never visible from your live keys. | Created in the production database, immediately operational for invoicing. |
| Pricing | Free, unlimited. | See Pricing page. |
How to switch
Just swap the value of the publishable-key attribute. The widget, the Scell.io backend, and the underlying SUPER PDP account all switch automatically. No code, API URL, or configuration changes required.
Example — switching from test to live
-<scell-onboarding publishable-key="pk_test_xxxxxxxx" />
+<scell-onboarding publishable-key="pk_live_xxxxxxxx" />Best practices
- Test in sandbox until the user journey is fully validated BEFORE switching to live.
- Store live keys in server-side environment variables — NEVER in the browser.
- pk_test_* keys are public by design (front-end OK). sk_test_*/sk_live_* keys are secret (back-end only).
- An invoice issued in sandbox has no legal or fiscal value.
Framework Examples
Copy-paste examples for popular frameworks.
import { useEffect, useRef } from 'react';// Declarer le custom element pour TypeScript (React 19)declare module 'react' { namespace JSX { interface IntrinsicElements { 'scell-onboarding': React.DetailedHTMLProps< React.HTMLAttributes<HTMLElement> & { 'publishable-key': string; 'external-id'?: string; 'callback-url'?: string; 'theme'?: 'light' | 'dark' | 'auto'; 'locale'?: 'fr' | 'en'; 'width'?: string; 'height'?: string; }, HTMLElement >; } }}interface OnboardingResult { subTenant: { id: string; tenant_id: string; name: string; siret: string | null; vat_number: string | null; onboarding_status: | 'pending_superpdp' | 'superpdp_redirected' | 'superpdp_authorized' | 'superpdp_pending_review' | 'active' | 'superpdp_failed'; is_active: boolean; }; credentials: { api_endpoint: string; parent_tenant_id: string; sub_tenant_id: string; external_id: string | null; superpdp_company_id: string | null; }; externalId?: string;}interface Props { publishableKey: string; externalId?: string; callbackUrl?: string; onComplete?: (result: OnboardingResult) => void; onError?: (err: { code: string; message: string }) => void;}export function ScellOnboarding({ publishableKey, externalId, callbackUrl, onComplete, onError }: Props) { const ref = useRef<HTMLElement>(null); useEffect(() => { // Le script CDN s'auto-enregistre comme custom element global, // donc on n'a a le charger qu'une seule fois par page. if (document.querySelector('script[data-scell-widget]')) return; const script = document.createElement('script'); script.src = 'https://cdn.scell.io/widget/v1/onboarding.js'; script.async = true; script.dataset.scellWidget = 'true'; document.head.appendChild(script); }, []); useEffect(() => { const el = ref.current; if (!el) return; const handleComplete = (e: Event) => onComplete?.((e as CustomEvent<OnboardingResult>).detail); const handleError = (e: Event) => onError?.((e as CustomEvent<{ code: string; message: string }>).detail); el.addEventListener('onboarding:completed', handleComplete); el.addEventListener('onboarding:error', handleError); return () => { el.removeEventListener('onboarding:completed', handleComplete); el.removeEventListener('onboarding:error', handleError); }; }, [onComplete, onError]); return ( <scell-onboarding ref={ref} publishable-key={publishableKey} external-id={externalId} callback-url={callbackUrl} theme="auto" locale="fr" /> );}OAuth 2.1 redirect URIs registered with SUPER PDP
Reference info: Scell.io declared 3 official redirect_uris on the SUPER PDP side. You DO NOT need to configure anything to embed the widget — these URIs are used internally by Scell flows.
| URL | Purpose |
|---|---|
/api/v1/widget/oauth-callback | Callback for the <scell-onboarding> widget embedded on your site. Creates the sub-tenant + returns sub_tenant + credentials to the widget via postMessage. |
/api/v1/onboarding/superpdp/callback | Legacy POST endpoint kept for widget v1 integrations (back-compat). Same response contract as widget v2. |
/api/v1/me/superpdp/callback | Self-service onboarding from the Scell.io dashboard (logged-in tenant admin). Not related to widget integration. |
Recurring Invoices
Automate invoice emission on a cadence (subscriptions, rent, maintenance contracts). You define a recurring profile once; Scell.io then emits each invoice on its due date, hands-free.
Key concepts
- Editable template, frozen invoices. The recurring profile is an editable model. On each due date, Scell.io snapshots the CURRENT profile state into a real invoice, with its own ISCA chain. Editing the profile only affects FUTURE emissions — never invoices already issued.
auto_sendsubmits the invoice to the PDP and emails the buyer with the Factur-X PDF.draftcreates a draft for review (no sending).- Bilateral notifications + J-N reminder. Both issuer and recipient are notified on each cycle; a reminder is sent
notify_before_daysdays before the due date. - Never silently skipped. A failed cycle produces a visible
failedoccurrence (with its error); it is never dropped without a trace.
Endpoints
Base: /api/v1. Auth: X-API-Key: sk_* (server-side only).
| Method & path | Description |
|---|---|
GET /recurring-invoices | List (filters: status, sub_tenant_id, per_page) |
POST /recurring-invoices | Create a profile (201) |
GET /recurring-invoices/{id} | Get a profile |
PUT /recurring-invoices/{id} | Update (only affects future emissions) |
DELETE /recurring-invoices/{id} | Delete the profile (emitted invoices untouched) |
GET /recurring-invoices/{id}/occurrences | Occurrence history |
POST /recurring-invoices/{id}/pause | Pause |
POST /recurring-invoices/{id}/activate | Activate |
POST /recurring-invoices/{id}/cancel | Cancel (terminal) |
POST /recurring-invoices/{id}/run-now | Emit the next occurrence now (202) |
Enums
| Field | Values |
|---|---|
recurrence.interval_unit | day | week | month | year |
end_mode | never | on_date | after_occurrences |
emission_mode | draft | auto_send (default auto_send) |
status | active | paused | completed | cancelled |
occurrence status | pending | emitted | failed | skipped |
Create payload schema
title, lines, recurrence and start_date are required. The buyer is set via buyer_id (registry) OR the flat buyer_* fields. day_of_month is clamped to the month length (31 → 28/29/30).
{ "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}Examples
# Créer un profil de facture récurrente (la clé sk_* est server-side)curl -X POST https://api.scell.io/api/v1/recurring-invoices \ -H 'X-API-Key: sk_live_xxxxxxxx' \ -H 'Content-Type: application/json' \ -d '{ "title": "Abonnement SaaS — Plan Pro", "buyer_id": "b1f2c3d4-5e6f-7081-92a3-b4c5d6e7f809", "output_format": "facturx", "lines": [ { "description": "Abonnement mensuel Plan Pro", "quantity": 1, "unit_price": 49.00, "vat_rate": 20 } ], "recurrence": { "interval_unit": "month", "interval_count": 1, "day_of_month": 1 }, "start_date": "2026-07-01", "end_mode": "after_occurrences", "max_occurrences": 12, "emission_mode": "auto_send", "notify_before_days": 3 }'# Réponse 201 (extrait) :# { "id": "r1e2c3u4-...", "status": "active", "next_occurrence_at": "2026-07-01" }# Lister, mettre en pause, réactiver, émettre maintenant (202), annulercurl https://api.scell.io/api/v1/recurring-invoices?status=active -H 'X-API-Key: sk_live_xxxxxxxx'curl https://api.scell.io/api/v1/recurring-invoices/{id}/occurrences -H 'X-API-Key: sk_live_xxxxxxxx'curl -X POST https://api.scell.io/api/v1/recurring-invoices/{id}/pause -H 'X-API-Key: sk_live_xxxxxxxx'curl -X POST https://api.scell.io/api/v1/recurring-invoices/{id}/activate -H 'X-API-Key: sk_live_xxxxxxxx'curl -X POST https://api.scell.io/api/v1/recurring-invoices/{id}/run-now -H 'X-API-Key: sk_live_xxxxxxxx' # 202 Acceptedcurl -X POST https://api.scell.io/api/v1/recurring-invoices/{id}/cancel -H 'X-API-Key: sk_live_xxxxxxxx'Webhooks
Receive real-time notifications when onboarding events occur. Configure your webhook URL in the dashboard.
x-scell-signature header.Payload received in the widget (onboarding:completed event)
This payload is delivered both via window.opener.postMessage(...) (to the parent hosting the widget) and as event.detail on the onboarding:completed event of the <scell-onboarding> element.
// Payload envoye par window.opener.postMessage(...) ET recu via// l'event 'onboarding:completed' (event.detail) cote widget consumer.{ "type": "scell:onboarding:complete", "success": true, "sub_tenant": { "id": "01975f7c-...", "tenant_id": "01975f78-...", "name": "ACME SAS", "siret": "12345678901234", "vat_number": "FR12345678901", "onboarding_status": "active", "is_active": true }, "credentials": { "api_endpoint": "https://api.scell.io/api/v1", "parent_tenant_id": "01975f78-...", "sub_tenant_id": "01975f7c-...", "external_id": "user_42", "superpdp_company_id": "spp_company_99" }}Issue an invoice right after onboarding
As soon as onboarding:completed fires, the sub-tenant is operational: SUPER PDP OAuth 2.1 tokens encrypted at rest, KYB validated, isolated invoice numbering. Zero wait.
// Apres l'event onboarding:completed, votre backend peut emettre des// factures Factur-X au nom du sub-tenant immediatement. Les tokens// OAuth 2.1 SUPER PDP sont chiffres en base et utilises automatiquement// par Scell pour transmettre la facture chez SUPER PDP.//// IMPORTANT — Numerotation : ne jamais passer 'invoice_number' dans le// body. La numerotation est entierement geree par Scell.io. La facture// recoit un identifiant brouillon 'DRAFT-XXXXX' a la creation, puis un// numero definitif 'XXXXX-YYYYMM-NNNNN' a l'emission (sequence chrono-// logique sans rupture, conforme aux articles 242 nonies A et 289 du CGI).// Le numero attribue est retourne dans la reponse (champ 'invoice_number').// Modele d'auth : la cle sk_live_* appartient au TENANT parent. Pour// emettre au nom d'un sub-tenant, on passe son 'sub_tenant_id' dans le// body. La company emettrice est resolue automatiquement par Scell.io —// aucun 'company_id' a fournir.const res = await fetch('https://api.scell.io/api/v1/invoices', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.SCELL_PARENT_SECRET_KEY, // sk_live_* (parent — JAMAIS dans le navigateur) }, body: JSON.stringify({ sub_tenant_id: subTenant.id, // scope sur le sub-tenant issue_date: '2026-05-05', buyer_siret: '98765432100012', buyer_name: 'Customer SAS', lines: [ { description: 'Prestation', quantity: 1, unit_price_excl_tax: 1000, vat_rate: 20 } ], }),});// Reponse (extrait) :// {// "id": "inv_01975f80...",// "invoice_number": "T0001-202605-00042", <- attribue par Scell.io// "status": "issued",// "issue_date": "2026-05-05",// "total_excl_tax": 1000,// ...// }const { invoice_number } = await res.json();Available Webhook Events
| Event | Description |
|---|---|
sub_tenant.onboarded | A Scell.io sub-tenant was just created via the widget. The payload contains sub_tenant_id, external_id, siret, onboarding_status. Persist the mapping in your database and enable invoicing for this client. |
invoice.transmitted | An invoice issued for this sub-tenant was successfully transmitted to SUPER PDP. Includes superpdp_id and timestamp. |
invoice.accepted | The invoice was accepted by the recipient (Factur-X lifecycle). |
invoice.rejected | The invoice was rejected by the recipient or SUPER PDP. Payload includes rejection_reason. |
invoice.incoming.received | An incoming invoice for this sub-tenant just arrived via SUPER PDP. Scell.io resolves it automatically by superpdp_company_id and exposes it in /tenant/incoming-invoices. |
subtenant.threshold.warning | A micro-entrepreneur sub-tenant reached 80% or 90% of a threshold (VAT franchise or micro ceiling). Payload: sub_tenant_id, category, kind, percent, projected_crossing_date. |
subtenant.threshold.vat_base_exceeded | The VAT franchise base threshold was exceeded: VAT becomes due on January 1st of the following year. It is up to the sub-tenant to act (VAT registration URSSAF/INPI). |
subtenant.threshold.vat_majored_exceeded | The VAT franchise increased threshold was exceeded: VAT is due immediately, retroactively to the 1st of the month of overshoot. |
subtenant.threshold.micro_exceeded | The micro-enterprise ceiling was exceeded: possible exit from the regime after 2 consecutive calendar years of overshoot. |
Ready to integrate?
Create your account and get your API keys in minutes. Start with 100 free credits.