Pular para o conteúdo
Sursa Developers

Importar documentos

Este guia mostra como colocar os seus documentos no Sursa com a API de cartões: alguns documentos a partir de um script ou todos os documentos de um sistema por meio de um job periódico. Cada documento vira um cartão em uma coleção. As pessoas o encontram na busca, o assistente responde com base nele, e ele tem responsável, etiquetas, verificação e permissões como qualquer outro cartão.

Se você quer que o Sursa descubra quais documentos foram excluídos no seu sistema, ou se você envia documentos a partir de uma plataforma de integração, use uma fonte personalizada. Ela foi feita para sincronizações completas e incrementais. Este guia trata das gravações diretas em uma coleção.

Você precisa de uma chave de API com o escopo cards:write e uma função que possa gravar na coleção de destino (editor ou admin). Os exemplos usam a função auxiliar do Início rápido.

Escolher o endpoint certo

Você temUse
Um registro com um ID estável no seu sistemaPUT /v1/cards-by-external-id/{external_id}: criar ou substituir pelo seu ID.
Um texto para adicionar uma vez, sem ID próprioPOST /v1/cards/text
Um arquivo para adicionar uma vezPOST /v1/cards
Um arquivo compactado ZIP com muitos arquivosPOST /v1/cards/archive
Um arquivo maior que o limite de uma única requisiçãoUm upload em partes e depois qualquer um dos endpoints acima.
Um novo conteúdo para um cartão que você já temPUT /v1/cards/{card_id}/content

Para tudo o que você importa mais de uma vez, use external_id. Ele torna cada importação repetível: executá-la de novo atualiza os cartões que ela criou, em vez de acrescentar cópias.

Criar ou substituir um cartão pelo seu ID

PUT /v1/cards-by-external-id/{external_id} recebe o ID do registro no seu sistema. Se nenhum cartão tem esse external_id, ele cria um (201). Se um cartão o tem, ele substitui o conteúdo e os detalhes do cartão (200).

curl -X PUT https://api.sursa.com/v1/cards-by-external-id/kb-article-1042 \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "collection_id": 3,
    "folder_id": "0f8e2d7c-4b6a-4e1f-9c3d-2a5b8e7f1c40",
    "title": "Returning a damaged item",
    "content_type": "html",
    "body": "<h2>Damaged items</h2><p>Send a photo within 7 days and we ship a replacement.</p>",
    "tags": ["returns", "shipping"],
    "metadata": {"source": "helpdesk", "article_id": "1042"}
  }'

O que acontece em uma repetição:

  • O conteúdo não mudou: nenhuma versão nova é criada. A resposta é 200 com "status": "skipped" e "reason": "unchanged". Repetir a requisição é sempre seguro, mesmo sem uma Idempotency-Key.
  • O conteúdo mudou: o cartão ganha uma versão nova e é indexado de novo. O título, as etiquetas, o responsável, a categoria e metadata recebem os valores que você enviou; metadata é substituído por inteiro.
  • A coleção e a pasta só valem quando o cartão é criado. Para mover um cartão depois, altere-o com PATCH /v1/cards/{card_id}.

Uma versão nova remove a verificação do cartão, como qualquer alteração de conteúdo: um verificador o confirma de novo.

Formatos de texto

content_typeO corpo é
markdown (padrão)Markdown.
htmlHTML. Ele é limpo até restar uma marcação simples: títulos, parágrafos, listas, tabelas e links http, https e mailto. Scripts, estilos, frames, formulários e imagens são removidos com o conteúdo deles.
textTexto simples. Os parágrafos são separados por linhas em branco.

Esperar pelo cartão, ou não

Por padrão, a requisição retorna quando o cartão já pode ser encontrado, com "status": "ready" e o cartão em card. Isso leva de uma fração de segundo, para um texto curto, a um minuto ou mais, para um arquivo longo.

Nas importações em massa, envie "async": true. A resposta vem na hora, com "status": "queued" e o job de indexação em job (com o job_id dele). Consulte o job com GET /v1/jobs/{job_id} ou acompanhe todos os jobs em GET /v1/jobs/stream. Um job termina com status igual a done, error ou canceled.

curl https://api.sursa.com/v1/jobs/48213 \
  -H "Authorization: Bearer $SURSA_API_KEY"

Enviar um arquivo

Os arquivos vão em uma requisição multipart/form-data, no campo file. O Sursa lê PDF, Word, Excel, PowerPoint, planilhas OpenDocument, CSV, Markdown, HTML, AsciiDoc, e-mail (.eml), texto simples e os formatos de imagem comuns. Ele confere se o nome, o tipo declarado e o conteúdo do arquivo combinam e rejeita uma divergência com 415.

Para criar ou substituir um cartão de arquivo pelo seu ID, envie o arquivo para o mesmo PUT /v1/cards-by-external-id/{external_id}:

curl -X PUT https://api.sursa.com/v1/cards-by-external-id/handbook-2026 \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  -F "file=@employee-handbook.pdf" \
  -F "collection_id=3" \
  -F "tags=hr,policies" \
  -F 'metadata={"owner_team":"people"}'

Em um formulário, tags é uma string separada por vírgulas, e metadata é um objeto JSON escrito como string. Um cartão novo sem title recebe o nome do arquivo como título.

Para adicionar um arquivo uma única vez, sem um ID próprio, use POST /v1/cards com o mesmo formulário e uma Idempotency-Key. Enviar um arquivo que a coleção já tem retorna "status": "skipped" com "duplicate": true e o card_id existente, a menos que você passe um external_id.

O tamanho de um arquivo é limitado pela instalação (200 MB por padrão). Para arquivos maiores, ou em redes instáveis, envie primeiro o arquivo em partes.

Substituir o conteúdo de um cartão

Quando você já tem o ID de um cartão no Sursa, PUT /v1/cards/{card_id}/content substitui o texto ou o arquivo dele e mantém todo o resto: o responsável, as etiquetas, os verificadores, o intervalo de verificação e uma categoria definida à mão.

curl -X PUT https://api.sursa.com/v1/cards/3f2c9a7e-5b1d-4c8e-9f0a-1b2c3d4e5f60/content \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content_type": "markdown", "body": "# Refund policy\n\nRefunds are processed within 10 days."}'
  • O mesmo conteúdo de novo retorna "status": "skipped" com "reason": "unchanged".
  • Um cartão que está sendo indexado neste momento retorna 409 com o código cards.indexing_in_progress. Tente de novo em alguns segundos.
  • Um cartão que vem de uma fonte, como um conector ou uma fonte personalizada, só é alterado na fonte dele: cards.managed_by_source.
  • A chave precisa ter permissão para ver o cartão inteiro; um cartão parcialmente visível retorna cards.content_whole_access.

Importar muitos documentos

Um loop sobre os seus registros com PUT /v1/cards-by-external-id/… é a importação em massa mais simples, e é seguro interrompê-la e executá-la de novo a qualquer momento. Para torná-la rápida e respeitar o limite de taxa:

  1. Envie "async": true, para que cada requisição retorne assim que o cartão entrar na fila.
  2. Execute algumas requisições em paralelo (de 4 a 8) e diminua o ritmo quando o cabeçalho RateLimit mostrar que restam poucas requisições. Veja Limites de taxa e cotas.
  3. Em um 429, aguarde o Retry-After; em um erro de rede ou 5xx, repita a mesma requisição. As repetições não causam danos.
  4. Anote o external_id de cada registro que terminar em erro e tente esses de novo no final.
import { api } from "./sursa.mjs";

async function importAll(records, concurrency = 6) {
  const failed = [];
  const queue = [...records];
  async function worker() {
    for (let record = queue.shift(); record; record = queue.shift()) {
      try {
        await api("PUT", `/v1/cards-by-external-id/${encodeURIComponent(record.id)}`, {
          collection_id: 3,
          title: record.title,
          content_type: "html",
          body: record.html,
          metadata: { source: "helpdesk" },
          async: true,
        });
      } catch (error) {
        failed.push({ id: record.id, error: String(error) });
      }
    }
  }
  await Promise.all(Array.from({ length: concurrency }, worker));
  return failed;
}

Para um arquivo compactado ZIP, POST /v1/cards/archive transforma em cartão cada arquivo compatível que estiver dentro dele, com uma única requisição. Ele responde na hora com o plano (quais arquivos viram cartões, quais são ignorados e por quê) e um job; GET /v1/archives/{job_id} informa o resultado.

Encontrar e remover os cartões importados

Erros comuns

CódigoCausaO que fazer
collections.not_foundNenhuma coleção com esse ID ou nome está visível para a chave.Confira collection_id com GET /v1/collections.
collections.no_write_accessA chave não pode gravar nesta coleção.Use uma chave com a função editor e acesso à coleção.
cards.external_id_takenPOST com um external_id que outro cartão já tem.Use PUT /v1/cards-by-external-id/… para substituí-lo.
cards.managed_by_sourceO cartão pertence a uma fonte.Altere o documento na fonte dele.
files.type_mismatchO nome, o tipo e o conteúdo do arquivo não combinam.Envie o arquivo com a extensão real.
files.too_largeO arquivo passa do limite de upload ou do armazenamento da organização.Use um upload em partes ou libere espaço no armazenamento.
limits.cardsA organização atingiu o limite de cartões do plano.Exclua os cartões que não são mais necessários ou mude o plano.
api.metadata_invalidmetadata ultrapassa um limite.Veja Metadados.