Cet article rassemble des scripts API prêts à l'emploi pour les tâches les plus automatisées par les équipes : publication des modifications Notion depuis un pipeline d'intégration continue, sauvegarde de chaque article au format Markdown, envoi de commentaires négatifs sur Slack, signalement des recherches auxquelles votre centre d'aide ne peut répondre, identification des échecs de synchronisation et application d'une marque à plusieurs centres d'aide. Copiez une recette, définissez deux variables d'environnement et exécutez-la.
Avant de commencer
- Créez une clé API avec uniquement les autorisations nécessaires à la recette. Chaque recette les spécifie. Consultez la section « Authentification auprès de l’API Notiondesk à l’aide de clés API » .
- Trouvez le
idde votre centre d'aide avecGET /sites. Consultez la section « Lire vos centres d'aide avec l'API Notiondesk » .
- Définissez les deux comme variables d'environnement :
export NOTIONDESK_API_KEY="nd_wsk_..."
export NOTIONDESK_SITE_ID="c74781f6-1aea-4e17-ab23-bbb8b4d174b6"Les recettes JavaScript nécessitent Node.js 18 ou une version ultérieure.
Un petit assistant partagé
Enregistrez ce fichier sous le nom notiondesk.mjs à côté de vos scripts. Il envoie les en-têtes, attend la réponse de l'API (429 rate_limited) et lit chaque page d'une liste.
Synchronisez votre centre d'aide depuis GitHub Actions
Portée : __PRÉSERVÉ_30__
Publiez vos modifications Notion à partir d'un workflow GitHub Actions, par exemple juste après une publication. Stockez la clé comme secret du dépôt NOTIONDESK_API_KEY et l'identifiant du centre d'aide comme variable du dépôt NOTIONDESK_SITE_ID.
export const SITE_ID = process.env.NOTIONDESK_SITE_ID;
export async function api(path, options = {}) {
for (let attempt = 0; attempt < 5; attempt++) {
const response = await fetch(`https://api.notiondesk.so${path}`, {
...options,
headers: {
Authorization: `Bearer ${process.env.NOTIONDESK_API_KEY}`,
"nd-version": "2026-10-16",
...options.headers,
},
});
if (response.status === 429) {
const seconds = Number(response.headers.get("Retry-After")) || 5;
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
continue;
}
if (!response.ok) {
throw new Error(`${response.status} ${await response.text()}`);
}
return response.status === 204 ? null : response.json();
}
throw new Error(`Still rate limited on ${path}`);
}
export async function listAll(path) {
const items = [];
let cursor = null;
do {
const url = new URL(`https://api.notiondesk.so${path}`);
url.searchParams.set("page_size", "100");
if (cursor) {
url.searchParams.set("cursor", cursor);
}
const page = await api(`${url.pathname}${url.search}`);
items.push(...page.data);
cursor = page.has_more ? page.next_cursor : null;
} while (cursor);
return items;
}name: Sync help center
on:
workflow_dispatch:
release:
types: [published]
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Trigger a Notiondesk sync
run: |
status=$(curl -s -o response.json -w "%{http_code}" -X POST \
"https://api.notiondesk.so/sites/${{ vars.NOTIONDESK_SITE_ID }}/sync" \
-H "Authorization: Bearer ${{ secrets.NOTIONDESK_API_KEY }}" \
-H "nd-version: 2026-10-16" \
-H "Idempotency-Key: github-${{ github.run_id }}-${{ github.run_attempt }}")
if [ "$status" = "409" ]; then echo "A sync is already running."; exit 0; fi
if [ "$status" != "204" ]; then cat response.json; exit 1; fi
echo "Sync queued."204signifie que la synchronisation est mise en file d'attente. Elle s'exécute en arrière-plan.
409 conflict_errorsignifie qu'une synchronisation du centre d'aide est déjà en file d'attente ou en cours, l'étape réussit donc sans en démarrer une autre.
- Le
Idempotency-Keypermet d'éviter que la file d'attente des requêtes réessayées ne soit traitée deux fois.
Sauvegardez chaque article au format Markdown
Portée : __PRÉSERVÉ_24__
Enregistrez chaque article, dans chaque langue prise en charge par votre centre d'aide, sous forme de fichier Markdown :
La sauvegarde inclut les brouillons et les articles archivés. La lecture est limitée à 120 requêtes par minute ; un centre d’aide contenant 500 articles prend donc environ cinq minutes par langue ; le système se met automatiquement en pause une fois la limite atteinte.
Envoyer des commentaires négatifs sur Slack tous les jours
Portée : read uniquement
Publier les commentaires négatifs des dernières 24 heures sur un canal Slack via un webhook entrant . Définir SLACK_WEBHOOK_URL, puis exécuter le script une fois par jour.
La liste des commentaires est triée par ordre chronologique, du plus récent au plus ancien ; le script arrête donc la pagination à la première entrée datant de plus de 24 heures.
import { mkdir, writeFile } from "node:fs/promises";
import { api, listAll, SITE_ID } from "./notiondesk.mjs";
const site = await api(`/sites/${SITE_ID}`);
for (const locale of site.supported_locales) {
await mkdir(`backup/${locale}`, { recursive: true });
const articles = await listAll(`/sites/${SITE_ID}/articles?locale=${locale}`);
for (const article of articles) {
const full = await api(`/sites/${SITE_ID}/articles/${encodeURIComponent(article.id)}?locale=${locale}`);
const frontMatter = `---\ntitle: ${JSON.stringify(full.title ?? "")}\nstatus: ${full.status}\nurl: ${full.url ?? ""}\n---\n\n`;
await writeFile(`backup/${locale}/${encodeURIComponent(article.id)}.md`, frontMatter + full.text);
if (full.truncated) {
console.warn(`${locale}/${article.id} is longer than 200,000 bytes and was truncated`);
}
}
console.log(`${locale}: ${articles.length} articles saved`);
}import { api, SITE_ID } from "./notiondesk.mjs";
const since = Date.now() - 24 * 60 * 60 * 1000;
const escapeSlack = (text) => (text ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
async function feedbackSince(timestamp) {
const entries = [];
let cursor = null;
do {
const query = new URLSearchParams({ page_size: "100" });
if (cursor) {
query.set("cursor", cursor);
}
const page = await api(`/sites/${SITE_ID}/feedback?${query}`);
const recent = page.data.filter((entry) => Date.parse(entry.submitted_at) >= timestamp);
entries.push(...recent);
cursor = page.has_more && recent.length === page.data.length ? page.next_cursor : null;
} while (cursor);
return entries;
}
const negative = (await feedbackSince(since)).filter(
(entry) => entry.reaction_group === "negative" || entry.reaction_group === "not_helpful",
);
if (negative.length > 0) {
const lines = negative.map(
(entry) => `• *${escapeSlack(entry.article_title)}* (${entry.article_locale}): ${escapeSlack(entry.message) || "_no message_"}`,
);
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: `${negative.length} negative feedback in the last 24 hours\n${lines.join("\n")}` }),
});
}readDes recherches sans résultat sont signalées chaque semaine.
Portée : read
Dressez la liste des recherches effectuées par les lecteurs la semaine dernière sans qu'aucun article n'ait été trouvé. Chaque requête peut faire l'objet d'un nouvel article ou être associée à un titre plus pertinent.
Chaque liste de requêtes peut contenir jusqu'à 50 lignes. Les rapports d'analyse partagent également un budget de 30 requêtes par minute et par centre d'aide. Voir Obtenir des analyses du centre d'aide avec l'API Notiondesk .
Trouver les articles dont la dernière synchronisation a échoué
Portée : read, plus sync pour réessayer
Corrigez d'abord chaque article dans Notion, puis synchronisez-le à nouveau avec POST /sites/{site_id}/articles/{article_id}/sync.
Associez une seule marque à chaque centre d'assistance.
Portée : read et admin
Copiez les couleurs et les polices d'un centre d'aide vers tous les autres centres d'aide de votre espace de travail. Définissez SOURCE_SITE_ID sur le centre d'aide source.
Chaque mise à jour de l'identité visuelle actualise l'ensemble du centre d'aide cible ; le script envoie donc une requête par centre d'aide. Une couleur stockée sous une forme abrégée telle que #fff est rejetée : corrigez-la d'abord dans le tableau de bord du centre d'aide source. Consultez la section « Personnaliser l'identité visuelle et les thèmes du centre d'aide avec l'API Notiondesk » .
Redirections d'importation après une migration
Portée : read et write
Vous utilisez un autre outil de centre d'aide ? Le script d'importation des redirections lit un fichier CSV contenant les anciens chemins d'accès et crée une redirection par ligne. Consultez la section « Gérer les redirections de page avec l'API Notiondesk » .
import { api, SITE_ID } from "./notiondesk.mjs";
const end = new Date();
end.setUTCHours(0, 0, 0, 0);
const start = new Date(end.getTime() - 7 * 24 * 60 * 60 * 1000);
const toApiDate = (date) => `${date.toISOString().slice(0, 19)}Z`;
const query = new URLSearchParams({
start_date: toApiDate(start),
end_date: toApiDate(end),
date_scale: "day",
});
const report = await api(`/sites/${SITE_ID}/analytics/search?${query}`);
console.log(`Searches: ${report.searches.total} (${report.searches.percentage_difference}% vs previous week)`);
console.log(`Without results: ${report.searches_without_results.total}`);
console.log(`Click-through rate: ${report.click_through_rate.current.toFixed(1)}%`);
for (const { query: text, searches } of report.searches_without_results.queries) {
console.log(`${searches}\t${text}`);
}import { api, listAll, SITE_ID } from "./notiondesk.mjs";
const articles = await listAll(`/sites/${SITE_ID}/articles`);
const failed = articles.filter((article) => article.sync_failed || article.sync_timeout);
for (const article of failed) {
const reason = article.sync_timeout ? "timed out" : article.sync_error_code ?? "failed";
console.log(`${article.title}: ${reason} ${article.notion_article_page_url ?? ""}`);
}import { createHash } from "node:crypto";
import { api, listAll } from "./notiondesk.mjs";
const sourceId = process.env.SOURCE_SITE_ID;
const { colors, primary_font, secondary_font } = await api(`/sites/${sourceId}/settings/customize`);
const body = JSON.stringify({ colors, primary_font, secondary_font });
const bodyHash = createHash("sha256").update(body).digest("hex");
const sites = await listAll("/sites");
for (const site of sites.filter((candidate) => candidate.id !== sourceId)) {
await api(`/sites/${site.id}/settings/customize`, {
method: "PATCH",
headers: { "Content-Type": "application/json", "Idempotency-Key": `brand-${site.id}-${bodyHash}` },
body,
});
console.log(`Updated ${site.full_domain}`);
}Questions fréquentes
Quelle langue puis-je utiliser avec l'API Notiondesk ?
Tout langage capable d'envoyer des requêtes HTTPS est compatible. L'API est une API REST simple utilisant JSON ; par conséquent, JavaScript, Python, PHP, Go et Ruby fonctionnent tous.
Où dois-je exécuter ces scripts ?
Sur un serveur, une tâche planifiée ou un pipeline d'intégration continue, la clé doit être stockée comme un secret. Ne les exécutez jamais dans un navigateur.
Puis-je utiliser ces scripts avec OAuth au lieu d'une clé API ?
Oui. Remplacez la clé API par un jeton d'accès OAuth dans l'en-tête Authorization et actualisez-le avant son expiration. Consultez la section « Connecter votre application à Notiondesk avec OAuth » .