Automatize tarefas comuns da central de ajuda com a API Notiondesk

Exemplos da API Notiondesk: sincronizar do GitHub Actions, fazer backup de artigos em Markdown, enviar feedback para o Slack e relatar pesquisas sem resultados.

7 min de leitura

Este artigo reúne scripts de API prontos para uso para as tarefas que as equipes mais automatizam: publicar edições do Notion a partir de um pipeline de CI, fazer backup de todos os artigos em Markdown, enviar feedback negativo para o Slack, relatar as pesquisas que sua central de ajuda não consegue responder, encontrar sincronizações com falha e aplicar uma única marca a várias centrais de ajuda. Copie uma receita, defina duas variáveis ​​de ambiente e execute-a.

Antes de começar

  • Defina ambas como variáveis ​​de ambiente:
export NOTIONDESK_API_KEY="nd_wsk_..."
export NOTIONDESK_SITE_ID="c74781f6-1aea-4e17-ab23-bbb8b4d174b6"

As receitas em JavaScript requerem Node.js versão 18 ou posterior.

Um pequeno ajudante compartilhado

Salve este arquivo como notiondesk.mjs ao lado de seus scripts. Ele envia os cabeçalhos, aguarda a resposta da API 429 rate_limited e lê cada página de uma lista.

Sincronize sua central de ajuda do GitHub Actions.

Escopo: __PRESERVADO_30__

Publique suas edições do Notion a partir de um fluxo de trabalho do GitHub Actions, por exemplo, logo após um lançamento. Armazene a chave como o segredo do repositório NOTIONDESK_API_KEY e o ID da central de ajuda como a variável do repositório 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 significa que a sincronização está na fila. Ela é executada em segundo plano.
  • 409 conflict_error significa que uma sincronização da central de ajuda já está na fila ou em execução, portanto, a etapa é concluída com sucesso sem a necessidade de iniciar outra.
  • O parâmetro Idempotency-Key faz com que uma solicitação repetida não seja enfileirada duas vezes.
Com a tradução automática ativada, cada sincronização traduz o conteúdo alterado e gera a cobrança correspondente. Acione o fluxo de trabalho após edições reais, não em um curto intervalo de tempo. Consulte Acionar sincronizações do Notion com a API Notiondesk .

Faça backup de todos os artigos em Markdown.

Escopo: __PRESERVADO_24__

Salve todos os artigos, em todos os idiomas que sua central de ajuda oferece, como um arquivo Markdown:

O backup inclui rascunhos e artigos arquivados. As leituras são limitadas a 120 solicitações por minuto, portanto, uma central de ajuda com 500 artigos leva cerca de cinco minutos por idioma; o sistema aguarda automaticamente quando atinge o limite.

Envie feedback negativo para o Slack todos os dias.

Escopo: read apenas

Publique as últimas 24 horas de feedback negativo em um canal do Slack por meio de um webhook de entrada . Defina SLACK_WEBHOOK_URL e execute o script uma vez por dia.

A lista de feedback é organizada do mais recente para o mais antigo, portanto o script para de paginar na primeira entrada com mais de 24 horas.

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")}` }),
  });
}
O feedback é escrito pelos seus leitores. Execute este script com uma chave que contenha apenas caracteres especiais, escape o texto antes de publicá-lo e nunca o passe para um LLM que possua uma chave com permissão de escrita. Consulte Exportar feedback do leitor com a API Notiondesk .read

Denunciar pesquisas sem resultados todas as semanas

Escopo: __PRESERVADO_20__

Liste o que os leitores pesquisaram na semana passada sem encontrar um artigo. Cada consulta é uma candidata para um novo artigo ou um título melhor.

Cada lista de consultas comporta até 50 linhas. Os relatórios analíticos também compartilham um orçamento de 30 solicitações por minuto por central de ajuda. Consulte Obter análises da central de ajuda com a API Notiondesk .

Encontre artigos cuja última sincronização falhou

Escopo: read, mais sync para tentar novamente

Corrija cada artigo no Notion primeiro e, em seguida, sincronize-o novamente com POST /sites/{site_id}/articles/{article_id}/sync.

Aplique uma única marca a todos os centros de ajuda.

Âmbito: read e admin

Copie as cores e fontes de uma central de ajuda para todas as outras centrais de ajuda do seu espaço de trabalho. Defina SOURCE_SITE_ID para a central de ajuda da qual deseja copiar.

Cada atualização de marca atualiza toda a central de ajuda de destino, portanto, o script envia uma solicitação por central de ajuda. Uma cor armazenada em um formato abreviado, como #fff, é rejeitada: corrija-a primeiro no painel da central de ajuda de origem. Consulte Personalizar a marca e os temas da central de ajuda com a API Notiondesk .

Redirecionamentos de importação após uma migração

Escopo: read e write

Está migrando de outra ferramenta de central de ajuda? O script de importação de redirecionamentos lê um arquivo CSV com os caminhos antigos e cria um redirecionamento por linha. Consulte Gerenciar redirecionamentos de página com a 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}`);
}

Perguntas frequentes

Qual idioma posso usar com a API Notiondesk?

Qualquer linguagem que possa enviar solicitações HTTPS. A API é REST pura com JSON, então JavaScript, Python, PHP, Go ou Ruby funcionam.

Onde devo executar esses scripts?

Em um servidor, uma tarefa agendada ou um pipeline de CI, com a chave armazenada como um segredo. Nunca os execute em um navegador.

Posso usar esses scripts com OAuth em vez de uma chave de API?

Sim. Substitua a chave da API por um token de acesso OAuth no cabeçalho Authorization e atualize-o antes que expire. Consulte Conectar seu aplicativo ao Notiondesk com OAuth .

O que vem a seguir?

Artigos relacionados

Esta página foi útil?

Still need help? Ask us