Dieser Artikel enthält sofort einsatzbereite Skripte für die Notiondesk-API, mit denen Teams häufig automatisierte Aufgaben erledigen: Notion-Änderungen aus einer CI-Pipeline veröffentlichen, jeden Artikel als Markdown sichern, negatives Feedback an Slack senden, Anfragen melden, die Ihr Hilfecenter nicht beantworten kann, fehlgeschlagene Synchronisierungen finden und mehrere Hilfecenter einheitlich gestalten. Kopieren Sie einfach ein Skript, legen Sie zwei Umgebungsvariablen fest und führen Sie es aus.
Bevor Sie beginnen
- Erstellen Sie einen API-Schlüssel, der nur die für das Rezept benötigten Berechtigungen enthält. Jedes Rezept benennt diese Berechtigungen. Siehe Authentifizierung mit der Notiondesk-API mithilfe von API-Schlüsseln .
- Finden Sie die
idIhres Hilfezentrums mitGET /sites. Siehe Hilfezentren mit der Notiondesk API lesen .
- Setzen Sie beide als Umgebungsvariablen:
export NOTIONDESK_API_KEY="nd_wsk_..."
export NOTIONDESK_SITE_ID="c74781f6-1aea-4e17-ab23-bbb8b4d174b6"Für die JavaScript-Rezepte wird Node.js 18 oder höher benötigt.
Ein kleiner, gemeinsamer Helfer
Speichern Sie diese Datei als notiondesk.mjs neben Ihren Skripten. Sie sendet die Header, wartet auf die Antwort der API (429 rate_limited) und liest jede Seite einer Liste.
Synchronisieren Sie Ihr Hilfecenter mit GitHub Actions.
Geltungsbereich: sync
Veröffentlichen Sie Ihre Notion-Änderungen über einen GitHub Actions-Workflow, beispielsweise direkt nach einem Release. Speichern Sie den Schlüssel als Repository-Geheimnis NOTIONDESK_API_KEY und die Hilfecenter-ID als Repository-Variable 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."204bedeutet, dass die Synchronisierung in der Warteschlange steht. Sie wird im Hintergrund ausgeführt.
409 conflict_errorbedeutet, dass eine Synchronisierung des Hilfecenters bereits in der Warteschlange ist oder ausgeführt wird, sodass der Schritt erfolgreich abgeschlossen wird, ohne einen weiteren zu starten.
- Der Parameter
Idempotency-Keybewirkt, dass eine erneute Anfrage nicht zweimal in die Warteschlange gestellt wird.
Sichere jeden Artikel als Markdown.
Geltungsbereich: read
Speichern Sie jeden Artikel in jeder Sprache, die Ihr Hilfezentrum anbietet, als Markdown-Datei:
Die Sicherung umfasst Entwürfe und archivierte Artikel. Die Anzahl der Lesevorgänge ist auf 120 Anfragen pro Minute begrenzt, sodass ein Hilfecenter mit 500 Artikeln etwa fünf Minuten pro Sprache benötigt; der Helper wartet automatisch, sobald das Limit erreicht ist.
Sende jeden Tag negatives Feedback an Slack.
Geltungsbereich: Nur read
Poste das negative Feedback der letzten 24 Stunden über einen eingehenden Webhook an einen Slack-Kanal. Setze SLACK_WEBHOOK_URL und führe das Skript einmal täglich aus.
Die Feedback-Liste ist nach dem neuesten Eintrag sortiert, daher stoppt das Skript die Seitenzählung beim ersten Eintrag, der älter als 24 Stunden ist.
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")}` }),
});
}readWöchentlich Suchanfragen ohne Ergebnisse melden
Geltungsbereich: read
Listen Sie auf, wonach Leser letzte Woche gesucht haben, ohne einen Artikel zu finden. Jede Suchanfrage bietet Potenzial für einen neuen Artikel oder einen aussagekräftigeren Titel.
Jede Abfrageliste enthält bis zu 50 Zeilen. Analyseberichte verfügen außerdem über ein Budget von 30 Anfragen pro Minute und Hilfezentrum. Weitere Informationen finden Sie unter „Hilfezentrum-Analysen mit der Notiondesk-API abrufen“ .
Finde Artikel, deren letzte Synchronisierung fehlgeschlagen ist
Gültigkeitsbereich: read, plus sync zum erneuten Versuch
Korrigiere zuerst jeden Artikel in Notion und synchronisiere ihn dann erneut mit POST /sites/{site_id}/articles/{article_id}/sync.
Verwenden Sie für jedes Hilfezentrum eine einheitliche Marke.
Geltungsbereich: read und admin
Kopieren Sie die Farben und Schriftarten eines Hilfecenters in alle anderen Hilfecenter Ihres Arbeitsbereichs. Legen Sie SOURCE_SITE_ID für das Hilfecenter fest, von dem kopiert werden soll.
Jede Aktualisierung des Brandings aktualisiert das gesamte Ziel-Hilfecenter. Daher sendet das Skript eine Anfrage pro Hilfecenter. Eine in Kurzform wie #fff gespeicherte Farbe wird abgelehnt: Korrigieren Sie dies zuerst im Dashboard des Quell-Hilfecenters. Siehe „Anpassen von Hilfecenter-Branding und -Designs mit der Notiondesk-API“ .
Importweiterleitungen nach einer Migration
Geltungsbereich: read und write
Wechseln Sie von einem anderen Hilfecenter-Tool? Das Skript zum Importieren von Weiterleitungen liest eine CSV-Datei mit alten Pfaden und erstellt für jede Zeile eine Weiterleitung. Siehe „Seitenweiterleitungen mit der Notiondesk-API verwalten“ .
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}`);
}Häufig gestellte Fragen
Welche Sprache kann ich mit der Notiondesk API verwenden?
Jede Sprache, die HTTPS-Anfragen senden kann. Die API ist eine einfache REST-API mit JSON, daher funktionieren JavaScript, Python, PHP, Go und Ruby alle.
Wo soll ich diese Skripte ausführen?
Auf einem Server, in einem geplanten Job oder einer CI-Pipeline, wobei der Schlüssel als Geheimnis gespeichert wird. Führen Sie diese niemals in einem Browser aus.
Kann ich diese Skripte mit OAuth anstelle eines API-Schlüssels verwenden?
Ja. Ersetzen Sie den API-Schlüssel im Authorization-Header durch ein OAuth-Zugriffstoken und aktualisieren Sie es, bevor es abläuft. Siehe Verbinden Ihrer App mit Notiondesk über OAuth .