Aller au contenu
Sursa Developers

Importer des documents

Ce guide montre comment placer vos documents dans Sursa avec l’API des fiches : quelques documents depuis un script, ou tous les documents d’un système au moyen d’un job régulier. Chaque document devient une fiche dans une collection. Les gens la trouvent dans la recherche, l’assistant s’en sert pour répondre, et elle a un responsable, des étiquettes, une validation et des permissions comme n’importe quelle autre fiche.

Si vous voulez que Sursa détermine quels documents ont été supprimés dans votre système, ou si vous envoyez des documents depuis une plateforme d’intégration, utilisez plutôt une source personnalisée. Elle est conçue pour les synchronisations complètes et incrémentales. Ce guide traite de l’écriture directe dans une collection.

Il vous faut une clé API dotée de la portée cards:write et d’un rôle qui peut écrire dans la collection cible (editor ou admin). Les exemples utilisent la fonction utilitaire du Démarrage rapide.

Choisir le bon point de terminaison

Vous avezUtilisez
Un enregistrement avec un identifiant stable dans votre systèmePUT /v1/cards-by-external-id/{external_id} : création ou remplacement selon votre identifiant.
Un texte à ajouter une seule fois, sans identifiant proprePOST /v1/cards/text
Un fichier à ajouter une seule foisPOST /v1/cards
Une archive ZIP de nombreux fichiersPOST /v1/cards/archive
Un fichier qui dépasse la limite d’une requête uniqueUn téléversement en plusieurs parties, puis l’un des points de terminaison ci-dessus.
Un nouveau contenu pour une fiche existantePUT /v1/cards/{card_id}/content

Pour tout ce que vous importez plus d’une fois, utilisez external_id. Chaque import devient ainsi répétable : le relancer met à jour les fiches qu’il a créées au lieu d’ajouter des copies.

Créer ou remplacer une fiche selon votre identifiant

PUT /v1/cards-by-external-id/{external_id} prend l’identifiant de l’enregistrement dans votre système. Si aucune fiche ne porte cet external_id, il en crée une (201). Si une fiche le porte, il remplace son contenu et ses détails (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"}
  }'

Ce qui se passe lors d’une répétition :

  • Le contenu n’a pas changé : aucune nouvelle version n’est créée. La réponse est 200 avec "status": "skipped" et "reason": "unchanged". Répéter la requête est toujours sans risque, même sans Idempotency-Key.
  • Le contenu a changé : la fiche reçoit une nouvelle version et elle est réindexée. Son titre, ses étiquettes, son responsable, sa catégorie et ses metadata prennent les valeurs envoyées ; metadata est remplacé en bloc.
  • La collection et le dossier ne s’appliquent qu’à la création de la fiche. Pour déplacer une fiche plus tard, modifiez-la avec PATCH /v1/cards/{card_id}.

Une nouvelle version retire la validation de la fiche, comme toute modification du contenu : un valideur la confirme de nouveau.

Formats de texte

content_typeLe corps est
markdown (par défaut)du Markdown.
htmldu HTML. Il est réduit à un balisage simple : titres, paragraphes, listes, tableaux et liens http, https et mailto. Les scripts, les styles, les cadres, les formulaires et les images sont supprimés avec leur contenu.
textdu texte brut. Les paragraphes sont séparés par des lignes vides.

Attendre la fiche, ou non

Par défaut, la requête répond une fois que la fiche est prête à être trouvée, avec "status": "ready" et la fiche dans card. Cela prend d’une fraction de seconde pour un texte court à une minute ou plus pour un long fichier.

Pour les imports en masse, envoyez "async": true. La réponse arrive immédiatement avec "status": "queued" et le job d’indexation dans job (avec son job_id). Vérifiez le job avec GET /v1/jobs/{job_id}, ou suivez tous les jobs dans GET /v1/jobs/stream. Un job se termine avec status égal à done, error ou canceled.

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

Téléverser un fichier

Les fichiers s’envoient dans une requête multipart/form-data, dans le champ file. Sursa lit les fichiers PDF, Word, Excel, PowerPoint, les feuilles de calcul OpenDocument, CSV, Markdown, HTML, AsciiDoc, les e-mails (.eml), le texte brut et les formats d’image courants. Il vérifie que le nom du fichier, son type déclaré et son contenu concordent, et rejette toute discordance avec 415.

Pour créer ou remplacer une fiche de fichier selon votre identifiant, envoyez le fichier au même 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"}'

Dans un formulaire, tags est une chaîne de valeurs séparées par des virgules et metadata un objet JSON écrit sous forme de chaîne. Une nouvelle fiche sans title prend le nom du fichier comme titre.

Pour ajouter un fichier une seule fois, sans identifiant propre, utilisez POST /v1/cards avec le même formulaire et une Idempotency-Key. Téléverser un fichier que la collection possède déjà renvoie "status": "skipped" avec "duplicate": true et le card_id existant, sauf si vous passez un external_id.

La taille d’un fichier est limitée par l’installation (200 Mo par défaut). Pour les fichiers plus volumineux, ou sur des réseaux peu fiables, téléversez d’abord le fichier en plusieurs parties.

Remplacer le contenu d’une fiche

Quand vous avez déjà l’identifiant Sursa d’une fiche, PUT /v1/cards/{card_id}/content remplace son texte ou son fichier et conserve tout le reste : responsable, étiquettes, valideurs, intervalle de validation et catégorie définie à la main.

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."}'
  • Le même contenu renvoie "status": "skipped" avec "reason": "unchanged".
  • Une fiche en cours d’indexation renvoie 409 avec le code cards.indexing_in_progress. Réessayez dans quelques secondes.
  • Une fiche qui provient d’une source, comme un connecteur ou une source personnalisée, ne se modifie que dans sa source : cards.managed_by_source.
  • La clé doit avoir le droit de voir la fiche entière ; une fiche partiellement visible renvoie cards.content_whole_access.

Importer de nombreux documents

Une boucle sur vos enregistrements avec PUT /v1/cards-by-external-id/… est l’import en masse le plus simple, et vous pouvez l’arrêter et le relancer à tout moment sans risque. Pour qu’il soit rapide et respecte la limite de débit :

  1. Envoyez "async": true, pour que chaque requête réponde dès que la fiche est mise en file d’attente.
  2. Exécutez quelques requêtes en parallèle (de 4 à 8) et ralentissez quand l’en-tête RateLimit indique qu’il reste peu de requêtes. Voir Limites de débit et quotas.
  3. Sur 429, attendez la durée de Retry-After ; sur une erreur réseau ou un 5xx, réessayez la même requête. Les répétitions sont sans conséquence.
  4. Notez l'external_id de chaque enregistrement qui se termine par une erreur, et réessayez ceux-là à la fin.
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;
}

Pour une archive ZIP de fichiers, POST /v1/cards/archive transforme en fiche, en une seule requête, chaque fichier pris en charge qu’elle contient. Ce point de terminaison répond immédiatement avec le plan (quels fichiers deviennent des fiches, lesquels sont ignorés et pourquoi) et un job ; GET /v1/archives/{job_id} indique le résultat.

Retrouver et supprimer les fiches importées

Erreurs courantes

CodeCauseQue faire
collections.not_foundAucune collection portant cet identifiant ou ce nom n’est visible pour la clé.Vérifiez collection_id avec GET /v1/collections.
collections.no_write_accessLa clé n’a pas le droit d’écrire dans cette collection.Utilisez une clé dotée du rôle editor et d’un accès à la collection.
cards.external_id_takenPOST avec un external_id qu’une autre fiche porte déjà.Utilisez PUT /v1/cards-by-external-id/… pour la remplacer.
cards.managed_by_sourceLa fiche appartient à une source.Modifiez le document dans sa source.
files.type_mismatchLe nom, le type et le contenu du fichier ne concordent pas.Envoyez le fichier avec sa véritable extension.
files.too_largeLe fichier dépasse la limite de téléversement ou l’espace de stockage de l’organisation.Utilisez un téléversement en plusieurs parties, ou libérez de l’espace de stockage.
limits.cardsL’organisation a atteint la limite de fiches de son offre.Supprimez les fiches devenues inutiles, ou changez d’offre.
api.metadata_invalidmetadata dépasse une limite.Voir Métadonnées.