Patterns
Recurring invoices and subscriptions
Monthly/yearly cadence, draft vs auto-send, occurrence idempotency
A recurring invoice profile (`recurring_invoices`) describes a buyer, line items and a cadence (`recurrence`: day/week/month/year + `day_of_month`/`day_of_week`). At each due date the scheduler emits an occurrence. Two modes: `emission_mode='draft'` stages each occurrence as a draft (manual review before sending); `auto_send` emits AND sends automatically. Termination is controlled via `end_mode` (`never`, `on_date` + `end_date`, `after_occurrences` + `max_occurrences`). The critical fiscal pattern: each occurrence is a full ISCA invoice (hash chain, sequential numbering). Emission idempotency prevents a scheduler re-run from doubling an occurrence on the same day. `runNow()` forces an immediate emission (test/catch-up); `pause()`/`activate()` suspend the cadence without losing history.
Key facts
- Cadence: `recurrence.interval_unit` (day/week/month/year) + day_of_*
- `emission_mode`: `draft` (review) or `auto_send`
- `end_mode`: never / on_date / after_occurrences
- Each occurrence = full ISCA invoice (hash + sequence)
- Idempotency: a scheduler re-run does not double the occurrence
- Endpoints: `/api/v1/recurring-invoices` + `/{id}/run-now`, `/pause`, `/activate`
Code example
import { ScellApiClient } from '@scell/sdk'; // v3.5.0
const client = new ScellApiClient({ apiKey: process.env.SCELL_API_KEY! });
// 1. Profil d'abonnement mensuel auto-envoyé, le 1er de chaque mois
const profile = await client.recurringInvoices.create({
title: 'Abonnement mensuel — Acme',
buyer_id: 'buyer-uuid', // ou champs buyer_* inline
currency: 'EUR',
output_format: 'facturx',
emission_mode: 'auto_send',
start_date: '2026-07-01',
recurrence: { interval_unit: 'month', interval_count: 1, day_of_month: 1 },
end_mode: 'never',
notify_before_days: 3,
lines: [{ description: 'Abonnement SaaS', quantity: 1, unit_price: 99, vat_rate: 20 }],
});
// 2. Lister les occurrences déjà émises
const { data: occurrences } = await client.recurringInvoices.occurrences(profile.id, { per_page: 12 });
console.log(`${occurrences.length} occurrences émises`);
// 3. Émission immédiate (rattrapage / test)
const run = await client.recurringInvoices.runNow(profile.id);
console.log('Occurrence forcée :', run);
// 4. Suspendre puis réactiver sans perdre l'historique
await client.recurringInvoices.pause(profile.id);
await client.recurringInvoices.activate(profile.id);