Créer une facture ou un avoir par l’API Billabex suppose de joindre un document, PDF ou image. Ce guide décrit les formats acceptés, les limites de taille et les modes d’encodage.
Vue d’ensemble
Un fichier est exigé par :
POST /api/public/v1/invoices: créer une facturePOST /api/public/v1/credit-notes: créer un avoirPUT /api/public/v1/organizations/{organizationId}/logo: poser le logo de l’organisationPUT /api/public/v1/users/me/avatar: poser la photo de profil de l’utilisateur authentifié
Ces endpoints acceptent des requêtes multipart/form-data avec un fichier joint.
Les pièces jointes d’email prennent une autre forme : elles voyagent en JSON et non en
multipart/form-data, parce qu’un email en porte plusieurs à la fois. Les deux endpoints ci-dessous
acceptent un tableau files d’objets { content, filename, mimeType }, où content est le fichier
encodé en base64, aux côtés d’un tableau attachmentIds/attachments nommant des fichiers qui
existent déjà :
POST /api/public/v1/accounts/{accountId}/outgoing-email-communications: envoyer un email tout de suitePUT /api/public/v1/outgoing-email-communications/{communicationId}: modifier une relance planifiée
À la modification, attachments est la liste des identifiants de pièces à conserver : envoyer
une liste plus courte est donc la façon de détacher une pièce. Elle n’accepte que des identifiants
que le serveur a déjà validés sur cette communication : les fichiers déjà présents sur l’email, ceux
téléversés par files dans le même appel, ceux de son manifeste financier et son document de
coordonnées bancaires. Un document financier que la relance a déclaré obligatoire ne peut pas être
retiré, et requiredAttachmentIds sur la communication dit lesquels le sont. Les fichiers envoyés
par files sont attachés que attachments les cite ou non : leurs identifiants sont justement
créés par cet appel. Le serveur vérifie l’accès à la relance planifiée avant le téléversement, et
supprime tous les fichiers créés par la requête si la modification est refusée.
Les documents créés ainsi portent le canal PublicApi, renvoyé sur chaque facture et chaque avoir. Ils coexistent avec les documents importés par un connecteur et ceux saisis dans la webapp : une organisation peut utiliser tout cela en même temps. Voyez Sources de compte pour la différence entre le channel d’un document et la source d’un compte.
Formats acceptés
| Type MIME | Extension | Description |
|---|---|---|
application/pdf |
Documents PDF (recommandé) | |
image/png |
.png | Images PNG |
image/jpeg |
.jpg, .jpeg | Images JPEG |
image/webp |
.webp | Images WebP |
Le logo d’organisation et l’avatar utilisateur font exception : ils n’acceptent que des images
(image/png, image/jpeg, image/webp), jamais un PDF, et sont plafonnés à 2 Mo. Un DELETE sur
la même route retire l’image. Retirer le logo d’une organisation fait revenir au logo déduit de son
domaine email ; retirer un avatar fait revenir aux initiales de l’utilisateur.
Limites de taille
- Taille maximale : 10 Mo une fois décodé, 2 Mo pour les logos et les avatars
- Au-delà, le fichier est rejeté avec une erreur
400 Bad Request
Modes d’encodage
Billabex accepte deux encodages :
| Encodage | Content-Transfer-Encoding | Quand l’utiliser |
|---|---|---|
| Binaire | 7bit, 8bit ou binary |
Par défaut, pour la plupart des clients HTTP |
| Base64 | base64 |
Systèmes historiques, NetSuite, etc. |
Envoi binaire (par défaut)
La plupart des clients HTTP envoient les fichiers en binaire par défaut. C’est la méthode recommandée quand votre plateforme le permet.
Exemple JavaScript
const formData = new FormData();
formData.append('accountId', 'ACCOUNT_ID');
formData.append('number', 'INV-2024-001');
formData.append('issuedDate.year', '2024');
formData.append('issuedDate.month', '1');
formData.append('issuedDate.day', '15');
formData.append('dueDate.year', '2024');
formData.append('dueDate.month', '2');
formData.append('dueDate.day', '15');
formData.append('totalAmount', '1500.00');
formData.append('taxAmount', '300.00');
formData.append('paidAmount', '0');
formData.append('billingAddress.street', '123 rue Principale');
formData.append('billingAddress.city', 'Paris');
formData.append('billingAddress.postalCode', '75001');
formData.append('billingAddress.country', 'FR');
formData.append('file', fileBlob, 'facture.pdf');
const response = await fetch('[baseURL]/api/public/v1/invoices', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
},
body: formData,
});
Exemple cURL
curl -X POST "[baseURL]/api/public/v1/invoices" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-F "accountId=ACCOUNT_ID" \
-F "number=INV-2024-001" \
-F "issuedDate.year=2024" \
-F "issuedDate.month=1" \
-F "issuedDate.day=15" \
-F "dueDate.year=2024" \
-F "dueDate.month=2" \
-F "dueDate.day=15" \
-F "totalAmount=1500.00" \
-F "taxAmount=300.00" \
-F "paidAmount=0" \
-F "billingAddress.street=123 rue Principale" \
-F "billingAddress.city=Paris" \
-F "billingAddress.postalCode=75001" \
-F "billingAddress.country=FR" \
-F "file=@facture.pdf"
Envoi en base64
Certaines plateformes, NetSuite par exemple, ne savent pas envoyer de binaire et doivent encoder les fichiers en base64. Billabex détecte et décode automatiquement ce cas.
Pour l’utiliser :
- encodez le contenu du fichier en base64 ;
- posez l’en-tête
Content-Transfer-Encoding: base64sur la partie du fichier.
Ce que fait la base64
L’encodage base64 convertit du binaire en texte ASCII, ce qui augmente la taille d’environ 33 %. Un fichier de 10 Mo pèse environ 13,3 Mo une fois encodé.
Billabex le gère seul :
- la limite de 10 Mo porte sur la taille décodée ;
- un envoi encodé jusqu’à environ 13,7 Mo est accepté ;
- le fichier est décodé côté serveur avant stockage.
Exemple JavaScript (base64)
// Lire le fichier et l'encoder en base64
const fileBuffer = await file.arrayBuffer();
const base64Content = btoa(String.fromCharCode(...new Uint8Array(fileBuffer)));
// Construire la requête multipart avec le fichier encodé
const boundary = '----WebKitFormBoundary' + Math.random().toString(36).slice(2);
const body = [
`--${boundary}`,
'Content-Disposition: form-data; name="accountId"',
'',
'ACCOUNT_ID',
`--${boundary}`,
'Content-Disposition: form-data; name="number"',
'',
'INV-2024-001',
// ... autres champs ...
`--${boundary}`,
'Content-Disposition: form-data; name="file"; filename="facture.pdf"',
'Content-Type: application/pdf',
'Content-Transfer-Encoding: base64',
'',
base64Content,
`--${boundary}--`,
].join('\r\n');
const response = await fetch('[baseURL]/api/public/v1/invoices', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': `multipart/form-data; boundary=${boundary}`,
},
body: body,
});
Exemple Node.js (base64)
const fs = require('fs');
const path = require('path');
// Lire le fichier et l'encoder en base64
const filePath = './facture.pdf';
const fileContent = fs.readFileSync(filePath);
const base64Content = fileContent.toString('base64');
const filename = path.basename(filePath);
// Construire le corps multipart à la main
const boundary = '----FormBoundary' + Date.now();
const CRLF = '\r\n';
const parts = [
`--${boundary}`,
'Content-Disposition: form-data; name="accountId"',
'',
'ACCOUNT_ID',
`--${boundary}`,
'Content-Disposition: form-data; name="number"',
'',
'INV-2024-001',
`--${boundary}`,
'Content-Disposition: form-data; name="issuedDate.year"',
'',
'2024',
`--${boundary}`,
'Content-Disposition: form-data; name="issuedDate.month"',
'',
'1',
`--${boundary}`,
'Content-Disposition: form-data; name="issuedDate.day"',
'',
'15',
`--${boundary}`,
'Content-Disposition: form-data; name="dueDate.year"',
'',
'2024',
`--${boundary}`,
'Content-Disposition: form-data; name="dueDate.month"',
'',
'2',
`--${boundary}`,
'Content-Disposition: form-data; name="dueDate.day"',
'',
'15',
`--${boundary}`,
'Content-Disposition: form-data; name="totalAmount"',
'',
'1500.00',
`--${boundary}`,
'Content-Disposition: form-data; name="taxAmount"',
'',
'300.00',
`--${boundary}`,
'Content-Disposition: form-data; name="paidAmount"',
'',
'0',
`--${boundary}`,
'Content-Disposition: form-data; name="billingAddress.street"',
'',
'123 rue Principale',
`--${boundary}`,
'Content-Disposition: form-data; name="billingAddress.city"',
'',
'Paris',
`--${boundary}`,
'Content-Disposition: form-data; name="billingAddress.postalCode"',
'',
'75001',
`--${boundary}`,
'Content-Disposition: form-data; name="billingAddress.country"',
'',
'FR',
`--${boundary}`,
`Content-Disposition: form-data; name="file"; filename="${filename}"`,
'Content-Type: application/pdf',
'Content-Transfer-Encoding: base64',
'',
base64Content,
`--${boundary}--`,
];
const body = parts.join(CRLF);
const response = await fetch('[baseURL]/api/public/v1/invoices', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': `multipart/form-data; boundary=${boundary}`,
},
body: body,
});
Exemple Python (base64)
import base64
import requests
# Lire et encoder le fichier
with open('facture.pdf', 'rb') as f:
file_content = f.read()
base64_content = base64.b64encode(file_content).decode('ascii')
# Construire le corps multipart
boundary = '----PythonFormBoundary'
crlf = '\r\n'
body_parts = [
f'--{boundary}',
'Content-Disposition: form-data; name="accountId"',
'',
'ACCOUNT_ID',
f'--{boundary}',
'Content-Disposition: form-data; name="number"',
'',
'INV-2024-001',
# ... autres champs ...
f'--{boundary}',
'Content-Disposition: form-data; name="file"; filename="facture.pdf"',
'Content-Type: application/pdf',
'Content-Transfer-Encoding: base64',
'',
base64_content,
f'--{boundary}--',
]
body = crlf.join(body_parts)
response = requests.post(
'[baseURL]/api/public/v1/invoices',
headers={
'Authorization': f'Bearer {access_token}',
'Content-Type': f'multipart/form-data; boundary={boundary}',
},
data=body.encode('utf-8'),
)
Gestion des erreurs
| Erreur | Statut HTTP | Description |
|---|---|---|
| Aucun fichier envoyé | 400 | Le champ file manque dans la requête |
| Fichier trop volumineux | 400 | Le fichier dépasse 10 Mo une fois décodé |
| Type de fichier invalide | 400 | Type MIME absent de la liste autorisée |
| Multipart invalide | 400 | Requête multipart mal formée |
Exemple de réponse d’erreur :
{
"statusCode": 400,
"message": "File too large. Maximum size: 10485760 bytes",
"error": "Bad Request"
}
Bonnes pratiques
Choisir le bon encodage
- Préférez le binaire dès que possible, il est plus efficace
- N’utilisez la base64 que si votre plateforme ne sait pas envoyer de binaire
Valider côté client
Vérifiez la taille et le type avant d’envoyer, pour éviter des appels inutiles :
const MAX_SIZE = 10 * 1024 * 1024; // 10 Mo
const ALLOWED_TYPES = [
'application/pdf',
'image/png',
'image/jpeg',
'image/webp',
];
if (file.size > MAX_SIZE) {
throw new Error('Fichier trop volumineux');
}
if (!ALLOWED_TYPES.includes(file.type)) {
throw new Error('Type de fichier invalide');
}
Préférer le PDF
C’est le format recommandé pour un document de facture :
- il préserve la mise en forme d’un appareil à l’autre ;
- il permet l’extraction du texte ;
- il donne des fichiers plus légers sur les documents à dominante textuelle.
Pour aller plus loin
- Démarrage : les bases de l’API et l’authentification
- Référence de l’API : documentation complète des endpoints
Support
Une question sur l’envoi de fichiers ? Écrivez-nous via le formulaire de contact.