Integrer l'API Scell.io
Ce guide vous accompagne pour integrer l'API Scell.io dans votre application. De la premiere connexion a un systeme complet avec webhooks, vous aurez toutes les cles pour automatiser la facturation electronique et la signature de documents.
Ce tutorial est concu pour les developpeurs. Chaque etape inclut des exemples en curl, JavaScript, PHP et Python.
Ce que vous allez apprendre
- Les deux methodes d'authentification et quand les utiliser
- Faire votre premier appel API et verifier que tout fonctionne
- Creer des factures et des signatures via l'API
- Recevoir des notifications en temps reel avec les webhooks
- Gerer les erreurs correctement
- Utiliser le sandbox pour tester sans frais
- Les bonnes pratiques pour une integration solide
Prerequis
- Un compte Scell.io actif avec une entreprise configuree (SIRET verifie)
- Une cle API generee depuis le dashboard
- Un environnement de developpement avec au choix : curl, Node.js, PHP ou Python
Etape 1 : Authentification
L'API Scell.io propose deux methodes d'authentification. Chacune a son usage.
Option A : Token Bearer (pour le dashboard et les applications web)
Le token Bearer est obtenu en se connectant avec email et mot de passe. Il est ideal pour les applications qui agissent au nom d'un utilisateur connecte.
Obtenir un token :
curl -X POST https://api.scell.io/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "votre@email.com",
"password": "votre_mot_de_passe"
}'Reponse :
{
"message": "Connexion reussie",
"token": "1|abc123def456ghi789...",
"user": {
"id": "550e8400-...",
"name": "Jean Dupont",
"email": "votre@email.com"
}
}Utiliser le token :
curl -X GET https://api.scell.io/api/v1/invoices \
-H "Authorization: Bearer 1|abc123def456ghi789..."Option B : Cle API (pour les integrations serveur-a-serveur)
La cle API est la methode recommandee pour les integrations automatisees. Elle est liee a une entreprise et un environnement (sandbox ou production).
Utiliser la cle API :
curl -X GET https://api.scell.io/api/v1/invoices \
-H "X-API-Key: sk_live_VOTRE_CLE_API"Quelle methode choisir ?
| Critere | Token Bearer | Cle API |
|---|---|---|
| Usage | Dashboard, applications web | Integrations back-end, scripts, cron jobs |
| Duree de vie | Expire a la deconnexion | Permanent (sauf revocation) |
| Creation | Via login email/mot de passe | Via le dashboard (section Cles API) |
| Header HTTP | Authorization: Bearer {token} | X-API-Key: {cle} |
| Actions possibles | Toutes (lecture + ecriture) | Lecture + creation factures/signatures |
Astuce : Pour une integration en production, utilisez toujours une cle API. Le token Bearer est pratique pour le developpement et le dashboard, mais il n'est pas concu pour les appels automatises.
Etape 2 : Premier appel API
Avant de creer quoi que ce soit, verifions que votre connexion fonctionne en consultant votre solde :
curl
curl -X GET https://api.scell.io/api/v1/balance \
-H "Authorization: Bearer VOTRE_TOKEN"JavaScript (fetch)
const API_URL = 'https://api.scell.io/api/v1';
const API_KEY = process.env.SCELL_API_KEY; // sk_live_... (aussi appele SCELL_TENANT_KEY — c'est la meme cle sk_*)
async function getBalance() {
const response = await fetch(`${API_URL}/balance`, {
headers: {
'X-API-Key': API_KEY,
},
});
if (!response.ok) {
throw new Error(`Erreur HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
console.log(`Solde actuel : ${data.data.amount} ${data.data.currency}`);
return data;
}
getBalance();PHP (Guzzle)
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://api.scell.io/api/v1/',
'headers' => [
'X-API-Key' => getenv('SCELL_API_KEY'),
'Accept' => 'application/json',
],
]);
try {
$response = $client->get('balance');
$data = json_decode($response->getBody(), true);
echo "Solde actuel : {$data['data']['amount']} {$data['data']['currency']}\n";
} catch (\GuzzleHttp\Exception\ClientException $e) {
echo "Erreur : " . $e->getResponse()->getBody() . "\n";
}Python (requests)
import os
import requests
API_URL = 'https://api.scell.io/api/v1'
API_KEY = os.environ['SCELL_API_KEY']
response = requests.get(
f'{API_URL}/balance',
headers={'X-API-Key': API_KEY}
)
if response.ok:
data = response.json()
print(f"Solde actuel : {data['data']['amount']} {data['data']['currency']}")
else:
print(f"Erreur {response.status_code}: {response.text}")Reponse attendue :
{
"data": {
"amount": 150.00,
"currency": "EUR",
"auto_reload_enabled": false,
"auto_reload_threshold": null,
"auto_reload_amount": null,
"low_balance_alert_threshold": 10.00,
"critical_balance_alert_threshold": 2.00
}
}Si vous voyez votre solde, tout est en ordre. Passons a la creation de factures.
Etape 3 : Creer une facture
Voici un exemple complet de creation de facture avec tous les champs documentes.
curl
curl -X POST https://api.scell.io/api/v1/invoices \
-H "X-API-Key: sk_live_VOTRE_CLE_API" \
-H "Content-Type: application/json" \
-d '{
"invoice_number": "FACT-2026-001",
"direction": "outgoing",
"output_format": "facturx",
"issue_date": "2026-03-03",
"due_date": "2026-04-02",
"currency": "EUR",
"total_ht": 1500.00,
"total_tax": 300.00,
"total_ttc": 1800.00,
"seller_siret": "12345678901234",
"seller_name": "Ma Societe SAS",
"seller_address": {
"line1": "10 rue de la Paix",
"postal_code": "75002",
"city": "Paris",
"country": "FR"
},
"buyer_siret": "98765432109876",
"buyer_name": "Client SARL",
"buyer_address": {
"line1": "25 avenue des Champs-Elysees",
"postal_code": "75008",
"city": "Paris",
"country": "FR"
},
"lines": [
{
"description": "Developpement site web - Phase 1",
"quantity": 5,
"unit_price": 300.00,
"tax_rate": 20.00,
"total_ht": 1500.00,
"total_tax": 300.00,
"total_ttc": 1800.00
}
]
}'JavaScript (fetch)
async function createInvoice() {
const response = await fetch(`${API_URL}/invoices`, {
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
invoice_number: 'FACT-2026-001',
direction: 'outgoing',
output_format: 'facturx',
issue_date: '2026-03-03',
due_date: '2026-04-02',
currency: 'EUR',
total_ht: 1500.00,
total_tax: 300.00,
total_ttc: 1800.00,
seller_siret: '12345678901234',
seller_name: 'Ma Societe SAS',
seller_address: {
line1: '10 rue de la Paix',
postal_code: '75002',
city: 'Paris',
country: 'FR',
},
buyer_siret: '98765432109876',
buyer_name: 'Client SARL',
buyer_address: {
line1: '25 avenue des Champs-Elysees',
postal_code: '75008',
city: 'Paris',
country: 'FR',
},
lines: [
{
description: 'Developpement site web - Phase 1',
quantity: 5,
unit_price: 300.00,
tax_rate: 20.00,
total_ht: 1500.00,
total_tax: 300.00,
total_ttc: 1800.00,
},
],
}),
});
if (response.status === 201) {
const data = await response.json();
console.log('Facture creee :', data.data.id);
return data;
} else {
const error = await response.json();
console.error('Erreur :', error);
throw new Error(error.message);
}
}PHP (Guzzle)
<?php
try {
$response = $client->post('invoices', [
'json' => [
'invoice_number' => 'FACT-2026-001',
'direction' => 'outgoing',
'output_format' => 'facturx',
'issue_date' => '2026-03-03',
'due_date' => '2026-04-02',
'currency' => 'EUR',
'total_ht' => 1500.00,
'total_tax' => 300.00,
'total_ttc' => 1800.00,
'seller_siret' => '12345678901234',
'seller_name' => 'Ma Societe SAS',
'seller_address' => [
'line1' => '10 rue de la Paix',
'postal_code' => '75002',
'city' => 'Paris',
'country' => 'FR',
],
'buyer_siret' => '98765432109876',
'buyer_name' => 'Client SARL',
'buyer_address' => [
'line1' => '25 avenue des Champs-Elysees',
'postal_code' => '75008',
'city' => 'Paris',
'country' => 'FR',
],
'lines' => [
[
'description' => 'Developpement site web - Phase 1',
'quantity' => 5,
'unit_price' => 300.00,
'tax_rate' => 20.00,
'total_ht' => 1500.00,
'total_tax' => 300.00,
'total_ttc' => 1800.00,
],
],
],
]);
$data = json_decode($response->getBody(), true);
echo "Facture creee : {$data['data']['id']}\n";
} catch (\GuzzleHttp\Exception\ClientException $e) {
$error = json_decode($e->getResponse()->getBody(), true);
echo "Erreur de validation :\n";
foreach ($error['errors'] ?? [] as $field => $messages) {
echo " - {$field}: " . implode(', ', $messages) . "\n";
}
}Python (requests)
invoice_data = {
'invoice_number': 'FACT-2026-001',
'direction': 'outgoing',
'output_format': 'facturx',
'issue_date': '2026-03-03',
'due_date': '2026-04-02',
'currency': 'EUR',
'total_ht': 1500.00,
'total_tax': 300.00,
'total_ttc': 1800.00,
'seller_siret': '12345678901234',
'seller_name': 'Ma Societe SAS',
'seller_address': {
'line1': '10 rue de la Paix',
'postal_code': '75002',
'city': 'Paris',
'country': 'FR',
},
'buyer_siret': '98765432109876',
'buyer_name': 'Client SARL',
'buyer_address': {
'line1': '25 avenue des Champs-Elysees',
'postal_code': '75008',
'city': 'Paris',
'country': 'FR',
},
'lines': [
{
'description': 'Developpement site web - Phase 1',
'quantity': 5,
'unit_price': 300.00,
'tax_rate': 20.00,
'total_ht': 1500.00,
'total_tax': 300.00,
'total_ttc': 1800.00,
},
],
}
response = requests.post(
f'{API_URL}/invoices',
headers={
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
json=invoice_data,
)
if response.status_code == 201:
data = response.json()
print(f"Facture creee : {data['data']['id']}")
else:
print(f"Erreur {response.status_code}:")
errors = response.json().get('errors', {})
for field, messages in errors.items():
print(f" - {field}: {', '.join(messages)}")Description des champs
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
invoice_number | string | Oui | Votre numero de facture interne |
direction | string | Oui | outgoing (vente) ou incoming (achat) |
output_format | string | Oui | facturx, ubl ou cii |
issue_date | date | Oui | Date d'emission (format YYYY-MM-DD) |
due_date | date | Non | Date d'echeance |
currency | string | Non | Devise ISO 4217 (defaut: EUR) |
total_ht | number | Oui | Montant total hors taxes |
total_tax | number | Oui | Montant total de la TVA |
total_ttc | number | Oui | Montant total TTC |
seller_siret | string | Oui | SIRET du vendeur (14 chiffres) |
seller_name | string | Oui | Raison sociale du vendeur |
seller_address | object | Oui | Adresse complete du vendeur |
buyer_siret | string | Oui | SIRET de l'acheteur (14 chiffres) |
buyer_name | string | Oui | Raison sociale de l'acheteur |
buyer_address | object | Oui | Adresse complete de l'acheteur |
lines | array | Oui | Lignes de facture (au moins 1) |
external_id | string | Non | Votre identifiant interne pour retrouver la facture |
archive_enabled | boolean | Non | Activer l'archivage longue duree |
Chaque ligne de facture contient :
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
description | string | Oui | Description de la prestation ou du produit |
quantity | number | Oui | Quantite |
unit_price | number | Oui | Prix unitaire HT |
tax_rate | number | Oui | Taux de TVA en pourcentage (ex: 20.00) |
total_ht | number | Oui | Total HT de la ligne |
total_tax | number | Oui | Total TVA de la ligne |
total_ttc | number | Oui | Total TTC de la ligne |
Etape 4 : Configurer les Webhooks
Les webhooks vous permettent de recevoir des notifications en temps reel quand un evenement se produit sur vos factures ou signatures. Au lieu d'interroger l'API toutes les minutes pour verifier les statuts, c'est l'API qui vous previent.
Pourquoi utiliser les webhooks ?
Sans webhook, vous devriez faire du "polling" :
Votre serveur Scell.io
| |
|--- GET /invoices/123 --------->| (rien de nouveau)
|<--- status: transmitted -------|
| |
| ... 1 minute plus tard ... |
|--- GET /invoices/123 --------->| (toujours rien)
|<--- status: transmitted -------|
| |
| ... 1 minute plus tard ... |
|--- GET /invoices/123 --------->| (changement !)
|<--- status: completed ---------|Avec un webhook, Scell.io vous previent immediatement :
Votre serveur Scell.io
| |
| | (statut change)
|<--- POST /votre-webhook -------| event: invoice.validated
|--- 200 OK ------------------->|Creer un webhook
curl -X POST https://api.scell.io/api/v1/webhooks \
-H "Authorization: Bearer VOTRE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://mon-site.com/webhooks/scell",
"events": [
"invoice.validated",
"invoice.rejected",
"signature.completed",
"signature.refused"
],
"environment": "production"
}'Reponse :
{
"message": "Webhook cree avec succes",
"data": {
"id": "wh_abc123...",
"url": "https://mon-site.com/webhooks/scell",
"events": ["invoice.validated", "invoice.rejected", "signature.completed", "signature.refused"],
"is_active": true,
"environment": "production",
"secret": "whsec_abc123def456ghi789..."
}
}Attention : Conservez le
secreten lieu sur. Il ne sera plus affiche. Vous en aurez besoin pour verifier l'authenticite des webhooks recus.
Verifier la signature des webhooks
Chaque webhook envoye par Scell.io est signe avec votre secret. Verifiez toujours cette signature pour vous assurer que la requete vient bien de Scell.io et n'a pas ete alteree.
Le header X-Scell-Signature contient le timestamp et la signature HMAC :
X-Scell-Signature: t=1709467800,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdExemple de handler en Node.js (Express)
const express = require('express');
const crypto = require('crypto');
const app = express();
const WEBHOOK_SECRET = process.env.SCELL_WEBHOOK_SECRET;
// Middleware pour parser le body brut (necessaire pour la verification de signature)
app.post('/webhooks/scell', express.raw({ type: 'application/json' }), (req, res) => {
// 1. Extraire le timestamp et la signature du header
const signatureHeader = req.headers['x-scell-signature'];
if (!signatureHeader) {
return res.status(401).send('Signature manquante');
}
const parts = Object.fromEntries(
signatureHeader.split(',').map(part => part.split('='))
);
const timestamp = parts.t;
const receivedSignature = parts.v1;
// 2. Verifier que le timestamp n'est pas trop ancien (protection replay)
const fiveMinutesAgo = Math.floor(Date.now() / 1000) - 300;
if (parseInt(timestamp) < fiveMinutesAgo) {
return res.status(401).send('Timestamp trop ancien');
}
// 3. Calculer la signature attendue
const payload = `${timestamp}.${req.body.toString()}`;
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');
// 4. Comparer les signatures
if (!crypto.timingSafeEqual(
Buffer.from(receivedSignature),
Buffer.from(expectedSignature)
)) {
return res.status(401).send('Signature invalide');
}
// 5. Traiter l'evenement
const event = JSON.parse(req.body.toString());
const eventType = req.headers['x-scell-event'];
switch (eventType) {
case 'invoice.validated':
console.log(`Facture ${event.data.id} validee`);
// Mettre a jour votre base de donnees...
break;
case 'signature.completed':
console.log(`Signature ${event.data.id} terminee`);
// Telecharger le document signe...
break;
case 'invoice.rejected':
console.log(`Facture ${event.data.id} rejetee : ${event.data.status_message}`);
// Alerter l'utilisateur...
break;
default:
console.log(`Evenement non gere : ${eventType}`);
}
// 6. Repondre 200 rapidement
res.status(200).send('OK');
});
app.listen(3000, () => console.log('Webhook listener demarre sur le port 3000'));Exemple de handler en PHP (Laravel)
<?php
// routes/web.php
Route::post('/webhooks/scell', [WebhookReceiverController::class, 'handle']);
// app/Http/Controllers/WebhookReceiverController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class WebhookReceiverController extends Controller
{
public function handle(Request $request)
{
// 1. Extraire la signature
$signatureHeader = $request->header('X-Scell-Signature');
if (!$signatureHeader) {
return response('Signature manquante', 401);
}
// 2. Parser le timestamp et la signature
$parts = [];
foreach (explode(',', $signatureHeader) as $part) {
[$key, $value] = explode('=', $part, 2);
$parts[$key] = $value;
}
$timestamp = $parts['t'] ?? null;
$receivedSignature = $parts['v1'] ?? null;
// 3. Verifier le timestamp (5 minutes max)
if (abs(time() - (int) $timestamp) > 300) {
return response('Timestamp trop ancien', 401);
}
// 4. Verifier la signature
$payload = $timestamp . '.' . $request->getContent();
$expectedSignature = hash_hmac('sha256', $payload, config('services.scell.webhook_secret'));
if (!hash_equals($expectedSignature, $receivedSignature)) {
return response('Signature invalide', 401);
}
// 5. Traiter l'evenement
$eventType = $request->header('X-Scell-Event');
$data = $request->all();
match ($eventType) {
'invoice.validated' => $this->handleInvoiceValidated($data),
'signature.completed' => $this->handleSignatureCompleted($data),
'invoice.rejected' => $this->handleInvoiceRejected($data),
default => Log::info("Evenement webhook non gere : {$eventType}"),
};
return response('OK', 200);
}
private function handleInvoiceValidated(array $data): void
{
Log::info("Facture validee : {$data['data']['id']}");
// Mettre a jour votre base de donnees...
}
private function handleSignatureCompleted(array $data): void
{
Log::info("Signature terminee : {$data['data']['id']}");
// Telecharger le document signe...
}
private function handleInvoiceRejected(array $data): void
{
Log::warning("Facture rejetee : {$data['data']['id']}");
// Alerter l'utilisateur...
}
}Tester votre webhook
Une fois votre endpoint configure, testez-le depuis le dashboard ou via l'API :
curl -X POST https://api.scell.io/api/v1/webhooks/wh_abc123.../test \
-H "Authorization: Bearer VOTRE_TOKEN"Reponse :
{
"success": true,
"status_code": 200,
"response_time_ms": 142
}Consulter les logs de webhook
Si un webhook echoue, consultez les logs pour comprendre pourquoi :
curl -X GET https://api.scell.io/api/v1/webhooks/wh_abc123.../logs \
-H "Authorization: Bearer VOTRE_TOKEN"Etape 5 : Gerer les erreurs
Codes HTTP standards
L'API Scell.io utilise les codes HTTP standards :
| Code | Signification | Action recommandee |
|---|---|---|
200 | Succes | Traiter la reponse |
201 | Creation reussie | Traiter la nouvelle ressource |
400 | Requete invalide | Corriger la requete |
401 | Non authentifie | Verifier votre cle API ou token |
403 | Non autorise | Verifier vos permissions |
404 | Ressource non trouvee | Verifier l'identifiant |
422 | Erreur de validation | Corriger les champs en erreur |
429 | Trop de requetes | Ralentir et reessayer |
500 | Erreur serveur | Reessayer apres quelques secondes |
Structure des erreurs JSON
Toutes les erreurs suivent le meme format :
{
"message": "The given data was invalid.",
"errors": {
"seller_siret": ["Le SIRET du vendeur est requis."],
"lines": ["Au moins une ligne de facture est requise."]
}
}Ou pour les erreurs non liees a la validation :
{
"error": "Solde insuffisant",
"code": "INSUFFICIENT_BALANCE"
}Strategie de retry recommandee
Pour les erreurs temporaires (codes 429 et 5xx), utilisez un backoff exponentiel :
async function callWithRetry(fn, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
const statusCode = error.response?.status;
// Ne pas reessayer les erreurs client (sauf 429)
if (statusCode >= 400 && statusCode < 500 && statusCode !== 429) {
throw error;
}
if (attempt === maxRetries) {
throw error;
}
// Backoff exponentiel : 1s, 2s, 4s
const delay = Math.pow(2, attempt) * 1000;
console.log(`Tentative ${attempt + 1} echouee, retry dans ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Utilisation
const invoice = await callWithRetry(() => createInvoice(data));import time
def call_with_retry(fn, max_retries=3):
for attempt in range(max_retries + 1):
try:
return fn()
except requests.exceptions.HTTPError as e:
status_code = e.response.status_code
# Ne pas reessayer les erreurs client (sauf 429)
if 400 <= status_code < 500 and status_code != 429:
raise
if attempt == max_retries:
raise
delay = (2 ** attempt)
print(f"Tentative {attempt + 1} echouee, retry dans {delay}s...")
time.sleep(delay)Etape 6 : Utiliser le Sandbox
Le sandbox est un environnement de test complet qui fonctionne exactement comme la production, mais sans debiter vos credits.
URLs
| Environnement | URL de base |
|---|---|
| Production | https://api.scell.io/api/v1/ |
| Sandbox | https://api.scell.io/api/v1/sandbox/ |
Cle API sandbox
Creez une cle API dediee au sandbox depuis le dashboard :
- Allez dans Cles API
- Cliquez sur Nouvelle cle
- Selectionnez l'environnement sandbox
- La cle generee sera de la forme
sk_test_...
Donnees de test
En sandbox, utilisez les donnees que vous voulez. Les SIRET ne sont pas verifies contre l'INSEE, les SMS OTP ne sont pas envoyes (le code est toujours 000000), et aucun credit n'est debite.
Basculer en production
Quand vos tests sont concluants :
- Creez une cle API production (
sk_live_...) - Rechargez votre solde (minimum 10 EUR)
- Changez l'URL de base et la cle API dans votre application
- Configurez les webhooks pour l'environnement production
// Configuration par environnement
const config = {
development: {
apiUrl: 'https://api.scell.io/api/v1/sandbox',
apiKey: process.env.SCELL_TEST_API_KEY,
},
production: {
apiUrl: 'https://api.scell.io/api/v1',
apiKey: process.env.SCELL_LIVE_API_KEY,
},
};
const env = process.env.NODE_ENV || 'development';
const { apiUrl, apiKey } = config[env];Attention : Ne melangez jamais les cles sandbox et production. Une cle sandbox ne fonctionnera pas sur les endpoints de production, et vice versa.
Bonnes Pratiques
1. Stocker la cle API en variable d'environnement
Ne codez jamais votre cle API en dur dans le code source :
# .env (ajoute au .gitignore !)
SCELL_API_KEY=sk_live_abc123... # Aussi appele SCELL_TENANT_KEY — c'est la meme cle (sk_*)
SCELL_WEBHOOK_SECRET=whsec_def456... # Unique par webhook (genere dans le dashboard)// Bien
const apiKey = process.env.SCELL_API_KEY;
// Mal -- ne faites JAMAIS ceci
const apiKey = 'sk_live_abc123...';2. Utiliser le champ external_id pour l'idempotence
Si votre application peut envoyer la meme requete deux fois (retry apres timeout, par exemple), utilisez le champ external_id pour eviter les doublons :
{
"external_id": "ma-facture-interne-42",
"invoice_number": "FACT-2026-042",
...
}Vous pouvez ensuite retrouver la facture par son external_id sans risque de creer un doublon.
3. Respecter le rate limiting
L'API impose des limites de requetes pour garantir la disponibilite pour tous. Si vous recevez un code 429 :
- Attendez le delai indique dans le header
Retry-After - Implementez un backoff exponentiel (voir la section Gerer les erreurs)
- Regroupez vos requetes quand c'est possible
4. Paginer les listes
Les endpoints de liste retournent des resultats pagines. Utilisez les parametres per_page et page :
# Page 1, 50 resultats par page
curl "https://api.scell.io/api/v1/invoices?per_page=50&page=1" \
-H "X-API-Key: sk_live_..."Reponse avec metadonnees de pagination :
{
"data": [...],
"meta": {
"current_page": 1,
"last_page": 5,
"per_page": 50,
"total": 237
}
}5. Surveiller votre solde
Configurez les alertes de solde bas pour ne jamais etre bloque :
curl -X PUT https://api.scell.io/api/v1/balance/settings \
-H "Authorization: Bearer VOTRE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"auto_reload_enabled": true,
"auto_reload_threshold": 10.00,
"auto_reload_amount": 100.00,
"low_balance_alert_threshold": 20.00,
"critical_balance_alert_threshold": 5.00
}'Avec cette configuration :
- Vous recevez une alerte quand votre solde passe sous 20 EUR
- Vous recevez une alerte critique quand il passe sous 5 EUR
- Votre solde est automatiquement recharge de 100 EUR quand il passe sous 10 EUR
Exemples Complets
Application Node.js
Un module complet reutilisable pour integrer Scell.io dans une application Node.js :
// scell-client.js
const crypto = require('crypto');
class ScellClient {
constructor({ apiKey, environment = 'production' }) {
this.apiKey = apiKey;
this.baseUrl = environment === 'sandbox'
? 'https://api.scell.io/api/v1/sandbox'
: 'https://api.scell.io/api/v1';
}
async request(method, path, body = null) {
const options = {
method,
headers: {
'X-API-Key': this.apiKey,
'Content-Type': 'application/json',
},
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(`${this.baseUrl}${path}`, options);
if (!response.ok) {
const error = await response.json().catch(() => ({}));
const err = new Error(error.message || `HTTP ${response.status}`);
err.status = response.status;
err.errors = error.errors;
throw err;
}
return response.json();
}
// Factures
async createInvoice(data) {
return this.request('POST', '/invoices', data);
}
async getInvoice(id) {
return this.request('GET', `/invoices/${id}`);
}
async submitInvoice(id) {
return this.request('POST', `/invoices/${id}/submit`);
}
async downloadInvoice(id, type = 'pdf') {
return this.request('GET', `/invoices/${id}/download/${type}`);
}
// Signatures
async createSignature(data) {
return this.request('POST', '/signatures', data);
}
async getSignature(id) {
return this.request('GET', `/signatures/${id}`);
}
async remindSignature(id) {
return this.request('POST', `/signatures/${id}/remind`);
}
async cancelSignature(id) {
return this.request('POST', `/signatures/${id}/cancel`);
}
async downloadSigned(id) {
return this.request('GET', `/signatures/${id}/download/signed`);
}
}
module.exports = ScellClient;
// Utilisation
// const client = new ScellClient({
// apiKey: process.env.SCELL_API_KEY,
// environment: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox',
// });
// const invoice = await client.createInvoice({ ... });Application PHP / Laravel
Un service Laravel complet :
<?php
// app/Services/ScellService.php
namespace App\Services;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use Illuminate\Support\Facades\Log;
class ScellService
{
private Client $client;
public function __construct()
{
$environment = config('services.scell.environment', 'sandbox');
$baseUri = $environment === 'sandbox'
? 'https://api.scell.io/api/v1/sandbox/'
: 'https://api.scell.io/api/v1/';
$this->client = new Client([
'base_uri' => $baseUri,
'headers' => [
'X-API-Key' => config('services.scell.api_key'),
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
'timeout' => 30,
]);
}
// --- Factures ---
public function createInvoice(array $data): array
{
return $this->post('invoices', $data);
}
public function getInvoice(string $id): array
{
return $this->get("invoices/{$id}");
}
public function submitInvoice(string $id): array
{
return $this->post("invoices/{$id}/submit");
}
public function downloadInvoice(string $id, string $type = 'pdf'): array
{
return $this->get("invoices/{$id}/download/{$type}");
}
// --- Signatures ---
public function createSignature(array $data): array
{
return $this->post('signatures', $data);
}
public function getSignature(string $id): array
{
return $this->get("signatures/{$id}");
}
public function remindSignature(string $id): array
{
return $this->post("signatures/{$id}/remind");
}
public function cancelSignature(string $id): array
{
return $this->post("signatures/{$id}/cancel");
}
// --- HTTP helpers ---
private function get(string $uri): array
{
try {
$response = $this->client->get($uri);
return json_decode($response->getBody(), true);
} catch (ClientException $e) {
return $this->handleError($e);
}
}
private function post(string $uri, array $data = []): array
{
try {
$response = $this->client->post($uri, [
'json' => $data,
]);
return json_decode($response->getBody(), true);
} catch (ClientException $e) {
return $this->handleError($e);
}
}
private function handleError(ClientException $e): array
{
$response = $e->getResponse();
$body = json_decode($response->getBody(), true);
Log::error('Scell API error', [
'status' => $response->getStatusCode(),
'body' => $body,
]);
throw new \RuntimeException(
$body['message'] ?? 'Erreur API Scell.io',
$response->getStatusCode()
);
}
}Configuration Laravel :
// config/services.php
return [
// ...
'scell' => [
'api_key' => env('SCELL_API_KEY'),
'webhook_secret' => env('SCELL_WEBHOOK_SECRET'),
'environment' => env('SCELL_ENVIRONMENT', 'sandbox'),
],
];Script Python
Un module Python reutilisable :
# scell_client.py
import os
import time
import hmac
import hashlib
import requests
from typing import Optional
class ScellClient:
def __init__(self, api_key: Optional[str] = None, environment: str = 'sandbox'):
self.api_key = api_key or os.environ['SCELL_API_KEY']
self.base_url = (
'https://api.scell.io/api/v1/sandbox'
if environment == 'sandbox'
else 'https://api.scell.io/api/v1'
)
self.session = requests.Session()
self.session.headers.update({
'X-API-Key': self.api_key,
'Content-Type': 'application/json',
})
def _request(self, method: str, path: str, json_data: dict = None) -> dict:
url = f'{self.base_url}{path}'
response = self.session.request(method, url, json=json_data)
response.raise_for_status()
return response.json()
# --- Factures ---
def create_invoice(self, data: dict) -> dict:
return self._request('POST', '/invoices', data)
def get_invoice(self, invoice_id: str) -> dict:
return self._request('GET', f'/invoices/{invoice_id}')
def submit_invoice(self, invoice_id: str) -> dict:
return self._request('POST', f'/invoices/{invoice_id}/submit')
def download_invoice(self, invoice_id: str, file_type: str = 'pdf') -> dict:
return self._request('GET', f'/invoices/{invoice_id}/download/{file_type}')
# --- Signatures ---
def create_signature(self, data: dict) -> dict:
return self._request('POST', '/signatures', data)
def get_signature(self, signature_id: str) -> dict:
return self._request('GET', f'/signatures/{signature_id}')
def remind_signature(self, signature_id: str) -> dict:
return self._request('POST', f'/signatures/{signature_id}/remind')
def cancel_signature(self, signature_id: str) -> dict:
return self._request('POST', f'/signatures/{signature_id}/cancel')
# --- Verification de webhook ---
@staticmethod
def verify_webhook(payload: bytes, signature_header: str, secret: str) -> bool:
parts = dict(p.split('=', 1) for p in signature_header.split(','))
timestamp = parts.get('t', '')
received_sig = parts.get('v1', '')
# Verifier le timestamp (5 minutes max)
if abs(time.time() - int(timestamp)) > 300:
return False
# Calculer la signature attendue
signed_payload = f'{timestamp}.{payload.decode()}'
expected_sig = hmac.new(
secret.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_sig, received_sig)
# Utilisation :
# client = ScellClient(environment='sandbox')
# invoice = client.create_invoice({...})
# print(f"Facture creee : {invoice['data']['id']}")Recapitulatif
Voici les endpoints principaux que vous utiliserez au quotidien :
| Action | Methode | Endpoint |
|---|---|---|
| Consulter le solde | GET | /v1/balance |
| Creer une facture | POST | /v1/invoices |
| Voir une facture | GET | /v1/invoices/{id} |
| Soumettre une facture | POST | /v1/invoices/{id}/submit |
| Telecharger un PDF | GET | /v1/invoices/{id}/download/pdf |
| Creer une signature | POST | /v1/signatures |
| Voir une signature | GET | /v1/signatures/{id} |
| Envoyer un rappel | POST | /v1/signatures/{id}/remind |
| Annuler une signature | POST | /v1/signatures/{id}/cancel |
| Telecharger le signe | GET | /v1/signatures/{id}/download/signed |
| Creer un webhook | POST | /v1/webhooks |
| Tester un webhook | POST | /v1/webhooks/{id}/test |
Et ensuite ?
- Creer votre premiere facture : Voir le tutorial Creer votre Premiere Facture
- Faire signer un document : Voir le tutorial Votre Premiere Signature Electronique
- Documentation OpenAPI complete : Consultez la reference interactive sur api.scell.io/api/documentation
- Collections Postman : Importez la collection Postman disponible dans le dossier
docs/postman/du projet
Besoin d'aide ? Contactez notre equipe sur support@scell.io ou consultez la documentation API complete.