DocumentationAPI v1 · 1.3.0

Packages

SDK Node.js

Installer et utiliser @labyrinthe/connect : client TypeScript, Express, Next.js, SMS, OTP, webhooks et erreurs.

Client TypeScript officiel pour Node.js 18+ (ESM et CommonJS), compatible Deno, Bun et runtimes edge (fetch et Web Crypto natifs). Paramètres et résultats en camelCase, dates en Date.

Version 1.2.0. Code source, exemples et journal des modifications : dossier node sur GitHub.

Installation#

Shell
npm install @labyrinthe/connect

Configuration#

Node.js
import { Labyrinthe } from '@labyrinthe/connect';

// From the environment: LABYRINTHE_API_KEY, LABYRINTHE_BASE_URL
const client = new Labyrinthe();

// Or explicitly
const configured = new Labyrinthe({
  apiKey: 'lsk_test_xxxxxxxxxxxxxxxx',
  timeoutMs: 30_000,   // per attempt
  maxRetries: 2,       // 0 to 10
  defaultSender: 'MABOUTIQUE',
});

configured.isTestMode; // true with a lsk_test_ key
OptionDéfautRôle
apiKeyLABYRINTHE_API_KEYObligatoire.
baseUrlLABYRINTHE_BASE_URL, puis productionUn hôte seul reçoit /api/public/v1.
timeoutMs30000Par tentative.
maxRetries / maxRetryWaitMs2 / 10000Relances et attente Retry-After maximale acceptée.
defaultCountryCode, defaultSender243, —Numéros locaux, Sender ID par défaut.
fetch, logger—Implémentation fetch et journal.
dangerouslyAllowBrowserfalseLe client refuse de tourner dans un navigateur : la clé doit rester côté serveur.

Chaque méthode accepte en dernier argument { signal, timeoutMs, requestId, onResponse } ; onResponse reçoit le statut, requestId, replayed et les limites de débit.

Envoyer un SMS#

Node.js
import { Labyrinthe } from '@labyrinthe/connect';

const client = new Labyrinthe({ apiKey: 'lsk_test_xxxxxxxxxxxxxxxx' }); // or new Labyrinthe() (LABYRINTHE_API_KEY)

const message = await client.sms.send({
  to: '243812345678',
  body: 'Votre commande 1042 est prête.',
  reference: 'CMD-1042',
  idempotencyKey: 'order-1042-ready', // replaying within 24 h sends nothing again
});

console.log(message.id, message.status); // 'queued'

Plusieurs destinataires (jusqu'à 1000, tout ou rien) :

Node.js
import { Labyrinthe } from '@labyrinthe/connect';

const client = new Labyrinthe({ apiKey: 'lsk_test_xxxxxxxxxxxxxxxx' }); // or new Labyrinthe() (LABYRINTHE_API_KEY)

const batch = await client.sms.sendMany({
  recipients: ['243812345678', '0899999999'],
  body: 'Rappel : reunion demain 9h.',
  sender: 'MABOUTIQUE', // an approved Sender ID of the account
  reference: 'RAPPEL-2026-11-06',
  idempotencyKey: 'reminder-2026-11-06',
});

for (const message of batch.messages) {
  console.log(message.recipient, message.status);
}

Estimer le coût, consulter le solde, lister les messages :

Node.js
import { Labyrinthe } from '@labyrinthe/connect';

const client = new Labyrinthe({ apiKey: 'lsk_test_xxxxxxxxxxxxxxxx' }); // or new Labyrinthe() (LABYRINTHE_API_KEY)

const estimate = await client.sms.estimate({
  body: 'Votre code est 482913',
  recipients: ['243812345678', '0812345678', '0899999999'],
});

console.log(estimate.encoding, estimate.recipientsCount, estimate.totalCredits); // gsm7 2 2
if (!estimate.isAffordable) {
  // top up before sending
}
Node.js
import { Labyrinthe } from '@labyrinthe/connect';

const client = new Labyrinthe({ apiKey: 'lsk_test_xxxxxxxxxxxxxxxx' }); // or new Labyrinthe() (LABYRINTHE_API_KEY)

const { balance, held } = await client.balance.get();

console.log(balance + ' credits available, ' + held + ' held');
Node.js
import { Labyrinthe } from '@labyrinthe/connect';

const client = new Labyrinthe({ apiKey: 'lsk_test_xxxxxxxxxxxxxxxx' }); // or new Labyrinthe() (LABYRINTHE_API_KEY)

// One page
const page = await client.sms.list({ status: 'delivered', from: '2026-11-01', perPage: 50 });
console.log(page.total, page.hasMore);

// Every page, lazily
for await (const message of client.sms.list({ status: 'delivered', from: '2026-11-01' })) {
  console.log(message.id, message.recipient);
}

Vérification OTP#

1. Envoyer le code
import { Labyrinthe, OtpResendTooSoonError } from '@labyrinthe/connect';

const client = new Labyrinthe({ apiKey: 'lsk_test_xxxxxxxxxxxxxxxx' }); // or new Labyrinthe() (LABYRINTHE_API_KEY)

try {
  const otp = await client.otp.send({ phone: '243812345678', locale: 'fr' });
  session.otpId = otp.otpId; // keep it server-side
  console.log(otp.codeLength, otp.resendAfterSeconds); // 6 60
  console.log(otp.testCode); // '482913' with a lsk_test_ key, null in live
} catch (error) {
  if (error instanceof OtpResendTooSoonError) {
    console.log('Wait ' + (error.retryAfterMs ?? 0) / 1000 + ' s');
  } else {
    throw error;
  }
}
2. Vérifier le code
import { Labyrinthe } from '@labyrinthe/connect';

const client = new Labyrinthe({ apiKey: 'lsk_test_xxxxxxxxxxxxxxxx' }); // or new Labyrinthe() (LABYRINTHE_API_KEY)

const result = await client.otp.verify({ otpId: session.otpId, code: req.body.code });

if (result.verified) {
  delete session.otpId; // phone verified: log the user in
} else if (result.reason === 'invalid_code') {
  console.log('Wrong code, ' + result.attemptsRemaining + ' attempt(s) left');
} else {
  console.log('Ask for a new code:', result.reason); // expired, max_attempts_reached, already_used
}

Recevoir les webhooks#

Node.js
import express from 'express';
import { labyrintheWebhook } from '@labyrinthe/connect/express';

const app = express();

// Before express.json(): the middleware needs the raw body
app.post('/webhooks/labyrinthe', labyrintheWebhook({
  secret: process.env.LABYRINTHE_WEBHOOK_SECRET,
  onEvent: async (event) => {
    switch (event.event) {
      case 'message.status_changed':
        console.log(event.data.messageId, event.data.status);
        break;
      case 'otp.failed':
        console.log(event.data.otpId, event.data.reason);
        break;
      default:
        break; // unknown events are acknowledged
    }
  },
}));

app.use(express.json());

// Next.js App Router (app/api/webhooks/labyrinthe/route.ts):
// import { labyrintheWebhookHandler } from '@labyrinthe/connect/next';
// export const POST = labyrintheWebhookHandler({ onEvent: async (event) => { … } });

Erreurs et maintenance#

La maintenance (503 FEATURE_DISABLED et OTP_SERVICE_DISABLED) a sa propre exception : rien n'a été envoyé ni débité. Le package relance seul si Retry-After est court (10 s par défaut) ; sinon, mettez l'envoi en file d'attente. Voir Erreurs.

Node.js
import { Labyrinthe, ApiError, FeatureDisabledError, InsufficientCreditsError, RateLimitError, RecipientNotSendableError } from '@labyrinthe/connect';

const client = new Labyrinthe({ apiKey: 'lsk_test_xxxxxxxxxxxxxxxx' }); // or new Labyrinthe() (LABYRINTHE_API_KEY)

try {
  await client.sms.send({ to: '243812345678', body: 'Bonjour' });
} catch (error) {
  if (error instanceof FeatureDisabledError) {
    // 503 maintenance: nothing was sent, no credit used
    console.log(error.feature, error.blockedBy, error.adminMessage, error.until);
    if (error.retryAfter !== null) scheduleRetry(error.retryAfter); // seconds
    else queueForLater();
  } else if (error instanceof InsufficientCreditsError) {
    notifyTopUp(error.available, error.required);
  } else if (error instanceof RecipientNotSendableError) {
    console.log(error.rejected); // [{ recipient, reason }]
  } else if (error instanceof RateLimitError) {
    scheduleRetry((error.retryAfterMs ?? 60_000) / 1000);
  } else if (error instanceof ApiError) {
    console.error(error.code, error.status, error.requestId);
  } else {
    throw error; // network error, timeout…
  }
}

Webhooks avec Next.js#

Node.js
// app/api/webhooks/labyrinthe/route.ts — Node.js or Edge runtime
import { labyrintheWebhookHandler } from '@labyrinthe/connect/next';

export const POST = labyrintheWebhookHandler({
  // secret defaults to LABYRINTHE_WEBHOOK_SECRET
  onEvent: async (event) => {
    if (event.event === 'message.status_changed') {
      console.log(event.data.messageId, event.data.status);
    }
    // Answer within 10 s: queue slow work. Throwing answers 500 and the delivery is retried.
  },
});

Mode test#

client.isTestMode indique une clé lsk_test_… ; otp.testCode contient alors le code (masqué dans JSON.stringify et la console). Webhooks.sign(rawBody, secret) fabrique une signature valide pour vos tests.