Automatisieren Sie häufige Aufgaben im Hilfecenter mit der Notiondesk API

Notiondesk API-Beispiele: Synchronisierung von GitHub Actions, Sicherung von Artikeln als Markdown, Veröffentlichung von Feedback in Slack und Meldung von Suchanfragen ohne Ergebnisse.

7 Min. Lesezeit

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

  • 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."
  • 204 bedeutet, dass die Synchronisierung in der Warteschlange steht. Sie wird im Hintergrund ausgeführt.
  • 409 conflict_error bedeutet, 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-Key bewirkt, dass eine erneute Anfrage nicht zweimal in die Warteschlange gestellt wird.
Wenn die automatische Übersetzung aktiviert ist, übersetzt jede Synchronisierung die geänderten Inhalte und stellt sie in Rechnung. Der Workflow sollte erst nach tatsächlichen Änderungen und nicht nach einem kurzen Zeitplan ausgelöst werden. Weitere Informationen finden Sie unter „Notion-Synchronisierungen mit der Notiondesk-API auslösen“ .

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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");

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")}` }),
  });
}
Das Feedback stammt von Ihren Lesern. Führen Sie dieses Skript mit einem Schlüssel aus, der nur Leerzeichen enthält, maskieren Sie den Text vor dem Absenden und übergeben Sie ihn niemals an einen LLM, der einen Schreibschlüssel besitzt. Siehe „Leserfeedback mit der Notiondesk-API exportieren“ .read

Wö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 .

Was kommt als Nächstes?

Verwandte Artikel

War diese Seite hilfreich?

Still need help? Ask us