**Motivations:** - Align master with current codebase (token from projects/<id>/.secrets/<env>/ia_token) - Id resolution by mail To or by API token; no slug **Root causes:** - Token moved from conf.json to .secrets/<env>/ia_token; env from directory name **Correctifs:** - Server and scripts resolve project+env by scanning all projects and envs **Evolutions:** - tickets-fetch-inbox routes by To address; notary-ai agents and API doc updated **Pages affectées:** - ai_working_help/server.js, docs, project_config.py, lib/project_config.sh - projects/README.md, lecoffreio/docs/API.md, gitea-issues/tickets-fetch-inbox.py
58 KiB
APIs Externes - Documentation Complète
Auteur : Équipe 4NK Date : 2026-01-27 Version : 2.0
Référence unique (checks de déploiement) : docs/DEPLOYMENT.md#cartographie-des-checks-de-déploiement-source-unique
Ce document consolide toute la documentation relative aux APIs externes utilisées par LeCoffre.io :
- API d'ancrage Bitcoin Signet
- API Annuaire
- API agent IA notaire (ai_working_help)
API agent IA notaire (ai_working_help)
Vue d'ensemble
Les backends des applications métier appellent l'API ai_working_help (service agent IA notaire) pour les opérations ask, enqueue et getResponse. L'URL de base est configurée via NOTARY_AI_AGENT_URL sans slug (ex. http://192.168.1.173:3020/v1). Le projet (et l’env) sont identifiés par le token Bearer : l’API cherche dans tous les projets et tous les envs le fichier projects/<id>/.secrets/<env>/ia_token dont le contenu correspond au token. Le filtrage IP 192.168.1.* est géré côté API.
Authentification
Contrat obligatoire : tous les appels à l'API agent doivent envoyer le header :
Authorization: Bearer <token>
Le token est de la forme base + env (ex. nicolecoffreiotest, nicolecoffreiopprod). env est le nom d’environnement (test, pprod, prod), à adapter selon les environnements. La clé en base est NOTARY_AI_AGENT_TOKEN ; sa valeur doit correspondre à celle côté ia_dev dans projects/<id>/.secrets/<env>/ia_token pour l’env concerné. Le backend envoie ce header sur :
- POST
{NOTARY_AI_AGENT_URL}/ask— question synchrone (legacy) - POST
{NOTARY_AI_AGENT_URL}/enqueue— mise en file (spooler) - GET
{NOTARY_AI_AGENT_URL}/response/:request_uid— récupération de la réponse (poll après enqueue)
Implémentation côté backend : lecoffre-back-main/src/services/notary/NotaryFolderAIService/NotaryFolderAIService.ts (méthodes ask, enqueue, getResponse). Le header n'est ajouté que si NOTARY_AI_AGENT_TOKEN est renseigné ; si l'API exige l'authentification, la clé doit être définie dans .secrets/<env>/env-full et injectée en base lors du déploiement.
Consolidation des évolutions backend récentes
Cette section consolide les éléments issus des documents de travail intégrés intégrés dans la documentation API pérenne.
Intégration Genapi / iNot
- Endpoint backend ajouté pour l'envoi unitaire vers iNot :
POST /api/v1/notary/documents/:uid/push-inot. - Flux implémenté : OAuth token, recherche dossier par numéro, création eDocument, upload binaire.
- Contrôle métier côté backend : document validé requis avant push.
- Gestion d'indisponibilité normalisée avec code métier
GENAPI_API_UNAVAILABLE.
Standardisation des erreurs d'intégrations externes
- Normalisation des mappings d'indisponibilité
*_API_UNAVAILABLEsur plusieurs intégrations (GENAPI,ANNUAIRE,ANCHORAGE,IDNOT,OPENID,IPFS,MAILCHIMP,STRIPE). - Helpers partagés de propagation d'erreurs et de journalisation externe introduits pour homogénéiser les contrôleurs, helpers métier et services.
- Conventions de nommage d'opérations externes unifiées (
integration.operation) pour la traçabilité.
Correctifs de robustesse API
- Correctif d'interop CJS/ESM sur l'hydratation des ressources ID.Not (
User.hydrate is not a function) via normalisation des constructeurs hydratables. - Correctif de fusion PDF batch : normalisation des noms de fichiers multipart pour éviter les erreurs d'encodage WinAnsi pendant la génération de PDF fusionnés.
API d'Ancrage Bitcoin Signet
Vue d'ensemble
L'API d'ancrage Bitcoin Signet est un service autonome qui gère l'ancrage de hashes de documents sur la blockchain Bitcoin Signet. Elle est séparée du backend principal pour :
- Isolation : Le nœud Bitcoin et l'API d'ancrage tournent sur un serveur dédié
- Performance : Évite la surcharge du backend principal avec des opérations blockchain lentes
- Sécurité : Accès restreint par API key
- Scalabilité : Peut gérer une file d'attente d'ancrages indépendamment
Architecture
lecoffre-back-main (prod.lecoffreio.4nkweb.com:3009)
│
├─── HTTP POST ───> lecoffre-anchor-api (anchorage.certificator.4nkweb.com:3004)
│ │
│ ├─── JSON-RPC ───> Bitcoin Node (localhost:38332)
│ └─── Callback ───> lecoffre-back-main
│
└─── Continue processing...
Infrastructure
Serveur et déploiement
- Serveur : Serveur bitcoin (où le nœud Bitcoin Signet est déployé)
- Répertoire :
/home/ncantu/Bureau/code/bitcoin/api-anchorage - URL publique :
https://anchorage.certificator.4nkweb.com - Port interne :
3004 - Service systemd :
anchor-api.serviceouapi-anchorage.service(user service)
Proxy Nginx
Le proxy 192.168.1.100 route toutes les requêtes vers anchorage.certificator.4nkweb.com vers le serveur bitcoin (port 3004).
Configuration : /etc/nginx/sites-enabled/certificator.4nkweb.com
Configuration
Variables d'environnement
Le fichier .env est situé dans /home/ncantu/Bureau/code/bitcoin/api-anchorage/.env :
PORT=3004
NODE_ENV=production
BITCOIN_RPC_HOST=127.0.0.1
BITCOIN_RPC_PORT=18443
BITCOIN_COOKIE_PATH=/home/ncantu/.bitcoin/signet/.cookie
# Note: Utiliser 127.0.0.1 au lieu de localhost pour forcer IPv4
Service systemd
Fichier : /home/ncantu/.config/systemd/user/anchor-api.service
[Unit]
Description=Anchor API Service - anchorage.certificator.4nkweb.com:3004
After=network-online.target signet.service
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/home/ncantu/Bureau/code/bitcoin/api-anchorage
EnvironmentFile=/home/ncantu/Bureau/code/bitcoin/api-anchorage/.env
ExecStart=/bin/bash -c 'export PORT=3004; export NVM_DIR="/home/ncantu/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; cd /home/ncantu/Bureau/code/bitcoin/api-anchorage && npm run dev'
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=anchor-api
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=default.target
Nœud Bitcoin
- Service :
signet.service(user service) - Port RPC :
18443(localhost, Signet) - Port P2P :
38333(exposé via NAT) - Cookie :
/home/ncantu/.bitcoin/signet/.cookie - Configuration :
/srv/4NK/signet.4nkweb.com/bitcoin/bitcoin.conf
Fonctionnement
L'API d'ancrage fonctionne de manière synchrone :
- Le traitement Bitcoin se fait avant la réponse HTTP
- L'API retourne directement le
txidavec toutes les informations - Code HTTP 200 (OK) au lieu de 202 (Accepted)
- Pas de UUID, pas de status "pending", pas besoin de status endpoint
Analyse du fonctionnement réel
Code réel de l'API :
// AnchorController.anchorDocument()
// Ancrer directement sur Bitcoin (synchrone)
const txid = await this.bitcoinService.anchorHash(hash);
// Récupérer le statut de la transaction
const txStatus = await this.bitcoinService.getTransactionStatus(txid);
const response: IAnchorResponse = {
txid,
confirmations: txStatus.confirmations,
block_height: txStatus.block_height,
status: 'confirmed',
};
res.status(200).json(response); // Retourne directement { txid: "...", status: "confirmed", ... }
Fonctionnement :
- L'API attend que le traitement Bitcoin soit terminé (
await) - Elle retourne directement le
txidavec toutes les informations (confirmations, block_height) - Code HTTP 200 (OK) au lieu de 202 (Accepted)
- Pas de UUID, pas de status "pending"
Problème initial résolu : Le backend s'attendait à recevoir un UUID avec status: "pending" et HTTP 202, mais l'API fonctionne de manière synchrone et retourne directement le txid avec HTTP 200. Le backend a été aligné sur le fonctionnement réel de l'API.
Endpoints API
Health Check
GET /health
Réponse :
{
"ok": true,
"service": "anchor-api",
"bitcoin": {
"connected": true,
"blocks": 198769
},
"timestamp": "2026-01-23T11:03:33.668Z"
}
Ancrage de document
POST /api/anchor/document
Content-Type: application/json
x-api-key: <API_KEY>
{
"documentUid": "uuid-du-document",
"hash": "hash-sha256-du-fichier-filigrane-64-hex",
"callback_url": "https://prod.lecoffreio.4nkweb.com/api/v1/public/anchors/callback/document" (optionnel, non utilisé)
}
Réponse (200 OK) :
{
"txid": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85",
"status": "confirmed",
"confirmations": 0,
"block_height": 142062
}
Important :
- L'API fonctionne de manière synchrone : le traitement Bitcoin se fait avant la réponse
- Le
txidest retourné directement dans la réponse (pas d'UUID) - Le statut est toujours
"confirmed"(pas de "pending") - Toutes les informations sont disponibles immédiatement (confirmations, block_height)
- Le
callback_urlest optionnel et appelé après la réponse si fourni
Vérification d'ancrage
POST /api/anchor/verify
Content-Type: application/json
x-api-key: <API_KEY>
{
"hash": "hash-sha256-du-fichier-64-hex",
"txid": "txid-bitcoin-64-hex" (optionnel)
}
Réponse (200 OK) si ancré :
{
"verified": true,
"anchor_info": {
"transaction_id": "txid-bitcoin-64-hex",
"block_height": 198775,
"confirmations": 6,
"timestamp": "2026-01-23T11:03:33.668Z"
}
}
Réponse (200 OK) si non ancré :
{
"verified": false
}
Flux de traitement
1. Demande d'ancrage (synchrone)
-
Le backend appelle
POST /api/anchor/documentavec le hash du fichier filigrané -
L'API ancre directement le hash sur Bitcoin Signet via JSON-RPC (attente synchrone)
-
L'API retourne
200 OKavec toutes les informations :{ "txid": "56504e002d95301ebcfb4b30eaedc5d3fd9a448e121ffdce4f356b8d34169e85", "status": "confirmed", "confirmations": 0, "block_height": 142062 }
2. Callback (optionnel)
Si callback_url a été fourni lors de la demande d'ancrage :
-
L'API appelle
POST {callback_url}avec le résultat (après la réponse HTTP) :{ "document_uid": "uuid-du-document", "status": "confirmed", "txid": "txid-bitcoin-64-hex", "confirmations": 0, "block_height": 142062 } -
Le backend reçoit le callback et peut mettre à jour l'ancrage dans la base de données (optionnel)
Types TypeScript
Interfaces de l'API
interface IAnchorRequest {
documentUid: string;
hash: string; // 64 hex characters
callback_url?: string;
}
interface IAnchorResponse {
txid: string; // Hash Bitcoin de la transaction (64 hex) - retourné directement
status: 'confirmed'; // Toujours 'confirmed' (pas de 'pending')
confirmations: number; // Nombre de confirmations (0 si dans le mempool)
block_height?: number; // Hauteur du bloc si confirmé
}
interface IVerifyRequest {
hash: string; // 64 hex characters
txid?: string; // Optionnel pour vérifier un txid spécifique
}
interface IVerifyResponse {
verified: boolean;
anchor_info?: {
transaction_id: string;
block_height: number;
confirmations: number;
timestamp: string;
};
}
Utilisation dans le backend
Le backend utilise BitcoinSignetApiHelper pour interagir avec l'API :
// 1. Ancrer un hash (synchrone)
const result = await bitcoinSignetApiHelper.anchorHashViaApi(hash, {
resourceUid: documentUid,
resourceType: "document"
});
// result.txid est directement disponible (pas d'UUID)
// result.status est toujours 'confirmed'
// result.confirmations et result.block_height sont disponibles
Important :
- L'API fonctionne de manière synchrone : le
txidest retourné directement - Le code HTTP est 200 OK (pas 202 Accepted)
- Toutes les informations sont disponibles immédiatement
- Pas besoin de status endpoint ou de polling
Code source
Le code source de l'API d'ancrage se trouve dans la branche v2-remote du dépôt v1 :
- Dépôt :
https://git.4nkweb.com/4nk/lecoffreio - Branche :
v2-remote - Répertoire :
lecoffre-anchor-api/
Structure
lecoffre-anchor-api/
├── src/
│ ├── config/
│ │ ├── bitcoin.config.ts
│ │ └── logger.ts
│ ├── controllers/
│ │ └── AnchorController.ts
│ ├── services/
│ │ ├── BitcoinService.ts
│ │ └── AnchorQueueService.ts
│ └── index.ts
├── package.json
├── tsconfig.json
└── .env
Installation et déploiement
Clonage du code
cd /srv/4NK/certificator.4nkweb.com
git clone -b v2-remote --single-branch --depth 1 git@git.4nkweb.com:4nk/lecoffreio.git temp-anchor-api
mv temp-anchor-api/lecoffre-anchor-api .
rm -rf temp-anchor-api
Installation des dépendances
cd /srv/4NK/certificator.4nkweb.com/lecoffre-anchor-api
source /home/ncantu/.nvm/nvm.sh
npm install
Configuration
- Créer le fichier
.envavec les variables d'environnement - Vérifier que le cookie Bitcoin existe et est accessible
- Mettre à jour le service systemd pour pointer vers le bon répertoire
Démarrage du service
systemctl --user daemon-reload
systemctl --user start anchor-api.service
systemctl --user enable anchor-api.service
Vérification
Vérifier le statut du service
systemctl --user status anchor-api.service
Vérifier les logs
journalctl --user -u anchor-api.service -f
Tester l'API depuis l'extérieur
Test simple avec curl :
curl -k https://anchorage.certificator.4nkweb.com/health
Réponse attendue :
{
"ok": true,
"service": "anchor-api",
"bitcoin": {
"connected": true,
"blocks": 152321
}
}
Exemple de client Node.js :
Un exemple complet de client pour tester l'API :
#!/usr/bin/env node
/**
* Exemple basique de client pour l'API Anchor
* Usage: node example-client.js
*/
const https = require('https');
const crypto = require('crypto');
// Configuration
const API_URL = 'https://anchorage.certificator.4nkweb.com';
const API_KEY = 'your-api-key-here'; // Remplacez par votre clé API
/**
* Effectue une requête HTTPS
*/
function makeRequest(method, path, data = null) {
return new Promise((resolve, reject) => {
const url = new URL(path, API_URL);
const options = {
hostname: url.hostname,
port: url.port || 443,
path: url.pathname,
method: method,
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY,
},
};
const req = https.request(options, (res) => {
let body = '';
res.on('data', (chunk) => {
body += chunk;
});
res.on('end', () => {
try {
const json = JSON.parse(body);
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(json);
} else {
reject(new Error(`HTTP ${res.statusCode}: ${json.error || body}`));
}
} catch (e) {
reject(new Error(`Parse error: ${e.message}`));
}
});
});
req.on('error', (error) => {
reject(error);
});
if (data) {
req.write(JSON.stringify(data));
}
req.end();
});
}
/**
* Vérifie l'état de l'API
*/
async function checkHealth() {
try {
console.log('🔍 Vérification de l\'état de l\'API...');
const response = await makeRequest('GET', '/health');
console.log('✅ API opérationnelle');
console.log(` Bitcoin connecté: ${response.bitcoin.connected ? 'Oui' : 'Non'}`);
console.log(` Blocs: ${response.bitcoin.blocks}`);
return response;
} catch (error) {
console.error('❌ Erreur:', error.message);
throw error;
}
}
/**
* Ancre un document sur Bitcoin Signet
*/
async function anchorDocument(documentUid, hash, callbackUrl = null) {
try {
console.log(`\n📎 Ancrage du document: ${documentUid}`);
console.log(` Hash: ${hash.substring(0, 16)}...`);
const data = {
documentUid,
hash,
};
if (callbackUrl) {
data.callback_url = callbackUrl;
}
const response = await makeRequest('POST', '/api/anchor/document', data);
console.log('✅ Document ancré avec succès');
console.log(` Transaction ID: ${response.txid}`);
console.log(` Confirmations: ${response.confirmations}`);
if (response.block_height) {
console.log(` Block height: ${response.block_height}`);
}
return response;
} catch (error) {
console.error('❌ Erreur lors de l\'ancrage:', error.message);
throw error;
}
}
/**
* Vérifie si un hash est ancré
*/
async function verifyAnchor(hash, txid = null) {
try {
console.log(`\n🔍 Vérification de l'ancrage...`);
console.log(` Hash: ${hash.substring(0, 16)}...`);
const data = { hash };
if (txid) {
data.txid = txid;
console.log(` Transaction ID: ${txid.substring(0, 16)}...`);
}
const response = await makeRequest('POST', '/api/anchor/verify', data);
if (response.verified) {
console.log('✅ Hash vérifié et ancré');
if (response.anchor_info) {
console.log(` Transaction ID: ${response.anchor_info.transaction_id}`);
console.log(` Block height: ${response.anchor_info.block_height}`);
console.log(` Confirmations: ${response.anchor_info.confirmations}`);
}
} else {
console.log('❌ Hash non trouvé dans la blockchain');
}
return response;
} catch (error) {
console.error('❌ Erreur lors de la vérification:', error.message);
throw error;
}
}
/**
* Fonction principale
*/
async function main() {
console.log('=== Client API Anchor ===\n');
try {
// 1. Vérifier l'état de l'API
await checkHealth();
// 2. Générer un hash de test
const documentContent = `Document de test - ${new Date().toISOString()}`;
const hash = crypto.createHash('sha256').update(documentContent).digest('hex');
const documentUid = `doc-${Date.now()}`;
console.log(`\n📄 Document de test:`);
console.log(` UID: ${documentUid}`);
console.log(` Hash: ${hash}`);
// 3. Ancrer le document
const anchorResult = await anchorDocument(documentUid, hash);
// 4. Attendre un peu pour que la transaction soit propagée
console.log('\n⏳ Attente de 3 secondes pour la propagation...');
await new Promise((resolve) => setTimeout(resolve, 3000));
// 5. Vérifier l'ancrage
await verifyAnchor(hash, anchorResult.txid);
console.log('\n✅ Tous les tests sont passés avec succès!');
} catch (error) {
console.error('\n❌ Erreur:', error.message);
process.exit(1);
}
}
// Exécuter le script
if (require.main === module) {
main();
}
// Exports pour utilisation comme module
module.exports = {
checkHealth,
anchorDocument,
verifyAnchor,
};
Utilisation :
# Modifier API_KEY dans le script
node example-client.js
Points importants :
- L'API retourne directement le
txiddans la réponse (fonctionnement synchrone) - Le code HTTP est 200 OK (pas 202 Accepted)
- Toutes les informations sont disponibles immédiatement (confirmations, block_height)
Bonnes pratiques
Utilisation de l'API
L'API fonctionne de manière synchrone et retourne directement toutes les informations :
const result = await bitcoinSignetApiHelper.anchorHashViaApi(hash, {
resourceUid: documentUid,
resourceType: "document",
});
// result.txid est directement disponible
// result.status est "confirmed"
// result.confirmations et result.block_height sont disponibles
Avantages :
- Pas besoin de status endpoint ou de polling
- Toutes les informations sont disponibles immédiatement
- Comportement prévisible et fiable
Gestion des erreurs
Erreurs bloquantes : Si l'API retourne une erreur (HTTP 500), c'est un problème bloquant :
try {
const result = await bitcoinSignetApiHelper.anchorHashViaApi(hash, options);
// Le txid est obligatoire et doit être présent
if (!result.txid) {
throw new Error("Txid manquant (BLOQUANT)");
}
} catch (error) {
// Toute erreur est bloquante, le document ne peut pas être créé
throw error;
}
Callback (optionnel)
Le callback_url est optionnel et appelé après la réponse HTTP. Il peut être utilisé pour :
- Mise à jour asynchrone de la base de données
- Notification d'autres services
- Logging supplémentaire
Note : Le callback n'est pas nécessaire car toutes les informations sont déjà dans la réponse initiale.
Dépannage
Le service ne démarre pas
- Vérifier que le répertoire existe :
/srv/4NK/certificator.4nkweb.com/lecoffre-anchor-api - Vérifier que les dépendances sont installées :
npm install - Vérifier les logs :
journalctl --user -u anchor-api.service -n 50
Erreur "Bitcoin node not available"
- Vérifier que le nœud Bitcoin est actif :
systemctl --user status signet.service - Vérifier que le cookie existe :
ls -la /home/ncantu/.bitcoin/signet/.cookie - Vérifier que le port RPC est accessible :
ss -tlnp | grep 38332 - Vérifier la configuration dans
.env:BITCOIN_COOKIE_PATHdoit pointer vers le bon chemin
Erreur IPv6 "connect EADDRNOTAVAIL ::1:38332"
Date : 2026-01-25
Problème : L'API d'ancrage essaie de se connecter au nœud Bitcoin via IPv6 (::1:38332) mais le nœud n'écoute que sur IPv4.
Solution : Forcer IPv4 dans la configuration de l'API d'ancrage :
Fichier : /home/ncantu/Bureau/code/bitcoin/api-anchorage/.env
# Utiliser explicitement IPv4
BITCOIN_RPC_HOST=127.0.0.1
BITCOIN_ZMQ_HOST=127.0.0.1
BITCOIN_ZMQ_PORT=38332
Vérification : Vérifier que le nœud Bitcoin écoute sur IPv4 :
ss -tlnp | grep 38332
ss -tlnp | grep 18443
Erreur "too-long-mempool-chain"
Date : 2026-01-25
Problème : Le mempool Bitcoin Signet est saturé avec plus de 25 transactions non confirmées en chaîne (limite de sécurité Bitcoin).
Solution : Le script détecte cette erreur et s'arrête immédiatement avec un message clair. L'utilisateur doit attendre que les transactions soient confirmées avant de relancer.
Vérification :
bitcoin-cli -signet getmempoolinfo
bitcoin-cli -signet getrawmempool
Erreurs de timeout (504, ESOCKETTIMEDOUT, client timeout)
Date : 2026-01-26 à 2026-01-28
Problèmes :
- 504 Gateway Timeout : Timeout du proxy Nginx (probablement
proxy_read_timeouttrop court) - ESOCKETTIMEDOUT : Timeout socket lors de la connexion au nœud Bitcoin
- Client timeout (AbortController) : Timeout client de 5 minutes dépassé
Solutions appliquées :
- Détection des timeouts : Tous les timeouts (504, 500 avec timeout socket, client timeout) sont détectés comme retryable
- Retry automatique : 3 tentatives avec délai de 30 secondes entre chaque tentative
- Timeout client : Timeout explicite de 5 minutes avec
AbortController(aligné avecproxy_read_timeoutNginx) - Logs améliorés : Logs détaillés pour chaque tentative, timeout, retry et succès
Configuration Nginx recommandée :
location / {
proxy_pass http://192.168.1.103:3004;
proxy_connect_timeout 60s;
proxy_send_timeout 600s; # 10 minutes
proxy_read_timeout 600s; # 10 minutes
}
Logs à surveiller :
📡 [BitcoinSignetService] Anchor API - Tentative X/3 pour hash ...⏱️ [BitcoinSignetService] Anchor API - Timeout client déclenché après 300000ms⚠️ [BitcoinSignetService] Anchor API timeout (504/500/client) - Tentative X/3, attente 30000ms avant retry...✅ [BitcoinSignetService] Anchor API - Ancrage réussi pour hash ...
Erreur "Cannot find module './config/logger'"
Le fichier src/config/logger.ts doit être créé manuellement :
const logger = {
info: (message: string) => console.log('[INFO] ' + new Date().toISOString() + ' - ' + message),
error: (message: string) => console.error('[ERROR] ' + new Date().toISOString() + ' - ' + message),
warn: (message: string) => console.warn('[WARN] ' + new Date().toISOString() + ' - ' + message),
debug: (message: string) => console.debug('[DEBUG] ' + new Date().toISOString() + ' - ' + message),
};
export default logger;
Erreur "Anchor API returned invalid txid"
Cause : Le code s'attend à recevoir un txid Bitcoin immédiatement, mais l'API retourne un UUID (job ID).
Solution : Le code a été corrigé pour accepter un UUID et récupérer le txid via le status endpoint ou le callback.
Erreur "Anchor API transaction not ready yet"
Cause : La transaction est en cours de traitement (status "pending" sans txid).
Solution : Attendre le callback. Ne pas relancer l'ancrage. Le callback mettra à jour le txid automatiquement.
Transactions non traitées
Cause : La file d'attente est en mémoire. Si le service redémarre, les transactions en cours sont perdues.
Solution actuelle : Les transactions sont recréées automatiquement lors du traitement des fichiers.
Amélioration future : Migrer vers Redis ou une base de données pour la persistance.
Configuration dans le backend
L'URL de l'API d'ancrage est configurée dans la base de données via system_configuration :
-
Clé :
ANCHORE_API_URL -
Valeur :
https://anchorage.certificator.4nkweb.com -
Catégorie :
INTEGRATION_BITCOIN -
Clé :
ANCHORE_API_KEY -
Valeur :
<API_KEY> -
Catégorie :
INTEGRATION_BITCOIN -
Sensible :
true
Historique
2026-01-23 : Alignement du backend sur le fonctionnement synchrone de l'API
Problème : Le backend s'attendait à recevoir un UUID avec status "pending" et HTTP 202, mais l'API fonctionne de manière synchrone et retourne directement le txid avec HTTP 200.
Corrections :
- Alignement du backend sur le fonctionnement réel de l'API (synchrone)
- Attente de HTTP 200 (OK) au lieu de HTTP 202 (Accepted)
- Récupération directe du
txiddans la réponse initiale - Suppression de la méthode
getStatusdevenue inutile - Suppression de toute logique de polling ou d'attente
- Documentation mise à jour pour refléter le fonctionnement synchrone
Résultat : Le backend reçoit directement toutes les informations (txid, confirmations, block_height) dans la réponse initiale, sans besoin de status endpoint ou de polling.
2026-01-23 : Restauration de l'API d'ancrage
Problème : L'API d'ancrage était inactive et le service systemd pointait vers un répertoire inexistant (/home/ank/dev/lecoffre-anchor-api).
Solution :
- Clonage du code depuis la branche
v2-remotedu dépôt v1 dans/srv/4NK/certificator.4nkweb.com/lecoffre-anchor-api - Installation des dépendances npm
- Création du fichier
logger.tsmanquant - Création du fichier
.envavec la configuration Bitcoin - Mise à jour du service systemd pour pointer vers le nouveau répertoire
- Correction du chemin du cookie Bitcoin (
/home/ncantu/.bitcoin/signet/.cookie)
Résultat : L'API d'ancrage est opérationnelle et connectée au nœud Bitcoin Signet.
2026-01-27 : Mise à jour URL API d'ancrage
Problème : L'URL de l'API d'ancrage a été changée de https://prod.signet.4nkweb.com vers https://anchorage.certificator.4nkweb.com.
Solution :
- Mise à jour de
ANCHORE_API_URLdansenv-full-<env>-for-bdd-injection.txt - Mise à jour dans
system_configurationviasetSettings - Port interne :
3004(configuré dans.envet systemd) - Nginx : Route
anchorage.certificator.4nkweb.com→192.168.1.103:3004
Vérification :
cd lecoffre-back-main
npm run anchorage:test-anchor-api
Problème 502 Bad Gateway : Si vous rencontrez des erreurs 502 Bad Gateway :
- Vérifier l'état du service :
systemctl --user status anchor-api.service - Vérifier que le service écoute sur le port 3004 :
ss -tlnp | grep 3004 - Tester depuis le backend :
curl -s http://localhost:3004/health - Configuration Nginx : Route
anchorage.certificator.4nkweb.com→192.168.1.103:3004
API Annuaire
Vue d'Ensemble
L'API Annuaire est appelée avec une authentification Basic Auth (login/password) distincte de l'API IdNot existante (clé API). Les recherches utilisent des appels API au moment de la demande (pas de tables annu_*).
Configuration
Variables d'environnement (via system_configuration) :
ANNUAIRE_API_LOGIN: Login Basic AuthANNUAIRE_API_PASSWORD: Password Basic Auth (sensible)ANNUAIRE_API_BASE_URL: URL de base de l'API Annuaire
Lecture directe depuis la BDD : les helpers de recherche (IdNot/Annuaire) lisent ces configurations depuis system_configuration (via ConfigLoader), sans passer par .env.
Préparation configuration Genapi (push iNot)
Variables prévues pour injection depuis env-full-<env>-for-bdd-injection.txt vers system_configuration :
GENAPI_AUTH_URL: URL OAuth token Genapi (.../Authorization/OAuth/Token)GENAPI_API_BASE_URL: URL de base API Genapi (.../actes)GENAPI_TENANT_ID: Tenant Genapi (headertenantId)GENAPI_CLIENT_ID: Client ID OAuth GenapiGENAPI_CLIENT_SECRET: Client secret OAuth Genapi (sensible)GENAPI_SCOPE: Scopes OAuth Genapi (ex:sas:read sas:write)
Backend endpoint for Genapi push
POST /api/v1/notary/documents/:uid/push-inot
Execution flow:
- Validate document status (
VALIDATEDrequired). - Resolve dossier in Genapi by folder
numerousing/api/v1/Dossiers/Search. - Create an eDocument in Genapi (
/api/v1/Clients/{idParent}/Edocumentor/api/v1/Partenaires/{idParent}/Edocument). - Upload binary payload to
/api/v1/Documents/{id}/Data.
Unavailable response:
- If Genapi endpoint is unreachable, backend returns
GENAPI_API_UNAVAILABLE. - Frontend displays a dedicated message for this case ("API Genapi indisponible").
Backend mutualization:
- Shared helper for same auth protocol integrations:
TenantOAuthHttpClient - Scope: external backend calls using OAuth client-credentials +
tenantIdheader. - Frontend unavailable error mappings are centralized in
front/Utils/errorMessages/constants/externalServiceErrorPatterns.ts. - External integration registry is centralized in backend:
lecoffre-back-main/src/common/utils/externalServiceErrors.ts. - Standardized backend unavailable codes applied to multiple integrations:
GENAPI_API_UNAVAILABLEANNUAIRE_API_UNAVAILABLEANCHORAGE_API_UNAVAILABLEIDNOT_API_UNAVAILABLEOPENID_API_UNAVAILABLEIPFS_API_UNAVAILABLEMAILCHIMP_API_UNAVAILABLE
- Common controller mapper for integration errors:
lecoffre-back-main/src/common/utils/controllerExternalErrorHelper.ts- Shared integration->HTTP mapping table moved to:
lecoffre-back-main/src/common/utils/externalIntegrationHttpMappings.ts
- Variant wrapper available:
handleControllerErrorWithExternalIntegration(...)
- Domain factory utility available:
getExternalIntegrationMappings("mailchimp" | "idnot" | "ipfs" | "openid" | "anchorage" | "genapi" | "stripe" | "bitcoin_signet" | "idnot_annuaire" | "all")
- Subset mapping utilities available:
EXTERNAL_INTEGRATION_HTTP_MAPPINGS_MAILCHIMP_ONLYEXTERNAL_INTEGRATION_HTTP_MAPPINGS_IDNOT_ONLYEXTERNAL_INTEGRATION_HTTP_MAPPINGS_IPFS_ONLYEXTERNAL_INTEGRATION_HTTP_MAPPINGS_OPENID_ONLYEXTERNAL_INTEGRATION_HTTP_MAPPINGS_ANCHORAGE_ONLYEXTERNAL_INTEGRATION_HTTP_MAPPINGS_GENAPI_ONLYEXTERNAL_INTEGRATION_HTTP_MAPPINGS_STRIPE_ONLYEXTERNAL_INTEGRATION_HTTP_MAPPINGS_BITCOIN_SIGNET_ONLYEXTERNAL_INTEGRATION_HTTP_MAPPINGS_IDNOT_ANNUAIRE
- Mapping response messages are now derived from
buildExternalServiceUnavailableCode(...)to keep integration error messages homogeneous. - Already wired in:
notary/DocumentsControlleridnot/UserControlleridnot/OfficeControllernotary/MailchimpControllerpublic/IncidentReportControllernotary/FolderThirdPartiesController(resend-code flow)super-admin/HealthTestsControlleradmin/SyncControllerpublic/ThirdPartyAuthController(request-code)notary/FolderSharingController(searchNotaries)notary/DocumentAnchorsController(anchorDocument,anchorFolderDocuments,verifyPendingAnchors)notary/DocumentsController(push-inot,resend-request,resend-request-batch)notary/FolderThirdPartiesController(resend-codewithmailchimpdomain mapping)idnot/UserControlleridnot/OfficeController
- Helper-level fallback utility available for non-controller flows:
handleErrorWithExternalIntegrationFallback(...)
- Service-level propagation utility available for business layers without HTTP response object:
toExternalServiceUnavailableError(...)isExternalServiceUnavailableError(...)buildExternalOperationName(integration, operation)(integration.operation)wrapExternalCall(integration, operation, fn)wrapExternalCallWithLog(integration, operation, fn, logError)createExternalIntegrationCallLogger(integrationLabel, context, level?, extraMetadata?)buildStandardExternalLogMetadata({ targetEmail?, subscriptionId?, requestPath?, resourceUid? })mergeExternalLogMetadata(...metadataItems)buildStripeSubscriptionMetadata({ requestPath, subscriptionType?, frequency?, subscriptionId? })buildIdNotRequestMetadata({ targetEmail?, requestPath })buildMailchimpRequestMetadata({ targetEmail, requestPath })buildOpenIdRequestMetadata({ requestPath, resourceUid? })buildIpfsReadMetadata({ requestPath, resourceUid? })buildIpfsWriteMetadata({ requestPath, resourceUid? })buildOpenIdTokenMetadata({ requestPath, resourceUid? })buildOpenIdJwksMetadata({ requestPath, resourceUid? })buildOpenIdUserInfoMetadata({ requestPath, resourceUid? })buildOpenIdLogoutMetadata({ requestPath, resourceUid? })buildIpfsPinMetadata({ requestPath, resourceUid? })buildIpfsUnpinMetadata({ requestPath, resourceUid? })buildIpfsGatewayReadMetadata({ requestPath, resourceUid? })buildAnchorageRequestMetadata({ requestPath, resourceUid? })buildGenapiRequestMetadata({ requestPath, resourceUid? })createIpfsGatewayReadLogger(context, requestPath)createIpfsGatewayReadLoggerFromPath(context, pathOrUrl)createIpfsPinLogger(context, requestPath, resourceUid?)createIpfsPinLoggerFromFileName(context, fileName, requestPath?)createIpfsUnpinLogger(context, requestPath, resourceUid?)createIpfsUnpinLoggerFromHash(context, hashToUnpin)createOpenIdUserInfoLogger(context, requestPath, resourceUid?)createOpenIdLogoutLogger(context, requestPath, resourceUid?)createOpenIdTokenLogger(context, requestPath, resourceUid?)createOpenIdJwksLogger(context, requestPath, resourceUid?)createOpenIdUserInfoLoggerFromPath(context, pathOrUrl, resourceUid?)createOpenIdLogoutLoggerFromPath(context, pathOrUrl, resourceUid?)createOpenIdTokenLoggerFromPath(context, pathOrUrl, resourceUid?)createOpenIdJwksLoggerFromPath(context, pathOrUrl, resourceUid?)createIpfsExternalCallLogger(context)createMailchimpRequestLoggerFromPath(context, pathOrUrl, targetEmail)createIdNotRequestLoggerFromPath(context, pathOrUrl, targetEmail?, level?)createStripeSubscriptionLogger(context, { requestPath, subscriptionType?, frequency?, subscriptionId?, level? })createStripeSubscriptionLoggerFromPath(context, { pathOrUrl, subscriptionType?, frequency?, subscriptionId?, level? })createAnchorageRequestLogger(context, requestPath, resourceUid?, level?)createAnchorageRequestLoggerFromPath(context, pathOrUrl, resourceUid?, level?)createGenapiRequestLogger(context, requestPath, resourceUid?, level?)createGenapiRequestLoggerFromPath(context, pathOrUrl, resourceUid?, level?)createMailchimpExternalCallLogger(context, extraMetadata?)createIdNotExternalCallLogger(context, level?, extraMetadata?)createStripeExternalCallLogger(context, level?, extraMetadata?)createAnchorageExternalCallLogger(context, level?, extraMetadata?)createGenapiExternalCallLogger(context, level?, extraMetadata?)lecoffre-back-main/src/common/utils/externalServiceErrorPropagation.tslecoffre-back-main/src/common/utils/externalServiceCallLoggers.ts
- Helper-level fallback already wired in:
notary/helpers/FolderThirdPartiesResendCodeHelper.tspublic/helpers/ThirdPartyAuthHelper.tsnotary/helpers/FolderSharingResendHelper.ts
- Service-level propagation already wired in:
services/notary/DocumentsService/DocumentsService.tsservices/notary/OfficesService/OfficesRibService.tsservices/common/FileProcessingService/helpers/FileProcessingStepHelper.tsservices/common/BitcoinSignetService/BitcoinSignetService.tsservices/common/MailchimpService/MailchimpService.tsservices/notary/OfficesService/helpers/OfficesRibProcessingHelper.tsservices/common/FileProcessingService/helpers/FileProcessingViewHelper.ts
wrapExternalCall(...)usage extended to secondary flows where failure is explicitly external:- IPFS read/view/download helpers
- Mailchimp list subscription API call
wrapExternalCallWithLog(...)usage extended where log+rethrow was duplicated:- Bitcoin RPC anchorage flows
- Secondary Mailchimp resend flows
- Secondary IPFS/watermark processing flows
- Secondary IPFS view/download/watermark read helper flows
- Secondary RIB IPFS low-level download flow
- Mailchimp list subscription flow (
addToMailchimpList) - Annuaire/IdNot secondary email-search API flow (
searchByEmail) - Stripe checkout-session creation flow (
createCheckoutSession)
- Standardized metadata keys used in external log payloads:
targetEmailsubscriptionIdrequestPathresourceUid
- Inline callback metadata objects replaced by dedicated metadata builders on active IDNOT/MAILCHIMP/STRIPE wrappers.
- Existing IPFS gateway-read wrappers aligned with
buildIpfsGatewayReadMetadata(...). - Existing IPFS upload wrappers aligned with
buildIpfsPinMetadata(...). - Remaining OPENID/IPFS wrapper logger lambdas replaced by dedicated logger wrappers.
- Existing IPFS unpin flow migrated to wrapper + dedicated unpin logger (
IpfsService.unpinFile). - Existing IPFS gateway-read logger usage migrated to
createIpfsGatewayReadLoggerFromPath(...)for URL/path normalization. - Existing IPFS unpin logger usage migrated to
createIpfsUnpinLoggerFromHash(...)for hash-only caller contract. - Existing IPFS pin logger usage migrated to
createIpfsPinLoggerFromFileName(...)for default pin endpoint encapsulation. - OPENID userinfo/logout flows are now available in
OpenIdServicewith dedicated logger wrappers. - Existing OPENID logger usage migrated to
fromPathwrappers for normalized endpoint metadata. OpenIdServiceimplementscommon/system/OpenIdInterface.ts(getOpenIdConfig,getSigningKey,verifyIdToken,getUserInfo,notifyLogout).- Existing OPENID calls aligned with dedicated metadata builders for discovery/token and JWKS endpoints.
- Existing Mailchimp and Annuaire/IdNot flows aligned with
fromPathwrappers for endpoint normalization:services/common/MailchimpService/MailchimpService.ts(addToMailchimpList)services/common/IdNotDirectoryService/IdNotDirectoryService.ts(searchByEmail)
- Existing Stripe checkout-session flow aligned with
createStripeSubscriptionLoggerFromPath(...)for endpoint normalization:services/common/StripeService/StripeService.ts(createCheckoutSession)
- Existing Genapi HTTP paths aligned with
createGenapiRequestLoggerFromPath(...):services/common/GenapiService/GenapiService.ts(findDossierIdByFolderNumber,createEdocument,uploadDocumentData)
- Existing Anchorage verify endpoint aligned with
createAnchorageRequestLoggerFromPath(...):services/common/BitcoinSignetService/helpers/BitcoinSignetApiHelper.ts(verifyHashViaApi)
Endpoints API
Offices : GET /api/pp/v2/entites?type=office&page={page}&size={size}&key={IDNOT_API_KEY}
Personnes : GET /api/pp/v2/personnes?page={page}&size={size}&key={IDNOT_API_KEY}
Rattachements : GET /api/pp/v2/personnes/{idnot}/rattachements?page={page}&size=100&key={IDNOT_API_KEY}
Notes :
- Auth : la clé API se passe en query (puis fallback header x-api-key)
- Pagination : utiliser page>=1 (ou 0 selon endpoint) et size=100
- Erreurs : 403 accessDenied → essayer fallback /entites sans type et filtrer côté client
Tables
Les tables historiques annu_* ne sont plus utilisées par l’application.
Synchronisation
Les synchronisations batch (IdNot/Annuaire) ne sont plus exécutées via des scripts dédiés. La recherche et la résolution de confrères reposent sur des appels API (IdNot/Annuaire) au moment de la demande, complétés par la base du site (users/contacts/offices) et les partages (folder_sharing).
Troubleshooting
Erreur : Missing ANNUAIRE_API_LOGIN or ANNUAIRE_API_PASSWORD
- Solution : Injecter les clés dans
system_configurationvia le mécanisme standard d’injection (fichier.secrets/<env>/env-full-<env>-for-bdd-injection.txtpuis déploiement).
Erreur : HTTP 401 Unauthorized
- Cause : Login ou mot de passe incorrect
- Solution : Vérifier les credentials en base et mettre à jour si nécessaire
Erreur : HTTP 403 Forbidden
- Cause : Le compte n'a pas accès à l'API Annuaire
- Solution : Contacter le support ID.NOT pour vérifier les droits du compte
Recherche confrères (appel direct API annuaire)
La recherche de notaires pour inviter un confrère au partage de dossier utilise uniquement l’API Annuaire (pas de base du site ni de confrères déjà invités).
Endpoint exposé : GET /api/v1/notary/search/notaries?q={query}
- Authentification : session notaire (authHandler, permissionContextInjector).
- Paramètre :
q(string, min. 2 caractères) – nom, prénom, email ou nom d’office. - Réponse :
{ results: Array<...>, annuaireConfigured: boolean, message?: string }.results: tableau d’objets{ idNot, email, firstName, lastName, officeName, crpcen, isPrimary, displayName, hasActiveLicense?, phone? }. Si l’annuaire n’est pas configuré : 503 Service Unavailable avec le même format (annuaireConfigured: false,messageexplicatif,results: []). Pas de 200 avec résultats vides.
Comportement :
- Si
ANNUAIRE_API_BASE_URLetIDNOT_API_KEYsont renseignés en base (system_configuration) : appel API Annuaire V2 avec pagination complète (pages de 50 jusqu’à épuisement) :- PB lookup :
GET {ANNUAIRE_API_BASE_URL}/api/v2/directory/lookup?nomNotaire={query}&page=&size=50&key=...(toutes les pages). - Fallback PP :
GET {ANNUAIRE_API_BASE_URL}/api/pp/v2/personnes?prenom=...&nomUsuel=...&page=&size=50&key=...(toutes les pages).
- PB lookup :
- Si l’annuaire n’est pas configuré : réponse
{ results: [], annuaireConfigured: false, message }; le front affiche le message d’alerte (pas de fallback legacy IdNot).
Fichiers :
lecoffre-back-main/src/services/common/IdNotDirectoryService/IdNotDirectoryService.ts:searchNotaries()retourneSearchNotariesResponse;getAnnuaireCredentials().lecoffre-back-main/src/services/common/IdNotDirectoryService/helpers/IdNotDirectoryApiSearchHelper.ts: API Annuaire V2 (lookup + PP, pagination complète).lecoffre-back-main/src/app/api/notary/helpers/FolderSharingSearchHelper.ts: renvoie{ results, annuaireConfigured, message? }.
Configuration : ANNUAIRE_API_BASE_URL, IDNOT_API_KEY en base (system_configuration). Obligatoires pour que la recherche renvoie des résultats.
Notaire invité : email et IdNot en base (folder_sharing)
Après la création d'un partage, seul l'email en base associé au notaire invité est utilisé pour les envois (invitations, relances, documents). Aucune relecture de l'email côté IdNot.
- Un seul champ email :
invited_notary_email= email de préférence pour les envois. À l'invitation il est en général pré-rempli depuis IdNot (annuaire) ; il peut être modifié avant enregistrement. L'email IdNot et l'email de préférence sont donc le même champ en base pour le notaire invité. - Accès aux dossiers invités : croisement sur l'IdNot (
invited_notary_idnot). Quand l'utilisateur connecté a un IdNot, seuls les partages dontinvited_notary_idnotcorrespond sont pris en compte. L'email en base n'est pas utilisé pour ce croisement. - Profils avec IdNot : à l'invitation, le partage enregistre
invited_notary_idnot. Les notaires invités (y compris sans seat) ont en base un identifiant IdNot : sur chaque partage (folder_sharing.invited_notary_idnot), et après première connexion via IdNot sur le profil utilisateur (users.idNot).
Voir aussi : docs/ARCHITECTURE.md § Partage Inter-Études, docs/README.md (consolidation opérationnelle).
Architecture
Les synchronisations batch historiques (et les tables annu_* associées) ne sont plus utilisées par l’application. Les recherches de confrères et les résolutions (ex: par email) s’effectuent via des appels API (IdNot / Annuaire) au moment de la demande, avec complément de la base du site et des partages (folder_sharing).
Déploiement
Déploiement standard du backend via deploy/scripts/build-and-deploy.sh <env>. Aucune migration spécifique de tables annu_* n’est requise. Les appels API Annuaire/IdNot nécessitent que les clés correspondantes soient présentes dans system_configuration (injection via .secrets/<env>/env-full-<env>-for-bdd-injection.txt puis déploiement).
Sécurité
Authentification Basic Auth :
Authorization: Basic <base64(login:password)>
Stockage des credentials :
- Login : Stocké en clair dans
system_configuration(is_sensitive: false) - Password : Marqué comme sensible dans
system_configuration(is_sensitive: true) - Affichage : Le mot de passe est masqué (
***) dans l'interface super-admin
Logs : Les logs ne contiennent jamais les credentials. Seuls les URLs (sans query params) et les codes HTTP sont loggés.
Tests et Vérification
Les tests de connectivité et les vérifications se font via les parcours applicatifs qui déclenchent des appels API (IdNot / Annuaire), ou via les cartes de tests live de la page super-admin santé.
Troubleshooting API Annuaire
Erreur : Missing ANNUAIRE_API_LOGIN or ANNUAIRE_API_PASSWORD :
Cause : Les credentials ne sont pas en base de données.
Solution :
deploy/scripts/build-and-deploy.sh test
Erreur : HTTP 401 Unauthorized :
Cause : Login ou mot de passe incorrect.
Solution :
- Vérifier les credentials en base :
SELECT key, value FROM system_configuration WHERE key LIKE 'ANNUAIRE_API_%';
- Mettre à jour si nécessaire via l'interface super-admin ou le script.
Erreur : HTTP 403 Forbidden :
Cause : Le compte n'a pas accès à l'API Annuaire.
Solution : Contacter le support ID.NOT pour vérifier les droits du compte.
Aucune donnée synchronisée :
Cause : Les pages retournent 0 résultats ou l'API retourne un format inattendu.
Solution :
- Vérifier les logs backend (service API) pour les erreurs IdNot/Annuaire
- Regarder les "sample" logs (premier élément de chaque type)
- Adapter le parsing si nécessaire dans les helpers de recherche IdNot/Annuaire
Rappels Automatiques Documents
Statut : ✅ 100% | Version : 2.0.0
Architecture Backend
Service : DocumentRemindersService (~350 lignes)
Fichier : lecoffre-back-main/src/services/notary/DocumentRemindersService/DocumentRemindersService.ts
Repository : DocumentRemindersConfigRepository
Endpoints
6 endpoints (config CRUD + trigger manuel) :
GET /api/v1/notary/document-types/:documentTypeUid/reminders/config: Récupérer configuration pour un type de documentGET /api/v1/notary/offices/:officeUid/reminders/config: Liste toutes les configurations d'un officePOST /api/v1/notary/document-types/:documentTypeUid/reminders/config: Créer configurationPUT /api/v1/notary/document-types/:documentTypeUid/reminders/config: Modifier configurationDELETE /api/v1/notary/document-types/:documentTypeUid/reminders/config: Supprimer configurationPOST /api/v1/notary/reminders/send: Déclencher manuellement (⚠️ DÉSACTIVÉ - relances automatiques désactivées)
Cron : sendDocumentReminders() quotidien 9h (⚠️ DÉSACTIVÉ - relances automatiques totalement désactivées, voir CronService.ts ligne 144-168)
Configuration Base de Données
Table : document_reminders_config
Champs :
document_type_uid: Type de document concernéoffice_uid: Office concernéreminder_intervals: JSON array d'intervalles en jours (ex:[7, 14, 21])reminder_message: Message personnalisé optionnelauto_send: Envoi automatique activé (défaut: true)is_active: Configuration active/inactive
Frontend
Pages :
/reminders/config.tsx: Liste + toggle actif/inactif/folders/[uid]/documents-reminder-history: Historique rappels
API : DocumentReminders (~130 lignes)
Support destinataires :
- Clients : Relance via
depositor.uid - Tiers : Relance via
third_party_depositor.uid(v2.0.0) - Notaires invités : Relance via
shared_to_office.uid(v2.0.1)
Fonctionnalités
- Intervalles configurables : Ex: [7, 14, 21] jours après demande
- Message personnalisé : Optionnel par type document + office
- Configuration par type document + office : Granularité fine
- Activation/désactivation : Via champ
isActivedans la configuration (pas d'endpoint toggle dédié) - Trigger manuel : ⚠️ DÉSACTIVÉ - L'endpoint
POST /api/v1/notary/reminders/sendest désactivé. Utiliser/api/v1/notary/customers/:uid/send_reminderpour les relances manuelles - Évite doublons : 1 rappel/jour maximum par document
- Support notaires invités : Relance possible pour les confrères via partage inter-études (v2.0.1)
Configuration Système
Variable : CRON_SEND_REMINDERS_ENABLED (dans system_configuration)
Gestion : Désactivable via /super-admin/system-config
Opérations Batch
Statut : ✅ 100% | Version : 2.0.0
Certificat Unique Dossier
Service : FolderAnchorCertificateService
Fonctionnalité : PDF unique pour dossier complet
Inclut :
- Liste tous documents ancrés
- Merkle root dossier
- Hash ZIP complet
- Informations blockchain
Envoi Documents par Destinataire
Backend :
- Endpoint :
POST /api/v1/notary/documents_notary/send - Body :
recipientUid,folderUid,files(FormData) - Middleware :
multer().array("files", 20)(20 fichiers max) - Logique : Un destinataire par appel ; le frontend boucle sur les destinataires sélectionnés et agrège les résultats
Note : Route exclue du middleware multer global (voir README.md)
Filtres Avancés
Backend :
- Service :
FolderSearchService - Controller :
FolderSearchController - Endpoint :
POST /api/v1/notary/folders/search
Filtres disponibles :
- Numéro dossier
- Nom dossier
- Type acte
- Office
- Statut (LIVE, ARCHIVED, DELETED)
- Date création
- Collaborateurs
Téléchargement Multiple
Backend :
- Service :
ZipService - Endpoint :
POST /api/v1/notary/files/download-multiple
Fonctionnalités :
- Sélection multiple fichiers
- Conversion automatique en PDF si nécessaire
- Ajout filigrane "lecoffre.io" (préfixe "acpl_")
- Ajout métadonnées d'ancrage dans les PDF
- Inclut certificats d'ancrage si disponibles
- Utilise versions filigrannées existantes
Fichier : lecoffre-back-main/src/services/common/ZipService/ZipService.ts
Certificat Agrégé Documents
Statut : ✅ 100% | Version : 2.1.0
Service : AggregatedCertificateService
Fichier : lecoffre-back-main/src/services/notary/AggregatedCertificateService/AggregatedCertificateService.ts
Fonctionnalités :
- Génération d'un certificat PDF unique pour plusieurs documents sélectionnés
- Support des Documents (documents validés) et DocumentsNotary (documents envoyés)
- Ancrage du certificat sur la blockchain Bitcoin Signet
- Upload du certificat sur IPFS
- Inclusion automatique dans les ZIP téléchargés
Endpoints :
POST /api/v1/notary/files/download-aggregated-certificate: Certificat agrégé pour DocumentsPOST /api/v1/notary/files-notary/download-aggregated-certificate: Certificat agrégé pour DocumentsNotary
Limite : 100 documents maximum par certificat
Mémo API Annuaire (Qualification)
Endpoints :
- ID.not Auth:
https://qual-connexion.idnot.fr(authorize/token) - Annuaire base:
https://qual-api.notaires.fr/annuaire - Recherche personnes (si supporté):
GET {base}/persons?q={query}avec headerx-api-key - Entités (offices):
GET /api/pp/v2/entites?type=office&page={page}&size={size}&key={IDNOT_API_KEY} - Fallback offices:
GET /api/pp/v2/entites?key={IDNOT_API_KEY}→ filtrertypeEntite.name==office - Personnes:
GET /api/pp/v2/personnes?page={page}&size={size}&key={IDNOT_API_KEY} - Rattachements:
GET /api/pp/v2/personnes/{idnot}/rattachements?page={page}&size=100&key={IDNOT_API_KEY}
Notes :
- Auth : la clé API se passe en query (puis fallback header x-api-key)
- Pagination : utiliser page>=1 (ou 0 selon endpoint) et size=100
- Erreurs : 403 accessDenied → essayer fallback /entites sans type et filtrer côté client
- Sécurité : ne publier aucune valeur de clé
Exemple de Client pour l'API d'Ancrage
Un exemple complet de client Node.js pour tester l'API d'ancrage est disponible dans le code source. Le client permet de :
- Vérifier l'état de l'API (
/health) - Ancrer un document (
POST /api/anchor/document) - Vérifier un ancrage (
POST /api/anchor/verify)
Fonctionnement synchrone : L'API retourne directement le txid dans la réponse (HTTP 200 OK), pas besoin de polling ou de status endpoint.
Points importants :
- L'API retourne directement le
txiddans la réponse (fonctionnement synchrone) - Le code HTTP est 200 OK (pas 202 Accepted)
- Toutes les informations sont disponibles immédiatement (confirmations, block_height)
Voir le code source dans lecoffre-anchor-api/ pour l'exemple complet de client.
Mise à jour de l'URL de l'API d'ancrage
Date : 2026-01-27
URL mise à jour : L'URL de l'API d'ancrage a été changée de https://prod.signet.4nkweb.com vers https://anchorage.certificator.4nkweb.com.
Fichiers à mettre à jour :
1. Fichiers .secrets/<env>/env-full-<env>-for-bdd-injection.txt :
Ces fichiers sont utilisés pour injecter les variables dans la base de données via setSettings.
Emplacements :
.secrets/test/env-full-test-for-bdd-injection.txt.secrets/pprod/env-full-pprod-for-bdd-injection.txt.secrets/prod/env-full-prod-for-bdd-injection.txt
Variable à mettre à jour :
ANCHORE_API_URL=https://anchorage.certificator.4nkweb.com
Note : Ces fichiers ne sont pas versionnés (dans .gitignore) et doivent être mis à jour manuellement sur chaque machine de développement.
2. Base de données (via system_configuration) :
Si les fichiers env-full-*-for-bdd-injection.txt ont déjà été injectés, la valeur dans la base de données doit être mise à jour :
Requête SQL :
UPDATE system_configuration
SET value = 'https://anchorage.certificator.4nkweb.com'
WHERE key = 'ANCHORE_API_URL'
AND category = 'INTEGRATION_BITCOIN';
Ou via le script setSettings :
# Après avoir mis à jour le fichier env-full-<env>-for-bdd-injection.txt
bash deploy/scripts_v2/remote/set-settings.sh <env> <domain> <app_root>
Vérification :
# Depuis le backend
cd lecoffre-back-main
npm run anchorage:test-anchor-api
Le script doit afficher :
✅ [TestAnchorAPI] Variables récupérées depuis la BDD
URL: https://anchorage.certificator.4nkweb.com
Problème 502 Bad Gateway : Si vous rencontrez des erreurs 502 Bad Gateway :
- Vérifier l'état du service :
systemctl --user status anchor-api.service - Vérifier que le service écoute sur le port 3004 :
ss -tlnp | grep 3004 - Tester depuis le backend :
curl -s http://localhost:3004/health - Configuration Nginx : Route
anchorage.certificator.4nkweb.com→192.168.1.103:3004
Configuration actuelle :
- Service :
anchor-api.service(user service) - Port :
3004(configuré dans.envet systemd) - Nginx : Route
anchorage.certificator.4nkweb.com→192.168.1.103:3004 - Bitcoin RPC :
localhost:18443(Signet)
Dernière mise à jour : 2026-01-28 (consolidation API.md, API_ANNUAIRE_IMPLEMENTATION.md, API_MEMO.md, ANCHOR_API_ANALYSIS.md, ANCHOR_API_EXAMPLE_CLIENT.md, UPDATE_ANCHOR_API_URL.md)
Correctif accès invités notaires (2026-03-02)
Problème observé :
- Un notaire invité pouvait voir des dossiers partagés (
GUEST_FOLDERS) mais être déconnecté à l'ouverture d'un dossier lors deGET /v1/notary/documents.
Cause racine :
- Des chemins helper notaire appelaient encore
FolderSharingService.hasAccess(folderUid, officeUid, email)sans propagation deidNot, malgré la centralisation d'identité déjà engagée.
Correctif appliqué :
- Propagation de
idNotdans les appels de contrôle d'accès des helpers notaire concernés. - Extension du même alignement à des helpers customer/middleware/service où la vérification restait email-only.
- Réutilisation de l'extraction d'identité centralisée
buildUserIdentityContextpour éviter les divergences de signature d'appel.
Effet attendu :
- Les contrôles d'accès invités notaires utilisent de manière cohérente l'identité
idNotsur le fluxGUEST_FOLDERS, sans régression sur les flux non-notaire (oùuserIdNotreste optionnel).