Documentation développeur Scell.io
Guide complet de l'API Scell.io : facturation électronique (Factur-X / UBL / CII), TVA & autoliquidation intra-UE, signatures eIDAS, devis, avoirs, factures récurrentes, conformité fiscale ISCA — plus le widget d'onboarding sans code. Exemples curl et SDK (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 est une plateforme d'API pour la facturation électronique (Factur-X / UBL / CII, conforme EN16931 et à la réforme française) et la signature électronique simple (eIDAS EU-SES). Ce guide couvre l'intégralité de l'API par domaine fonctionnel, avec des exemples curl et SDK. Pour la référence exhaustive endpoint par endpoint, consultez la référence OpenAPI.
Facturation
Factur-X / UBL / CII, transmission SUPER PDP / PEPPOL, cycle de vie, avoirs, devis, récurrentes.
Signatures eIDAS
Signature électronique simple via OpenAPI.com, multi-signataires, OTP, preuve horodatée.
Conformité fiscale
Chaîne ISCA immuable (SHA-256), clôtures, ancrage OpenTimestamps, export FEC.
URL de base & environnements
Un seul host pour la production ET le bac à sable. C'est le préfixe de la clé API qui sélectionne l'environnement (et la base de données isolée).
# 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.Authentification
Quatre modes d'authentification selon le contexte. La règle d'or : les clés secrètes sk_* sont SERVER-SIDE uniquement (jamais dans un navigateur ou une app mobile).
| Clé / mode | Header | Usage |
|---|---|---|
| sk_live_* / sk_test_* | X-API-Key | Server-side. Émission factures, avoirs, signatures, webhooks. Pleine portée tenant. |
| pk_live_* / pk_test_* | X-Publishable-Key | Front / widget. Endpoints widget publics uniquement. Safe côté navigateur. |
| Bearer (Sanctum) | Authorization: Bearer | Dashboard SPA app.scell.io (cookies HttpOnly) ou clients API. |
| X-Tenant-Key (legacy) | X-Tenant-Key | Ancien multi-tenant. Préférer sk_*/pk_* pour tout nouveau projet. |
# 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 projetCréer un compte & des clés
# 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>'Concepts clés
Modèle multi-locataires
TenantLe compte maître (le vôtre). La clé sk_* appartient au tenant.Sub-tenantUn client final que vous facturez en marque blanche (créé via le widget d’onboarding). On le cible via "sub_tenant_id" dans le corps de la requête.CompanyL’entité émettrice (SIRET, IBAN, mentions Factur-X). Résolue automatiquement par Scell.io.Buyer / SupplierRegistres scopés (tenant, sub_tenant). Acheteurs : créés manuellement ou upsert via facture. Fournisseurs : dérivés automatiquement des factures reçues — pas de création manuelle, enrichissement email/phone/notes/metadata uniquement.ProductCatalogue produits/services réutilisable scopé (tenant, sub_tenant). Pré-remplit une ligne via "product_id" ; la case "save_to_catalog" enregistre une ligne saisie. Catégorisation fiscale (revenue_category) + catégories personnalisées.
Numérotation & immuabilité
Ne passez jamais "invoice_number" : Scell.io le génère. À la création, la facture est en brouillon (numéro DRAFT-…) ; à l'émission (submit), elle reçoit un numéro définitif séquentiel sans rupture (CGI art. 242 nonies A & 289) et devient immuable (chaîne ISCA SHA-256). Toute correction passe par un avoir.
Facturation électronique (Factur-X)
Émettez des factures conformes Factur-X (PDF/A-3 + XML EN16931 embarqué), UBL ou CII. Le cycle de vie : création (brouillon) → émission (submit : scellement du numéro + transmission SUPER PDP / PEPPOL) → suivi (transmise, acceptée, rejetée, payée).
# 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.xmlCycle de vie & endpoints
| Action | Endpoint | Effet |
|---|---|---|
| Créer | POST /invoices | Brouillon, numéro DRAFT-… |
| Modifier | PUT /invoices/{id} | Tant que brouillon |
| Émettre | POST /invoices/{id}/submit | Scelle le n° + transmet (immuable) |
| Marquer payée | POST /invoices/{id}/mark-paid | BT-81 moyen de paiement requis |
| Télécharger | GET /invoices/{id}/download/{pdf|xml} | Factur-X / XML |
| Piste d’audit | GET /invoices/{id}/audit-trail | Historique horodaté |
| Lot | POST /invoices/bulk-submit · bulk-status | Traitement en masse |
B2C (particulier)
# 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 } ]
}'Associés : factures entrantes (GET /tenant/invoices/incoming, accept/reject/dispute/mark-paid), modèles de facture (/invoice-templates, dont POST /invoice-templates/derive-colors-from-email-logo et le champ is_enabled), aperçu HTML live non persisté (POST /documents/preview), acomptes & soldes (voir Devis). Pour la TVA intracommunautaire et l'autoliquidation, voir la section suivante.
TVA & autoliquidation intra-UE
Scell.io résout la TVA de manière AUTORITAIRE : à l'émission, le serveur recalcule la catégorie EN16931 attendue de chaque ligne d'après le contexte (vendeur, acheteur, pays, validité VIES du numéro de TVA, nature biens/services) et émet le code + la mention légale exacte dans le Factur-X. C'est une protection contre le redressement (responsabilité solidaire si 0 % appliqué sans contrôle).
Les 12 catégories de TVA
| Catégorie | EN16931 | Taux FR | Mention légale (CGI) |
|---|---|---|---|
| STANDARD | S | 20 % | — (art. 278) |
| INTERMEDIATE | S | 10 % | — |
| REDUCED | S | 5,5 % | — |
| SUPER_REDUCED | S | 2,1 % | — |
| ZERO_RATED | Z | 0 % | — (pas de mention BT-120) |
| EXEMPT | E | 0 % | Opération exonérée — art. 261 |
| REVERSE_CHARGE | AE | 0 % | Autoliquidation — art. 283-2 |
| OUT_OF_SCOPE | O | 0 % | TVA non applicable — art. 259-1 |
| INTRACOM_GOODS | K | 0 % | Livraison intracom. de biens — art. 262 ter, I |
| EXPORT | G | 0 % | Exportation de biens — art. 262, I |
| FRANCHISE_BASE | E | 0 % | Franchise en base — art. 293 B |
| EXEMPT_TRAINING | E | 0 % | Formation pro. — art. 261-4-4°a |
La mention ne se déduit PAS du seul code EN16931 : FRANCHISE_BASE, EXEMPT et EXEMPT_TRAINING partagent le code E mais portent des mentions distinctes (293 B / 261 / 261-4-4°a). La catégorie applicative est la source de vérité de la mention.
Biens vs services — le champ supply_type
| Destination | supply_type='services' | supply_type='goods' |
|---|---|---|
| FR → FR | STANDARD (S) | STANDARD (S) |
| FR → UE B2B (TVA valide) | REVERSE_CHARGE (AE) | INTRACOM_GOODS (K) |
| FR → hors UE | OUT_OF_SCOPE (O) | EXPORT (G) |
| FR → UE B2C / sans TVA | TVA FR (art. 259-2) | TVA FR |
Par défaut une ligne est traitée comme un service. Renseignez systématiquement supply_type="goods" pour une vente transfrontalière de biens physiques.
Champs de pilotage TVA (par ligne)
| Champ | Description |
|---|---|
| vat_category | Catégorie explicite (sinon résolue par le serveur). |
| supply_type | 'goods' | 'services' — discrimine K/G vs AE/O. |
| place_of_supply | Pays ISO-2 du lieu de prestation (override art. 259 A). |
| vat_override_reason | Assume un taux divergent (évite le 409, trace fiscale). |
Exemples
# 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" }
]
}'Contrôle VIES & réponse 409
# 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.Côté SDK, ce 409 est typé : VatCorrectionRequiredException (PHP, getCorrections()/getHint()) et VatCorrectionRequiredError (JS, .corrections / .hint).
Signatures électroniques (eIDAS)
Signature électronique simple (EU-SES, conforme eIDAS) sur vos PDF, via OpenAPI.com. Multi-signataires, OTP par SMS/email, positions de signature (en %, ou pixels), personnalisation de l'interface, et téléchargement du document signé + de la preuve horodatée. Conversion DOCX/DOC → PDF intégrée (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.pdfDevis, acomptes & soldes
Créez des devis signables (lien public, accepte/refuse sans compte), avec échéancier de paiement, puis convertissez-les en factures d'acompte (TVA exigible, Factur-X type 386) et de solde (type 380, déduction automatique des acomptes). L'audit trail des devis est hors chaîne ISCA (chaîne SHA-256 séparée).
# 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 …/pdfÉchéancier de paiement
Découpez le règlement d'un devis en lignes d'échéance (en pourcentage du total ou en montant fixe, avec date d'échéance et libellé de jalon). Chaque ligne peut être convertie en facture (acompte/solde), ou émise automatiquement à l'échéance via "auto_generate". POST remplace l'intégralité de l'échéancier (max 50 lignes), PATCH applique un lot add/update/remove.
# É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'Aperçu du devis (non persisté)
POST /api/v1/quotes/preview rend le PDF d'un devis en cours de saisie sans rien persister ni consommer de numéro. Pour un aperçu HTML A4 live (branding + modèle), utilisez POST /api/v1/documents/preview avec 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.Avoirs (notes de crédit)
Un avoir cible TOUJOURS une facture existante. Total (annulation complète) ou partiel : un avoir partiel sélectionne des lignes de la facture source (le prix unitaire et le taux de TVA EXACT de chaque ligne sont hérités — une facture peut mêler 20 % / 5,5 % / exonéré 0 %). Après émission, l'avoir est immuable (chaîne ISCA).
# 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'Acheteurs, fournisseurs & catalogue
Registres scopés strictement par (tenant, sub_tenant) — anti-IDOR. Idempotents sur le SIRET (B2B) ou l'email (B2C). Une fois créé, réutilisez un acheteur via "buyer_id" sur vos factures ; ses données sont alors figées sur la facture émise (immutabilité fiscale).
Les fournisseurs sont dérivés automatiquement des factures reçues (POST /incoming-invoices) — aucune création manuelle. Seuls email, phone, notes et metadata sont modifiables via PATCH /api/v1/suppliers/:id. Les champs d'identité (name, SIRET, adresse, etc.) proviennent de la facture source et sont en lecture seule.
Le catalogue produits/services (GET/POST /api/v1/products, GET/POST /api/v1/product-categories) permet de réutiliser des éléments de ligne : une ligne de devis, facture ou avoir peut être pré-remplie via "product_id", et la case "save_to_catalog" enregistre une ligne saisie au catalogue (upsert atomique côté serveur, dédup SKU puis nom). Le lien "product_id" sur la ligne reste souple — le prix/description émis restent figés (immutabilité ISCA).
# 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.Catalogue produits / services
Un produit porte un prix HT, un taux de TVA et une remise par défaut, une unité (code UN/ECE), une catégorie fiscale (revenue_category : goods / service / accommodation) et, optionnellement, une catégorie d'organisation personnalisée. Sur une ligne de facture, devis ou avoir : "product_id" pré-remplit la ligne ; "save_to_catalog": true enregistre une ligne saisie au 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 & aperçus live
Le branding e-mail (GET/PATCH /api/v1/branding/tenant, variante /branding/sub-tenants/:id) porte le logo, la couleur primaire, le pied de page et la signature de vos e-mails transactionnels. Le champ "brand_email_enabled" active ou coupe la personnalisation (false = branding par défaut du canal) ; "computed_email_footer" (lecture seule) expose le pied de page calculé depuis votre société, utilisé au rendu quand "brand_email_footer" est vide. Le logo s'envoie soit en presigned S3 (POST …/logo-upload-url), soit directement en multipart (POST …/logo, champ "logo", jpeg/png/webp/svg, max 2 Mo — les SVG sont normalisés automatiquement).
Côté factures, les modèles (/api/v1/invoice-templates) gagnent le champ "is_enabled" (false = le modèle est ignoré par la cascade de résolution, repli sur le modèle système, sans perdre sa configuration) et l'endpoint POST /invoice-templates/derive-colors-from-email-logo qui extrait les couleurs dominantes de votre logo e-mail et les applique au modèle par défaut (404 si aucun logo, 422 si les teintes sont trop neutres).
Enfin, deux aperçus HTML live : GET /branding/tenant/preview accepte des overrides en query (brand_primary_color, brand_email_footer, brand_email_signature, brand_logo_url) pour tester un réglage avant de l'enregistrer, et POST /api/v1/documents/preview rend un document en cours de saisie (facture, avoir ou devis) avec le vrai modèle, le branding et les mentions légales de la société émettrice — rien n'est persisté (Cache-Control: no-store). C'est cet endpoint qui alimente l'aperçu A4 temps réel des écrans de création du dashboard.
# 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 }]
}'Crédits & facturation Scell.io
Scell.io fonctionne en prépayé : vous consommez des crédits à chaque émission. Suivez la consommation et le solde, rechargez (Stripe) ou achetez des packs. Vos relevés de consommation et factures Scell.io sont disponibles via l'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'Gestion des erreurs
| HTTP | Signification |
|---|---|
| 200 / 201 | Succès |
| 401 | Non authentifié (TENANT_NOT_RESOLVED si clé sans tenant) |
| 402 | Solde insuffisant (crédits) |
| 403 | KYB/KYC requis, sub-tenant non prêt (production) |
| 404 | Introuvable / hors scope (SUB_TENANT_NOT_FOUND, anti-IDOR) |
| 409 | Conflit métier (VAT_CORRECTION_REQUIRED, QUOTE_NOT_EDITABLE) |
| 422 | Validation (détail par champ) ou règle fiscale (NO_ISSUER_COMPANY, SUB_TENANT_HAS_FISCAL_ENTRIES) |
| 429 | Rate limit dépassé (header Retry-After) |
| 5xx | Erreur serveur |
// 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
Trois SDK officiels (v3.5.0) exposent toute l'API avec un typage strict, des builders (lignes de facture, devis) et des exceptions métier typées (dont VAT_CORRECTION_REQUIRED). Le serveur MCP donne aux agents IA un accès à 148 outils (tous préfixés scell_). Détails et exemples sur la page SDK.
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 outils
npx @scell/mcp-clientDémarrage rapide
pk_test_* (obtenue dans votre dashboard) pour tester le composant en mode sandbox. Les données de test ne sont pas persistées.Le composant d'onboarding Scell.io gère l'intégralité du flux de vérification d'entreprise : recherche SIREN/SIRET, validation du numéro de TVA, vérification d'adresse et confirmation du représentant légal.
Étape 1 : Inclure le script
<script src="https://cdn.scell.io/widget/v1/onboarding.js"></script>Étape 2 : Ajouter le composant
<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>Étape 3 : Gérer les événements
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);
});Démo interactive
Cette démo utilise une clé publishable de test. Créez votre compte pour obtenir votre propre clé pk_test_* et tester le widget avec vos données.
Testez le composant Scell.io Onboarding directement ci-dessous. Ce widget permet à vos utilisateurs de vérifier leur entreprise (SIREN, TVA, représentant légal) et de compléter leur inscription.
Ce widget fonctionne en mode sandbox. Pour l'intégrer, consultez le guide Quick Start ci-dessus.
Référence API
Attributs
Configurez le comportement du composant via des attributs HTML.
| Attribut | Type | Requis | Description |
|---|---|---|---|
publishable-key | string | Oui | Votre clé publishable (pk_live_xxx ou pk_test_xxx) |
external-id | string | Non | Votre identifiant interne pour ce sub-tenant (lien avec votre système) |
callback-url | string | Non | URL de redirection plein écran après onboarding (utilisée si le widget n'est PAS ouvert dans une popup, ex. embed iframe sans opener). Reçoit ?onboarding=success&sub_tenant_id=…&external_id=… |
theme | "light" | "dark" | "auto" | Non | Thème de couleur. Par défaut : "light" |
locale | "fr" | "en" | Non | Langue du widget. Par défaut : "fr" |
width | string | Non | Largeur du widget. Par défaut : "100%" |
height | string | Non | Hauteur du widget. Par défaut : "600px" |
white-label | boolean (attribut HTML) | Non | Mode marque blanche : masque le header avec le logo Scell.io. Le tracker de progression et le contenu métier restent visibles. Suffisant en attribut nu (<scell-onboarding white-label>) ou avec valeur "true" / "1" / "on" / "yes". Par défaut : header Scell.io affiché. |
Mode marque blanche
L'attribut white-label masque le header avec le logo Scell.io. Le tracker de progression et tout le contenu métier (formulaire SIRET, redirect SUPER PDP, écran de fin) restent visibles. Utile pour intégrer le widget sans aucune mention Scell.io visible côté signataire/utilisateur final.
Valeurs acceptées : attribut nu (<scell-onboarding white-label>), ou "true" / "1" / "on" / "yes". Tout autre valeur (incluant "false" et l'absence de l'attribut) garde le 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>Événements
Écoutez les événements du composant pour réagir aux actions de l'utilisateur et aux changements d'état.
| Événement | Payload | Description |
|---|---|---|
onboarding:started | { sessionId: string } | Une session d'onboarding a été créée côté Scell.io. Aucun appel SUPER PDP encore émis. |
onboarding:step | { step: "connect" | "redirect" | "complete", progress: number } | L'utilisateur a changé d'étape (0% → 50% → 100%). |
onboarding:completed | { subTenant: SubTenantSummary, credentials: OnboardingCredentials, externalId?: string } | Le sub-tenant Scell.io est créé et opérationnel pour la facturation. Les tokens OAuth 2.1 SUPER PDP sont chiffrés en base et utilisés automatiquement par le backend pour transmettre les factures. |
onboarding:error | { code: string, message: string } | Une erreur a eu lieu (popup fermé, state CSRF invalide, échec KYB SUPER PDP, etc.). |
Comment ça marche
Le widget <scell-onboarding> est l'unique méthode d'intégration officielle. Il pilote un flux OAuth 2.1 + PKCE qui crée simultanément un sub-tenant Scell.io et l'enregistrement chez SUPER PDP, dans la même session utilisateur.
- 1Le widget appelle POST /onboarding/sessions avec votre publishable key (pk_*) pour créer une session liée à votre tenant parent.
- 2Le widget appelle POST /onboarding/superpdp/authorize : le backend Scell.io génère le PKCE (code_verifier/code_challenge S256), un state CSRF, et renvoie l'URL d'autorisation SUPER PDP.
- 3Le widget ouvre un popup vers cette URL. L'utilisateur final s'authentifie chez SUPER PDP et complète le KYB (SIREN, TVA, représentant légal).
- 4SUPER PDP redirige vers POST /onboarding/superpdp/callback avec un code et le state.
- 5Le backend Scell.io échange code+verifier contre des tokens (access + refresh), récupère les infos entreprise, puis crée un sub-tenant rattaché à votre tenant parent (avec ses propres clés sk_/pk_, son numéroteur de factures, sa balance).
- 6Le widget reçoit l'event scell:onboarding:complete avec le sub_tenant_id et les credentials initiales.
Pourquoi un seul mode ?
Depuis le 5 mai 2026, les modes 'Redirect Flow' et 'API Only' sont retirés. Le widget couvre l'intégralité des cas d'usage tout en garantissant la conformité OAuth 2.1 + PKCE et l'isolation parent/sub-tenant. Le code OAuth, la rotation des refresh tokens et le KYB SUPER PDP sont gérés côté backend Scell.io — vous n'avez qu'une ligne de code à intégrer.
Widget : reconnexion SUPER PDP
Variante avancée du widget. Le mode mode="superpdp" expose uniquement l'étape SUPER PDP pour un sous-tenant déjà créé et validé. Cas d'usage : reconnecter — ou forcer la reconnexion — d'un sous-tenant existant, sans repasser par l'identité, le lookup Sirene ni la création. Requiert le widget v3.2.0+ (CDN https://cdn.scell.io/widget/v1/onboarding.js).
2 étapes : (1) côté serveur, votre backend émet un jeton signé scopé à un sous-tenant à l'aide de votre clé sk_* ; (2) côté client, vous embarquez le widget avec ce jeton. Le jeton de reprise est une URL signée HMAC liée au sous-tenant ciblé — une clé publishable pk_* seule ne permet PAS de viser un sous-tenant arbitraire (anti-IDOR).
Étape 1 — Émettre le jeton (côté serveur)
Appelez POST /tenant/sub-tenants/{id}/superpdp-widget-token avec votre clé sk_* (jamais exposée au navigateur). Le body optionnel { "reset": true } déconnecte (révoque + reset) le sous-tenant avant d'émettre, pour forcer une reconnexion propre. La réponse renvoie un resume_token (URL signée, TTL 24h).
# 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.Étape 2 — Embarquer le widget (côté client)
Passez le resume_token récupéré à l'étape 1 dans l'attribut resume-token. Le widget ouvre directement la popup OAuth SUPER PDP (étape unique) et émet les mêmes événements que le flux complet (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>| Attribut | Requis | Description |
|---|---|---|
mode | Oui | Doit valoir "superpdp" pour n'exposer que l'étape SUPER PDP. |
resume-token | Oui | URL signée émise à l'étape 1 (POST .../superpdp-widget-token). Scopée au sous-tenant ciblé, TTL 24h. |
Sandbox vs Production
Scell.io fournit deux environnements totalement isolés. Le mode est déterminé automatiquement par le préfixe de la clé publishable que vous fournissez au widget — aucun paramètre supplémentaire à configurer.
| Critère | Sandbox | Production |
|---|---|---|
| Préfixe clé publishable | pk_test_* | pk_live_* |
| Préfixe clé secrète | sk_test_* | sk_live_* |
| Base de données | Isolée. Données de test, pas de valeur fiscale. | Données réelles, conservées 10 ans (autocertification ISCA). |
| SUPER PDP | Compte sandbox SUPER PDP. Les factures ne sont PAS transmises au réseau Peppol/PPF réel. | Compte SUPER PDP production. Transmission réelle au réseau électronique français (PPF). |
| KYB SUPER PDP | KYB simplifié — n'importe quel SIREN test fonctionne. Aucune vérification réelle. | KYB complet (SIREN, TVA, représentant légal vérifiés par SUPER PDP). |
| Sub-tenants | Créés en base sandbox, jamais visibles depuis vos clés live. | Créés en base production, immédiatement opérationnels pour facturer. |
| Tarification | Gratuit, illimité. | Voir page Tarifs. |
Comment basculer ?
Il suffit de remplacer la valeur de l'attribut publishable-key. Le widget, le backend Scell.io et le compte SUPER PDP utilisé sont basculés automatiquement. Aucun changement de code, d'URL d'API ou de configuration n'est nécessaire.
Exemple — passer de test à live
-<scell-onboarding publishable-key="pk_test_xxxxxxxx" />
+<scell-onboarding publishable-key="pk_live_xxxxxxxx" />Bonnes pratiques
- Tester en sandbox jusqu'à validation complète du parcours utilisateur AVANT de basculer en live.
- Stocker les clés live en variable d'environnement serveur — JAMAIS dans le navigateur.
- Les clés pk_test_* sont publiques par design (front-end OK). Les clés sk_test_*/sk_live_* sont secrètes (back-end uniquement).
- Une facture émise en sandbox n'a aucune valeur juridique ni fiscale.
Exemples par framework
Exemples prêts à copier pour les frameworks populaires.
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" /> );}URIs OAuth 2.1 enregistrées chez SUPER PDP
Information de référence : Scell.io a déclaré 3 redirect_uris officielles côté SUPER PDP. Vous n'avez RIEN à configurer pour intégrer le widget — ces URIs sont utilisées par les flows internes Scell.
| URL | Usage |
|---|---|
/api/v1/widget/oauth-callback | Callback du widget <scell-onboarding> embed sur votre site. Crée le sub-tenant + retourne sub_tenant + credentials au widget via postMessage. |
/api/v1/onboarding/superpdp/callback | Endpoint legacy POST exposé pour les intégrations widget v1 (rétrocompat). Même contrat de réponse que le widget v2. |
/api/v1/me/superpdp/callback | Onboarding self-service depuis le dashboard Scell.io (admin tenant connecté). Pas concerné par l'intégration widget. |
Factures récurrentes
Automatisez l'émission de factures sur une cadence (abonnements, loyers, contrats de maintenance). Vous définissez un profil récurrent une seule fois ; Scell.io émet ensuite chaque facture à l'échéance prévue, sans intervention.
Concepts clés
- Template éditable, factures figées. Le profil récurrent est un modèle modifiable. À chaque échéance, Scell.io fige l'état COURANT du profil dans une vraie facture, avec sa propre chaîne ISCA. Modifier le profil n'impacte que les émissions FUTURES — jamais les factures déjà émises.
auto_sendsoumet la facture au PDP et l'envoie par email au buyer avec le PDF Factur-X.draftcrée un brouillon pour relecture (aucun envoi).- Notifications bilatérales + rappel J-N. Émetteur et destinataire sont notifiés à chaque cycle ; un rappel est envoyé
notify_before_daysjours avant l'échéance. - Jamais de saut silencieux. Un cycle en échec produit une occurrence
failedvisible (avec son erreur) ; il n'est jamais ignoré sans trace.
Endpoints
Base : /api/v1. Authentification : X-API-Key: sk_* (server-side uniquement).
| Méthode & chemin | Description |
|---|---|
GET /recurring-invoices | Lister (filtres : status, sub_tenant_id, per_page) |
POST /recurring-invoices | Créer un profil (201) |
GET /recurring-invoices/{id} | Obtenir un profil |
PUT /recurring-invoices/{id} | Mettre à jour (n'affecte que les émissions futures) |
DELETE /recurring-invoices/{id} | Supprimer le profil (factures émises inchangées) |
GET /recurring-invoices/{id}/occurrences | Historique des occurrences |
POST /recurring-invoices/{id}/pause | Mettre en pause |
POST /recurring-invoices/{id}/activate | Réactiver |
POST /recurring-invoices/{id}/cancel | Annuler (terminal) |
POST /recurring-invoices/{id}/run-now | Émettre la prochaine occurrence maintenant (202) |
Énumérations
| Champ | Valeurs |
|---|---|
recurrence.interval_unit | day | week | month | year |
end_mode | never | on_date | after_occurrences |
emission_mode | draft | auto_send (défaut auto_send) |
status | active | paused | completed | cancelled |
statut d'occurrence | pending | emitted | failed | skipped |
Schéma du payload de création
title, lines, recurrence et start_date sont requis. L'acheteur se définit via buyer_id (registre) OU les champs buyer_* à plat. day_of_month est clampé à la longueur du mois (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}Exemples
# 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
Recevez des notifications en temps réel lorsque des événements d'onboarding se produisent. Configurez votre URL webhook depuis le tableau de bord.
x-scell-signature.Payload reçu côté widget (event onboarding:completed)
Ce payload est livré simultanément via window.opener.postMessage(...) (au parent qui héberge le widget) et comme event.detail de l'event onboarding:completed sur l'élément <scell-onboarding>.
// 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" }}Émettre une facture immédiatement après onboarding
Dès que l'event onboarding:completed est reçu, le sub-tenant est opérationnel : tokens OAuth 2.1 SUPER PDP chiffrés en base, KYB validé, numéroteur de factures isolé. Aucune attente.
// 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();Événements webhook disponibles
| Événement | Description |
|---|---|
sub_tenant.onboarded | Un sub-tenant Scell.io vient d'être créé via le widget. Le payload contient sub_tenant_id, external_id, siret, onboarding_status. C'est le moment de persister le mapping côté votre base et d'activer la facturation pour ce client. |
invoice.transmitted | Une facture émise pour ce sub-tenant a été transmise avec succès à SUPER PDP. Inclut superpdp_id et timestamp. |
invoice.accepted | La facture a été acceptée par le destinataire (lifecycle Factur-X). |
invoice.rejected | La facture a été rejetée par le destinataire ou par SUPER PDP. Le payload inclut rejection_reason. |
invoice.incoming.received | Une facture entrante destinée à ce sub-tenant vient d'arriver via SUPER PDP. Scell.io la résout automatiquement par superpdp_company_id et la rend disponible dans /tenant/incoming-invoices. |
subtenant.threshold.warning | Un sub-tenant auto-entrepreneur atteint 80 % ou 90 % d'un seuil (franchise TVA ou plafond micro). Payload : sub_tenant_id, category, kind, percent, projected_crossing_date. |
subtenant.threshold.vat_base_exceeded | Le seuil de base de la franchise en base de TVA est dépassé : la TVA deviendra exigible au 1er janvier de l'année suivante. C'est au sous-tenant d'agir (immatriculation TVA URSSAF/INPI). |
subtenant.threshold.vat_majored_exceeded | Le seuil majoré de la franchise en base de TVA est dépassé : la TVA est exigible immédiatement, rétroactivement au 1er du mois de dépassement. |
subtenant.threshold.micro_exceeded | Le plafond du régime micro-entreprise est dépassé : sortie possible du régime après 2 années civiles consécutives de dépassement. |
Prêt à intégrer ?
Créez votre compte et obtenez vos clés API en quelques minutes. Démarrez avec 100 crédits gratuits.