Direct naar de inhoud
Sursa Developers

Documenten importeren

Deze handleiding laat zien hoe je je documenten met de API voor kaarten in Sursa zet: een paar documenten vanuit een script, of elk document van een systeem via een regelmatige job. Elk document wordt een kaart in een collectie. Mensen vinden hem bij het zoeken, de assistent antwoordt op basis ervan, en hij heeft een eigenaar, tags, verificatie en rechten zoals elke andere kaart.

Wil je dat Sursa zelf uitzoekt welke documenten in je systeem zijn verwijderd, of verstuur je documenten vanuit een integratieplatform, gebruik dan een eigen bron. Die is gebouwd voor volledige en incrementele synchronisaties. Deze handleiding behandelt het rechtstreeks schrijven naar een collectie.

Je hebt een API-sleutel nodig met de scope cards:write en een rol die naar de doelcollectie mag schrijven (editor of admin). De voorbeelden gebruiken de hulpfunctie uit de Snelstart.

Het juiste endpoint kiezen

Je hebtGebruik
Een record met een stabiele ID in je systeemPUT /v1/cards-by-external-id/{external_id}: maken of vervangen op je eigen ID.
Tekst die je één keer toevoegt, zonder eigen IDPOST /v1/cards/text
Een bestand dat je één keer toevoegtPOST /v1/cards
Een ZIP-archief met veel bestandenPOST /v1/cards/archive
Een bestand dat groter is dan de limiet voor één requestEen upload in delen, daarna een van de endpoints hierboven.
Nieuwe inhoud voor een kaart die je al hebtPUT /v1/cards/{card_id}/content

Gebruik external_id voor alles wat je meer dan één keer importeert. Daarmee is elke import herhaalbaar: opnieuw uitvoeren werkt de kaarten bij die de import heeft gemaakt, in plaats van kopieën toe te voegen.

Een kaart maken of vervangen op je eigen ID

PUT /v1/cards-by-external-id/{external_id} neemt de ID van het record in je systeem aan. Heeft geen enkele kaart deze external_id, dan wordt er een gemaakt (201). Heeft een kaart hem wel, dan worden de inhoud en de gegevens van die kaart vervangen (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"}
  }'

Wat er gebeurt bij een herhaling:

  • De inhoud is niet veranderd: er wordt geen nieuwe versie gemaakt. De response is 200 met "status": "skipped" en "reason": "unchanged". Het request herhalen is altijd veilig, ook zonder Idempotency-Key.
  • De inhoud is veranderd: de kaart krijgt een nieuwe versie en wordt opnieuw geïndexeerd. De titel, tags, eigenaar, categorie en metadata krijgen de waarden die je hebt gestuurd; metadata wordt in zijn geheel vervangen.
  • De collectie en de map gelden alleen bij het maken van de kaart. Om een kaart later te verplaatsen, wijzig je hem met PATCH /v1/cards/{card_id}.

Een nieuwe versie haalt de verificatie van de kaart weg, zoals elke wijziging van de inhoud: een controleur bevestigt hem opnieuw.

Tekstformaten

content_typeDe body is
markdown (standaard)Markdown.
htmlHTML. Die wordt opgeschoond tot eenvoudige opmaak: koppen, alinea’s, lijsten, tabellen en links met http, https en mailto. Scripts, stijlen, frames, formulieren en afbeeldingen worden met hun inhoud verwijderd.
textPlatte tekst. Alinea’s worden gescheiden door lege regels.

Op de kaart wachten, of niet

Standaard geeft het request antwoord zodra de kaart vindbaar is, met "status": "ready" en de kaart in card. Dat duurt van een fractie van een seconde voor een korte tekst tot een minuut of langer voor een lang bestand.

Stuur bij imports in bulk "async": true mee. De response komt dan meteen, met "status": "queued" en de indexeringsjob in job (met zijn job_id). Controleer de job met GET /v1/jobs/{job_id} of volg alle jobs in GET /v1/jobs/stream. Een job eindigt met status done, error of canceled.

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

Een bestand uploaden

Bestanden gaan in een multipart/form-data-request, in het veld file. Sursa leest PDF, Word, Excel, PowerPoint, OpenDocument-spreadsheets, CSV, Markdown, HTML, AsciiDoc, e-mail (.eml), platte tekst en gangbare afbeeldingsformaten. Het controleert of de naam, het opgegeven type en de inhoud van het bestand overeenkomen, en weigert een afwijking met 415.

Om een bestandskaart te maken of te vervangen op je eigen ID, stuur je het bestand naar hetzelfde 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"}'

In een formulier is tags een string met door komma’s gescheiden waarden en is metadata een JSON-object dat als string is geschreven. Een nieuwe kaart zonder title krijgt de bestandsnaam als titel.

Om een bestand één keer toe te voegen, zonder eigen ID, gebruik je POST /v1/cards met hetzelfde formulier en een Idempotency-Key. Upload je een bestand dat de collectie al heeft, dan krijg je "status": "skipped" terug met "duplicate": true en de bestaande card_id, tenzij je een external_id meegeeft.

De grootte van één bestand wordt begrensd door de installatie (standaard 200 MB). Upload grotere bestanden, of bestanden over onbetrouwbare netwerken, eerst in delen.

De inhoud van een kaart vervangen

Heb je de Sursa-ID van een kaart al, dan vervangt PUT /v1/cards/{card_id}/content de tekst of het bestand ervan en blijft al het andere behouden: eigenaar, tags, controleurs, verificatie-interval en een met de hand ingestelde categorie.

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."}'
  • Dezelfde inhoud nog een keer geeft "status": "skipped" met "reason": "unchanged".
  • Een kaart die op dit moment wordt geïndexeerd, geeft 409 met de code cards.indexing_in_progress. Probeer het over een paar seconden opnieuw.
  • Een kaart die uit een bron komt, zoals een connector of een eigen bron, wordt alleen in die bron gewijzigd: cards.managed_by_source.
  • De sleutel moet de hele kaart mogen zien; een kaart die maar gedeeltelijk zichtbaar is, geeft cards.content_whole_access.

Veel documenten importeren

Een lus over je records met PUT /v1/cards-by-external-id/… is de eenvoudigste import in bulk, en je kunt hem op elk moment veilig stoppen en opnieuw uitvoeren. Zo maak je hem snel en vriendelijk voor de snelheidslimiet:

  1. Stuur "async": true mee, zodat elk request terugkomt zodra de kaart in de wachtrij staat.
  2. Voer een paar requests parallel uit (4 tot 8) en vertraag als de header RateLimit laat zien dat er nog maar weinig requests over zijn. Zie Snelheidslimieten en quota.
  3. Wacht bij 429 de tijd uit Retry-After af; probeer bij een netwerkfout of 5xx hetzelfde request opnieuw. Herhalingen kunnen geen kwaad.
  4. Noteer de external_id van elk record dat op een fout uitloopt, en probeer die aan het eind opnieuw.
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;
}

Voor een ZIP-archief met bestanden maakt POST /v1/cards/archive met één request van elk ondersteund bestand erin een kaart. Het antwoordt meteen met het plan (welke bestanden kaarten worden en welke worden overgeslagen, en waarom) en een job; GET /v1/archives/{job_id} meldt het resultaat.

Geïmporteerde kaarten vinden en verwijderen

Veelvoorkomende fouten

CodeOorzaakWat je doet
collections.not_foundEr is geen collectie met deze ID of naam zichtbaar voor de sleutel.Controleer collection_id met GET /v1/collections.
collections.no_write_accessDe sleutel mag niet naar deze collectie schrijven.Gebruik een sleutel met de rol editor en toegang tot de collectie.
cards.external_id_takenPOST met een external_id die een andere kaart al heeft.Gebruik PUT /v1/cards-by-external-id/… om hem te vervangen.
cards.managed_by_sourceDe kaart hoort bij een bron.Wijzig het document in zijn bron.
files.type_mismatchDe naam, het type en de inhoud van het bestand komen niet overeen.Stuur het bestand met zijn echte extensie.
files.too_largeHet bestand gaat boven de uploadlimiet of de opslag van de organisatie uit.Gebruik een upload in delen of maak opslag vrij.
limits.cardsDe organisatie heeft de kaartlimiet van haar abonnement bereikt.Verwijder kaarten die niet meer nodig zijn, of wijzig het abonnement.
api.metadata_invalidmetadata overschrijdt een limiet.Zie Metadata.