Bjet24

API v1 · OpenAPI 3.1 · stable

Référence API

Envoyez du courrier postal depuis votre CRM, ERP ou backoffice. Une seule URL de base, authentification par clé Bearer, facturation depuis votre wallet — aucune carte bancaire par appel.

Vue d'ensemble

Une API REST stateless, JSON in/out, versionnée. Toutes les routes sont préfixées par /v1.

  • URL de basehttps://bjet24.com/api
  • Versionv1 · OpenAPI 3.1
  • Formatapplication/json
  • AuthBearer / X-Api-Key

Authentification

Toutes les requêtes doivent inclure une clé API valide soit dans l'en-tête Authorization au format Bearer, soit dans l'en-tête X-Api-Key. Les clés ont le format bjk_<id>_<secret>.

curl https://bjet24.com/api/v1/mails \
  -H "Authorization: Bearer bjk_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Scopes disponibles

  • readlecture des envois, devis, listes
  • sendcréation d'envois (débite le wallet)
  • webhookscréation/suppression de webhooks
  • addressesGérer le carnet d'adresses (expéditeurs et destinataires).

Créez et révoquez vos clés depuis /account/api-keys.

Format des erreurs

Toutes les erreurs renvoient un code HTTP 4xx ou 5xx et un corps JSON avec un champ code (machine) et message (humain). Les erreurs de validation incluent un tableau issues décrivant chaque problème.

application/json
{
  "code": "VALIDATION_ERROR",
  "message": "Invalid request body",
  "issues": [
    { "path": ["pages"], "message": "Number must be greater than or equal to 1" }
  ]
}
  • VALIDATION_ERRORLe corps de la requête ne respecte pas le schéma. Voir issues.
  • UNAUTHORIZEDClé API absente ou invalide.
  • FORBIDDENLa clé n'a pas le scope requis pour cette route.
  • NOT_FOUNDRessource introuvable ou n'appartenant pas à votre organisation.
  • INSUFFICIENT_FUNDSSolde wallet insuffisant pour couvrir le coût de l'envoi.
  • PRICING_UNAVAILABLEPricing non configuré pour la zone/service demandé. Contactez le support.

Idempotence

Pour éviter les doublons en cas de retry réseau, passez un champ externalId unique (typiquement l'id de votre CRM) sur POST /v1/mails. Les rejouages avec le même externalId renvoient l'envoi existant — aucun nouveau débit.

Comment ça marche

À la première création réussie, le couple (organisation, externalId) est verrouillé. Tout POST suivant avec le même externalId renverra un 200 avec le mailJobId d'origine — même si le payload diffère.

Limites de débit

Les limites sont appliquées par clé API. Les requêtes au-delà du quota reçoivent un 429 avec un en-tête Retry-After.

  • Lecture (GET)120 / min
  • Écriture (POST, DELETE)60 / min
  • Burst max30

Obtenir un devis

POST/v1/quoteScope requis : read

Calcule le prix total pour un service / zone / nombre de pages. Aucun débit n'est effectué. Utile pour afficher le tarif côté client avant un envoi.

Corps de la requête

  • zonestringrequis
    NATIONALINTERNATIONAL

    Zone d'envoi. NATIONAL = Belgique, INTERNATIONAL = hors Belgique.

  • serviceTypestringrequis
    standardpriorregistered

    Type de service postal. registered ajoute une preuve de dépôt et un numéro de suivi.

  • pagesintegerrequis

    Nombre total de pages imprimées (1 à 2000).

  • colorbooleanoptionneldéfaut false

    Impression couleur. Tarif majoré.

  • acknowledgmentReceiptbooleanoptionneldéfaut false

    Accusé de réception (registered uniquement). Tarif majoré.

  • recipientCountintegeroptionneldéfaut 1

    Nombre de destinataires distincts (1 à 100). Multiplie le prix unitaire.

Réponses

  • 200Devis calculé. Le total inclut TVA si applicable.
  • 400Corps de requête invalide. Voir issues.
  • 401Clé API absente ou invalide.
curl -X POST https://bjet24.com/api/v1/quote \
  -H "Authorization: Bearer $BJET24_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "zone": "NATIONAL",
    "serviceType": "registered",
    "pages": 3,
    "color": false,
    "acknowledgmentReceipt": false,
    "recipientCount": 1
  }'

Pré-signer une URL d'upload

POST/v1/uploadsScope requis : send

Renvoie une URL PUT S3 valide 10 minutes. Uploadez votre PDF ou image dessus, puis passez la clé retournée comme s3Key dans POST /v1/mails. Les uploads vont directement vers S3 — pas de gros payloads transitent par notre API.

Corps de la requête

  • filenamestringrequis

    Nom de fichier original. Utilisé pour les logs et la facture.

  • mimeTypestringrequis
    application/pdfimage/jpegimage/pngimage/heicimage/heif

    Type MIME du fichier. PDF recommandé pour les courriers ; images converties automatiquement.

  • sizeBytesintegerrequis

    Taille en octets. Max 20 Mo.

Réponses

  • 200URL pré-signée renvoyée. Uploadez en PUT dans les 10 minutes.
  • 400Corps de requête invalide. Voir issues.
  • 401Clé API absente ou invalide.
curl -X POST https://bjet24.com/api/v1/uploads \
  -H "Authorization: Bearer $BJET24_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "contract.pdf",
    "mimeType": "application/pdf",
    "sizeBytes": 124532
  }'

Créer et envoyer un courrier

POST/v1/mailsScope requis : send

Met l'envoi en file et débite immédiatement votre wallet. Si dryRun=true, l'envoi est validé et tarifé sans débit ni création. Passez externalId pour l'idempotence et webhookUrl pour un callback par envoi.

Corps de la requête

  • zonestringrequis
    NATIONALINTERNATIONAL

    Zone d'envoi. NATIONAL = Belgique, INTERNATIONAL = hors Belgique.

  • serviceTypestringrequis
    standardpriorregistered

    Type de service postal. registered ajoute une preuve de dépôt et un numéro de suivi.

  • pagesintegerrequis

    Nombre total de pages imprimées (1 à 2000).

  • colorbooleanoptionneldéfaut false

    Impression couleur. Tarif majoré.

  • acknowledgmentReceiptbooleanoptionneldéfaut false

    Accusé de réception (registered uniquement). Tarif majoré.

  • scheduledDropAtstring (ISO 8601)optionnel

    Date/heure de dépôt programmée (ISO 8601). Si absent, dépôt à la prochaine cutoff.

  • recipientsRecipient[]requis

    Liste des destinataires (1 à 100). Chacun reçoit un exemplaire imprimé.

  • senderSenderoptionnel

    Adresse expéditeur affichée sur l'enveloppe. Si absent, utilise l'adresse par défaut de votre compte.

  • filesFile[]requis

    Fichiers à imprimer (1 à 20). Concaténés dans l'ordre. s3Key vient de POST /v1/uploads.

  • externalIdstringoptionnel

    Identifiant côté appelant (ex. id de votre CRM). Garantit l'idempotence.

  • webhookUrlstring (uri)optionnel

    URL de webhook spécifique à cet envoi. Surcharge la config globale.

  • dryRunbooleanoptionneldéfaut false

    Si true, valide et tarife sans débiter ni persister. Réponse identique mais mailJobId est null.

Réponses

  • 201Envoi créé et mis en file. Le wallet est débité.
  • 200Hit d'idempotence — l'envoi existait déjà avec le même externalId.
  • 400Corps de requête invalide. Voir issues.
  • 402Solde wallet insuffisant. Rechargez avant de réessayer.
  • 503Pricing non configuré pour la zone/service.
curl -X POST https://bjet24.com/api/v1/mails \
  -H "Authorization: Bearer $BJET24_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "zone": "NATIONAL",
    "serviceType": "registered",
    "pages": 3,
    "color": false,
    "recipients": [{
      "name": "Jean Dupont",
      "line1": "Rue de la Loi 16",
      "zip": "1000",
      "city": "Brussels",
      "country": "BE"
    }],
    "files": [{
      "s3Key": "uploads/2026-05-19/8af9...c12.pdf",
      "filename": "contract.pdf",
      "mimeType": "application/pdf",
      "sizeBytes": 124532
    }],
    "externalId": "crm-contract-7281"
  }'

Lister les envois

GET/v1/mailsScope requis : read

Renvoie vos envois, du plus récent au plus ancien. Paginé par curseur.

Paramètres de requête

  • statusstringoptionnel

    Filtre par statut (queued, printed, posted, delivered, failed).

  • externalIdstringoptionnel

    Filtre par externalId exact.

  • limitintegeroptionneldéfaut 20

    Nombre de résultats par page (1 à 100).

  • cursorstringoptionnel

    Curseur de pagination renvoyé par la réponse précédente.

Réponses

  • 200Liste paginée d'envois.
cURL
curl "https://bjet24.com/api/v1/mails?status=queued&limit=20" \
  -H "Authorization: Bearer $BJET24_KEY"

Récupérer un envoi

GET/v1/mails/{id}Scope requis : read

Renvoie le détail complet d'un envoi : statut, destinataires, fichiers, montants, timestamps.

Paramètres de chemin

  • idstringrequis

    Identifiant de l'envoi (mj_…).

Réponses

  • 200Détail de l'envoi.
  • 404Ressource introuvable.
cURL
curl https://bjet24.com/api/v1/mails/mj_01HZX5K8... \
  -H "Authorization: Bearer $BJET24_KEY"

Simuler le cycle de vie (sandbox)

POST/v1/mails/{id}/simulateScope requis : send

Mode test uniquement. Fait avancer un envoi sandbox dans son cycle de vie et déclenche le webhook « test » correspondant. Sans `to`, l'envoi avance d'une étape (queued → printed → posted → delivered) ; avec `to`, il saute directement à ce statut — par ex. {"to":"failed"} pour tester la gestion d'échec. Les envois sandbox progressent aussi automatiquement : cet endpoint sert au test déterministe et à la demande. Refusé (403) avec une clé live.

Paramètres de chemin

  • idstringrequis

    Identifiant de l'envoi (mj_…).

Corps de la requête

  • tostringoptionnel
    printedposteddeliveredfailed

    Statut cible. Omis : avance d'une étape. Valeurs : printed, posted, delivered, failed. Un saut direct ne déclenche que le webhook du statut atteint.

Réponses

  • 200Transition appliquée — renvoie l'ancien et le nouveau statut.
  • 400Corps de requête invalide. Voir issues.
  • 403Identifiants live — la simulation est réservée au mode test.
  • 404Ressource introuvable.
  • 409Transition impossible (statut déjà terminal ou cible inatteignable).
cURL
# Test mode only — advance the sandbox mail one step
curl -X POST https://bjet24.com/api/v1/mails/mj_01HZX5K8.../simulate \
  -H "Authorization: Bearer $BJET24_TEST_KEY"

# …or jump straight to a status (e.g. test failure handling)
curl -X POST https://bjet24.com/api/v1/mails/mj_01HZX5K8.../simulate \
  -H "Authorization: Bearer $BJET24_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to":"failed"}'

Lister les adresses

GET/v1/addressesScope requis : addresses

Renvoie les adresses enregistrées du carnet — expéditeurs et destinataires. Filtrable par type et par recherche texte.

Paramètres de requête

  • kindstringoptionnel
    SENDERRECIPIENT

    Filtre par type d'adresse.

  • qstringoptionnel

    Recherche texte sur le nom, la ville, le code postal ou le libellé.

  • limitintegeroptionneldéfaut 100

    Nombre maximum d'adresses renvoyées (1–200).

Réponses

  • 200La liste des adresses du carnet.
  • 401Clé API absente ou invalide.
cURL
curl "https://bjet24.com/api/v1/addresses?kind=RECIPIENT&limit=50" \
  -H "Authorization: Bearer $BJET24_KEY"

Créer une adresse

POST/v1/addressesScope requis : addresses

Enregistre une adresse dans le carnet — par exemple pour synchroniser les contacts de votre CRM.

Corps de la requête

  • kindstringrequis
    SENDERRECIPIENT

    Type d'adresse : expéditeur (SENDER) ou destinataire (RECIPIENT).

  • namestringrequis

    Nom du destinataire ou de l'expéditeur.

  • line1stringrequis

    Première ligne d'adresse (rue et numéro).

  • line2stringoptionnel

    Complément d'adresse facultatif (boîte, étage…).

  • zipstringrequis

    Code postal.

  • citystringrequis

    Ville.

  • countrystringrequis

    Code pays ISO 3166-1 alpha-2 (ex. BE).

  • labelstringoptionnel

    Libellé interne facultatif, ex. Siège ou Client X.

  • isDefaultbooleanoptionneldéfaut false

    Définit cette adresse comme adresse par défaut pour son type.

Réponses

  • 201L'adresse, dans sa forme publique.
  • 400Corps de requête invalide. Voir issues.
curl -X POST https://bjet24.com/api/v1/addresses \
  -H "Authorization: Bearer $BJET24_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "RECIPIENT",
    "name": "Jean Dupont",
    "line1": "Rue de la Loi 16",
    "zip": "1000",
    "city": "Brussels",
    "country": "BE",
    "label": "Client 7281"
  }'

Récupérer une adresse

GET/v1/addresses/{id}Scope requis : addresses

Renvoie une adresse précise du carnet.

Paramètres de chemin

  • idstringrequis

    Identifiant de l'adresse.

Réponses

  • 200L'adresse, dans sa forme publique.
  • 404Ressource introuvable.
cURL
curl https://bjet24.com/api/v1/addresses/adr_01HZX5K8... \
  -H "Authorization: Bearer $BJET24_KEY"

Modifier une adresse

PATCH/v1/addresses/{id}Scope requis : addresses

Met à jour les champs fournis d'une adresse existante. Les champs omis restent inchangés.

Paramètres de chemin

  • idstringrequis

    Identifiant de l'adresse.

Corps de la requête

  • kindstringoptionnel
    SENDERRECIPIENT

    Type d'adresse : expéditeur (SENDER) ou destinataire (RECIPIENT).

  • namestringoptionnel

    Nom du destinataire ou de l'expéditeur.

  • line1stringoptionnel

    Première ligne d'adresse (rue et numéro).

  • line2stringoptionnel

    Complément d'adresse facultatif (boîte, étage…).

  • zipstringoptionnel

    Code postal.

  • citystringoptionnel

    Ville.

  • countrystringoptionnel

    Code pays ISO 3166-1 alpha-2 (ex. BE).

  • labelstringoptionnel

    Libellé interne facultatif, ex. Siège ou Client X.

  • isDefaultbooleanoptionnel

    Définit cette adresse comme adresse par défaut pour son type.

Réponses

  • 200L'adresse, dans sa forme publique.
  • 400Corps de requête invalide. Voir issues.
  • 404Ressource introuvable.
cURL
curl -X PATCH https://bjet24.com/api/v1/addresses/adr_01HZX5K8... \
  -H "Authorization: Bearer $BJET24_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "city": "Bruxelles", "isDefault": true }'

Supprimer une adresse

DELETE/v1/addresses/{id}Scope requis : addresses

Supprime définitivement une adresse du carnet.

Paramètres de chemin

  • idstringrequis

    Identifiant de l'adresse.

Réponses

  • 200Adresse supprimée.
  • 404Ressource introuvable.
cURL
curl -X DELETE https://bjet24.com/api/v1/addresses/adr_01HZX5K8... \
  -H "Authorization: Bearer $BJET24_KEY"

Lister vos webhooks

GET/v1/webhooksScope requis : read

Renvoie tous les endpoints webhook actifs sur votre compte.

Réponses

  • 200Liste des webhooks (sans le secret).
cURL
curl https://bjet24.com/api/v1/webhooks \
  -H "Authorization: Bearer $BJET24_KEY"

Enregistrer un webhook

POST/v1/webhooksScope requis : webhooks

Crée un endpoint webhook. Le secret de signature n'est renvoyé qu'UNE seule fois — stockez-le immédiatement, on ne peut pas le récupérer.

Corps de la requête

  • urlstring (uri)requis

    URL HTTPS où les événements seront postés.

  • eventsstring[]requis
    MAIL_QUEUEDMAIL_PRINTEDMAIL_POSTEDMAIL_DELIVEREDMAIL_FAILED

    Liste d'événements à recevoir. Au moins un requis.

Réponses

  • 201Webhook créé. Notez le secret renvoyé.
  • 400Corps de requête invalide. Voir issues.
curl -X POST https://bjet24.com/api/v1/webhooks \
  -H "Authorization: Bearer $BJET24_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/webhooks/bjet24",
    "events": ["MAIL_QUEUED", "MAIL_POSTED", "MAIL_DELIVERED", "MAIL_FAILED"]
  }'

Supprimer un webhook

DELETE/v1/webhooks/{id}Scope requis : webhooks

Désactive immédiatement l'endpoint. Les événements ne seront plus livrés.

Paramètres de chemin

  • idstringrequis

    Identifiant du webhook (wh_…).

Réponses

  • 200Webhook supprimé.
  • 404Ressource introuvable.
cURL
curl -X DELETE https://bjet24.com/api/v1/webhooks/wh_01HZX5K8... \
  -H "Authorization: Bearer $BJET24_KEY"

Événements webhook

Cinq événements couvrent le cycle de vie d'un envoi. Chaque livraison inclut le payload JSON, l'en-tête X-Bjet24-Event et la signature.

  • MAIL_QUEUEDEnvoi mis en file après débit du wallet.
  • MAIL_PRINTEDDocument imprimé à l'atelier.
  • MAIL_POSTEDDéposé au bureau de poste.
  • MAIL_DELIVEREDDistribué (recommandé uniquement).
  • MAIL_FAILEDÉchec d'impression, de dépôt ou annulation.

Exemple de payload

application/json
{
  "event": "MAIL_POSTED",
  "mailJobId": "mj_01HZX5K8...",
  "externalId": "crm-contract-7281",
  "status": "posted",
  "postedAt": "2026-05-19T16:42:11.000Z"
}

Signature & vérification

Chaque livraison webhook est signée en HMAC-SHA256 avec le secret renvoyé à la création. Vérifiez la signature avant de traiter le payload.

En-tête de signature

X-Bjet24-Signature: t=<ts>,v1=<hex>

Le timestamp est en secondes Unix. La signature est calculée sur la concaténation <ts>.<rawBody> avec votre secret webhook.
Node.js
import crypto from 'node:crypto'

export function verifyBjet24(req, secret) {
  const header = req.headers['x-bjet24-signature']
  // header looks like: t=1714940000,v1=<hmac-sha256-hex>
  const [tPart, sPart] = header.split(',')
  const ts = tPart.split('=')[1]
  const sig = sPart.split('=')[1]

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${ts}.${req.rawBody}`)
    .digest('hex')

  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    throw new Error('Bad signature')
  }
  if (Date.now() / 1000 - Number(ts) > 300) {
    throw new Error('Replay')
  }
}

Protection contre le rejeu

Rejetez les livraisons dont le timestamp a plus de 5 minutes. Combinez signature valide + timestamp frais pour bloquer les attaques par rejeu.

Connecter une application tierce (OAuth2)

Pour qu'une application (un CRM, un outil métier) agisse au nom de plusieurs utilisateurs Bjet24 — chacun avec son propre portefeuille — utilisez OAuth2 plutôt qu'une clé API. L'utilisateur connecte son compte en un clic, sans copier-coller de clé.

Clé API ou OAuth2 ?

Une clé API convient quand vous n'envoyez du courrier que pour votre propre compte. OAuth2 est nécessaire quand votre application sert plusieurs clients Bjet24 distincts : chaque utilisateur autorise l'accès et c'est son portefeuille qui est débité.

Enregistrer votre application

Un administrateur Bjet24 enregistre votre application et vous communique un client_id, un client_secret et la ou les redirect_uri autorisées.

Le flux Authorization Code + PKCE

1. Rediriger l'utilisateur vers l'écran de consentement.

GET /oauth/authorize
https://bjet24.com/fr/oauth/authorize
  ?response_type=code
  &client_id=bjc_xxxxxxxxxxxxxxxxxxxxxxxx
  &redirect_uri=https://your-app.example.com/callback
  &scope=send%20read%20wallet
  &state=<opaque-random>
  &code_challenge=<base64url(sha256(code_verifier))>
  &code_challenge_method=S256

2. Échanger le code reçu contre une paire de jetons.

POST /api/oauth/token
curl -X POST https://bjet24.com/api/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  -d client_id=bjc_xxxxxxxxxxxxxxxxxxxxxxxx \
  -d client_secret=$BJET24_CLIENT_SECRET \
  -d code=$AUTH_CODE \
  -d redirect_uri=https://your-app.example.com/callback \
  -d code_verifier=$PKCE_VERIFIER
200 OK
{
  "access_token": "bja_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "bjr_...",
  "scope": "send read wallet"
}

3. Appeler l'API avec l'access token en Bearer.

cURL
curl https://bjet24.com/api/v1/wallet \
  -H "Authorization: Bearer bja_..."

4. Renouveler l'access token expiré avec le refresh token.

POST /api/oauth/token
curl -X POST https://bjet24.com/api/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=refresh_token \
  -d client_id=bjc_xxxxxxxxxxxxxxxxxxxxxxxx \
  -d client_secret=$BJET24_CLIENT_SECRET \
  -d refresh_token=$REFRESH_TOKEN

Durée de vie et révocation

Un access token vit 1 h, un refresh token 60 j et tourne à chaque usage. L'utilisateur peut révoquer l'accès à tout moment depuis son compte Bjet24 ; votre application peut aussi appeler POST /api/oauth/revoke.

Scopes OAuth

Les scopes demandés au moment de l'autorisation déterminent ce que votre application peut faire.

  • sendcréation d'envois (débite le wallet)
  • readlecture des envois, devis, listes
  • webhookscréation/suppression de webhooks
  • walletConsulter le solde du portefeuille de l'utilisateur.
  • addressesGérer le carnet d'adresses (expéditeurs et destinataires).