Este artículo recopila scripts de API listos para usar para las tareas que los equipos automatizan con mayor frecuencia: publicar ediciones de Notion desde una canalización de CI, hacer copias de seguridad de todos los artículos en formato Markdown, enviar comentarios negativos a Slack, informar sobre las búsquedas que su centro de ayuda no puede responder, encontrar sincronizaciones fallidas y aplicar una marca a varios centros de ayuda. Copie una receta, configure dos variables de entorno y ejecútela.
Antes de empezar
- Cree una clave API con solo los ámbitos que necesita la receta. Cada receta les asigna un nombre. Consulte Autenticarse con la API Notiondesk usando claves API .
- Encuentra el
idde tu centro de ayuda conGET /sites. Consulta Lee tus centros de ayuda con la API Notiondesk .
- Establece ambas como variables de entorno:
export NOTIONDESK_API_KEY="nd_wsk_..."
export NOTIONDESK_SITE_ID="c74781f6-1aea-4e17-ab23-bbb8b4d174b6"Las recetas de JavaScript requieren Node.js versión 18 o posterior.
Un pequeño ayudante compartido
Guarda este archivo como notiondesk.mjs junto a tus scripts. Envía los encabezados, espera a que la API responda con 429 rate_limited y lee cada página de una lista.
Sincroniza tu centro de ayuda desde GitHub Actions.
Alcance: sync
Publica tus ediciones de Notion desde un flujo de trabajo de GitHub Actions, por ejemplo, justo después de un lanzamiento. Guarda la clave como el secreto del repositorio NOTIONDESK_API_KEY y el ID del centro de ayuda como la variable del repositorio 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."204significa que la sincronización está en cola. Se ejecuta en segundo plano.
409 conflict_errorsignifica que ya hay una sincronización del centro de ayuda en cola o en ejecución, por lo que el paso se completa con éxito sin iniciar otro.
- El
Idempotency-Keyhace que una cola de solicitud reintentada no pase nada dos veces.
Realiza una copia de seguridad de cada artículo en formato Markdown.
Alcance: __PRESERVADO_24__
Guarda cada artículo, en cada idioma que admita tu centro de ayuda, como un archivo Markdown:
La copia de seguridad incluye borradores y artículos archivados. El número de lecturas está limitado a 120 solicitudes por minuto, por lo que un centro de ayuda con 500 artículos tarda aproximadamente cinco minutos por idioma; el asistente espera automáticamente cuando alcanza el límite.
Envía comentarios negativos a Slack todos los días.
Alcance: read solamente
Publica los comentarios negativos de las últimas 24 horas en un canal de Slack mediante un webhook entrante . Configura SLACK_WEBHOOK_URL y ejecuta el script una vez al día.
La lista de comentarios se muestra primero la más reciente, por lo que el script deja de paginar en la primera entrada con más de 24 horas de antigüedad.
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")}` }),
});
}readInforme cada semana sobre las búsquedas que no arrojan resultados.
Alcance: read
Enumera las búsquedas que realizaron los lectores la semana pasada sin encontrar ningún artículo. Cada consulta es una posible candidata para un nuevo artículo o un mejor título.
Cada lista de consultas admite hasta 50 filas. Los informes de análisis también comparten un presupuesto de 30 solicitudes por minuto por centro de ayuda. Consulte Obtener análisis del centro de ayuda con la API Notiondesk .
Encuentra artículos cuya última sincronización falló.
Alcance: read, más sync para reintentar
Primero corrige cada artículo en Notion y luego sincronízalo de nuevo con POST /sites/{site_id}/articles/{article_id}/sync.
Aplicar una sola marca a todos los centros de ayuda.
Ámbito: read y admin
Copia los colores y las fuentes de un centro de ayuda a todos los demás centros de ayuda de tu espacio de trabajo. Establece SOURCE_SITE_ID en el centro de ayuda del que se copiarán los colores y las fuentes.
Cada actualización de la marca actualiza todo el centro de ayuda de destino, por lo que el script envía una solicitud por centro de ayuda. Un color almacenado en un formato corto como #fff se rechaza: corríjalo primero en el panel del centro de ayuda de origen. Consulte Personalizar la marca y los temas del centro de ayuda con la API Notiondesk .
Importar redirecciones después de una migración
Ámbito: read y write
¿Vienes de otra herramienta del centro de ayuda? El script de importación de redirecciones lee un archivo CSV con las rutas antiguas y crea una redirección por cada fila. Consulta Gestionar redirecciones de página con la 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}`);
}Preguntas frecuentes
¿Qué idioma puedo usar con la API Notiondesk?
Cualquier lenguaje que pueda enviar solicitudes HTTPS. La API es REST simple con JSON, por lo que curl, JavaScript, Python, PHP, Go o Ruby funcionan.
¿Dónde debo ejecutar estos scripts?
En un servidor, una tarea programada o una canalización de CI, con la clave almacenada como secreto. Nunca las ejecute en un navegador.
¿Puedo usar estos scripts con OAuth en lugar de una clave API?
Sí. Reemplace la clave API con un token de acceso OAuth en el encabezado Authorization y actualícelo antes de que caduque. Consulte Conectar su aplicación a Notiondesk con OAuth .