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
| Tiene | Use |
|---|---|
| Un registro con un ID estable en su sistema | PUT /v1/cards-by-external-id/{external_id}: crear o sustituir por su ID. |
| Un texto que añadir una vez, sin ID propio | POST /v1/cards/text |
| Un archivo que añadir una vez | POST /v1/cards |
| Un archivo comprimido ZIP con muchos archivos | POST /v1/cards/archive |
| Un archivo mayor que el límite de una sola solicitud | Una subida por partes y, después, cualquiera de los endpoints anteriores. |
| Contenido nuevo para una tarjeta que ya tiene | PUT /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
200con"status": "skipped"y"reason": "unchanged". Repetir la solicitud nunca supone un riesgo, incluso sin unaIdempotency-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
metadatatoman los valores que envió;metadatase 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_type | El cuerpo es |
|---|---|
markdown (predeterminado) | Markdown. |
html | HTML. 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. |
text | Texto 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
409con el códigocards.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:
- Envíe
"async": true, para que cada solicitud responda en cuanto la tarjeta quede en cola. - Ejecute unas pocas solicitudes en paralelo (de 4 a 8) y reduzca el ritmo cuando el encabezado
RateLimitindique que quedan pocas solicitudes. Consulte Límites de frecuencia y cuotas. - Ante un
429, espere lo que indicaRetry-After; ante un error de red o un5xx, reintente la misma solicitud. Las repeticiones no causan ningún problema. - Anote el
external_idde 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
- Lea una tarjeta por su ID con
GET /v1/cards-by-external-id/{external_id}. - Enumere las tarjetas de una importación con un filtro de metadatos:
GET /v1/cards?metadata[source]=helpdesk. Consulte Metadatos. - Elimine una tarjeta con
DELETE /v1/cards/{card_id}.
Errores frecuentes
| Código | Causa | Qué hacer |
|---|---|---|
collections.not_found | La clave no ve ninguna colección con este ID o este nombre. | Compruebe collection_id con GET /v1/collections. |
collections.no_write_access | La clave no puede escribir en esta colección. | Use una clave con el rol editor y acceso a la colección. |
cards.external_id_taken | Un POST con un external_id que ya tiene otra tarjeta. | Use PUT /v1/cards-by-external-id/… para sustituirla. |
cards.managed_by_source | La tarjeta pertenece a una fuente. | Cambie el documento en su fuente. |
files.type_mismatch | El nombre, el tipo y el contenido del archivo no coinciden. | Envíe el archivo con su extensión real. |
files.too_large | El archivo supera el límite de subida o el almacenamiento de la organización. | Use una subida por partes o libere almacenamiento. |
limits.cards | La 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_invalid | metadata supera un límite. | Consulte Metadatos. |