Ir al contenido
Sursa Developers

Importar documentos

Esta guía muestra cómo llevar sus documentos a Sursa con la API de tarjetas: unos pocos documentos desde un script, o todos los documentos de un sistema mediante un trabajo periódico. Cada documento se convierte en una tarjeta de una colección. Las personas la encuentran en la búsqueda, el asistente responde a partir de ella, y tiene responsable, etiquetas, verificación y permisos como cualquier otra tarjeta.

Si quiere que Sursa averigüe qué documentos se eliminaron en su sistema, o si envía documentos desde una plataforma de integración, use en su lugar una fuente personalizada. Está pensada para sincronizaciones completas e incrementales. Esta guía trata de las escrituras directas en una colección.

Necesita una clave de API con el ámbito cards:write y un rol que pueda escribir en la colección de destino (editor o admin). Los ejemplos usan la función auxiliar del Inicio rápido.

Elegir el endpoint adecuado

TieneUse
Un registro con un ID estable en su sistemaPUT /v1/cards-by-external-id/{external_id}: crear o sustituir por su ID.
Un texto que añadir una vez, sin ID propioPOST /v1/cards/text
Un archivo que añadir una vezPOST /v1/cards
Un archivo comprimido ZIP con muchos archivosPOST /v1/cards/archive
Un archivo mayor que el límite de una sola solicitudUna subida por partes y, después, cualquiera de los endpoints anteriores.
Contenido nuevo para una tarjeta que ya tienePUT /v1/cards/{card_id}/content

Para todo lo que importe más de una vez, use external_id. Hace que cada importación se pueda repetir: al volver a ejecutarla, actualiza las tarjetas que creó en lugar de añadir copias.

Crear o sustituir una tarjeta por su ID

PUT /v1/cards-by-external-id/{external_id} recibe el ID del registro en su sistema. Si ninguna tarjeta tiene este external_id, crea una (201). Si una tarjeta lo tiene, sustituye su contenido y sus datos (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"}
  }'

Qué ocurre al repetir la solicitud:

  • El contenido no ha cambiado: no se crea ninguna versión nueva. La respuesta es 200 con "status": "skipped" y "reason": "unchanged". Repetir la solicitud nunca supone un riesgo, incluso sin una Idempotency-Key.
  • El contenido ha cambiado: la tarjeta recibe una versión nueva y se vuelve a indexar. Su título, sus etiquetas, su responsable, su categoría y metadata toman los valores que envió; metadata se sustituye completo.
  • La colección y la carpeta solo se aplican al crear la tarjeta. Para trasladar una tarjeta más adelante, cámbiela con PATCH /v1/cards/{card_id}.

Una versión nueva retira la verificación de la tarjeta, como cualquier cambio de contenido: un verificador vuelve a confirmarla.

Formatos de texto

content_typeEl cuerpo es
markdown (predeterminado)Markdown.
htmlHTML. Se limpia hasta dejar un marcado sencillo: títulos, párrafos, listas, tablas y enlaces http, https y mailto. Los scripts, los estilos, los marcos, los formularios y las imágenes se eliminan con su contenido.
textTexto sin formato. Los párrafos se separan con líneas en blanco.

Esperar a la tarjeta, o no

De forma predeterminada, la solicitud responde cuando la tarjeta ya se puede encontrar, con "status": "ready" y la tarjeta en card. Eso tarda desde una fracción de segundo, en un texto corto, hasta un minuto o más, en un archivo largo.

En las importaciones masivas, envíe "async": true. La respuesta llega de inmediato con "status": "queued" y el trabajo de indexación en job (con su job_id). Compruebe el trabajo con GET /v1/jobs/{job_id} o siga todos los trabajos en GET /v1/jobs/stream. Un trabajo termina con status igual a done, error o canceled.

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

Subir un archivo

Los archivos van en una solicitud multipart/form-data, en el campo file. Sursa lee PDF, Word, Excel, PowerPoint, hojas de cálculo OpenDocument, CSV, Markdown, HTML, AsciiDoc, correo electrónico (.eml), texto sin formato y los formatos de imagen habituales. Comprueba que el nombre del archivo, el tipo declarado y el contenido coinciden, y rechaza una discrepancia con 415.

Para crear o sustituir una tarjeta de archivo por su ID, envíe el archivo al mismo 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"}'

En un formulario, tags es una cadena separada por comas y metadata es un objeto JSON escrito como cadena. Una tarjeta nueva sin title recibe como título el nombre del archivo.

Para añadir un archivo una vez, sin ID propio, use POST /v1/cards con el mismo formulario y una Idempotency-Key. Subir un archivo que la colección ya tiene devuelve "status": "skipped" con "duplicate": true y el card_id existente, salvo que pase un external_id.

El tamaño de un archivo lo limita la instalación (200 MB de forma predeterminada). Para archivos más grandes, o en redes poco fiables, suba primero el archivo por partes.

Sustituir el contenido de una tarjeta

Si ya tiene el ID de Sursa de una tarjeta, PUT /v1/cards/{card_id}/content sustituye su texto o su archivo y conserva todo lo demás: el responsable, las etiquetas, los verificadores, el intervalo de verificación y una categoría asignada a mano.

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."}'
  • El mismo contenido otra vez devuelve "status": "skipped" con "reason": "unchanged".
  • Una tarjeta que se está indexando en este momento devuelve 409 con el código cards.indexing_in_progress. Vuelva a intentarlo en unos segundos.
  • Una tarjeta que procede de una fuente, como un conector o una fuente personalizada, solo se cambia en su fuente: cards.managed_by_source.
  • La clave debe poder ver la tarjeta completa; una tarjeta visible solo en parte devuelve cards.content_whole_access.

Importar muchos documentos

Un bucle sobre sus registros con PUT /v1/cards-by-external-id/… es la importación masiva más sencilla, y se puede detener y volver a ejecutar en cualquier momento sin riesgo. Para que sea rápida y respete el límite de frecuencia:

  1. Envíe "async": true, para que cada solicitud responda en cuanto la tarjeta quede en cola.
  2. Ejecute unas pocas solicitudes en paralelo (de 4 a 8) y reduzca el ritmo cuando el encabezado RateLimit indique que quedan pocas solicitudes. Consulte Límites de frecuencia y cuotas.
  3. Ante un 429, espere lo que indica Retry-After; ante un error de red o un 5xx, reintente la misma solicitud. Las repeticiones no causan ningún problema.
  4. Anote el external_id de cada registro que termine con un error y reintente esos registros al 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 un archivo comprimido ZIP, POST /v1/cards/archive convierte en tarjeta cada archivo compatible que contiene, con una sola solicitud. Responde de inmediato con el plan (qué archivos se convierten en tarjetas, cuáles se omiten y por qué) y un trabajo; GET /v1/archives/{job_id} informa del resultado.

Encontrar y eliminar las tarjetas importadas

Errores frecuentes

CódigoCausaQué hacer
collections.not_foundLa clave no ve ninguna colección con este ID o este nombre.Compruebe collection_id con GET /v1/collections.
collections.no_write_accessLa clave no puede escribir en esta colección.Use una clave con el rol editor y acceso a la colección.
cards.external_id_takenUn POST con un external_id que ya tiene otra tarjeta.Use PUT /v1/cards-by-external-id/… para sustituirla.
cards.managed_by_sourceLa tarjeta pertenece a una fuente.Cambie el documento en su fuente.
files.type_mismatchEl nombre, el tipo y el contenido del archivo no coinciden.Envíe el archivo con su extensión real.
files.too_largeEl archivo supera el límite de subida o el almacenamiento de la organización.Use una subida por partes o libere almacenamiento.
limits.cardsLa organización ha alcanzado el límite de tarjetas de su plan.Elimine las tarjetas que ya no necesite o cambie de plan.
api.metadata_invalidmetadata supera un límite.Consulte Metadatos.