Hopp til innholdet
Sursa Developers

Last opp store filer

En fil kan sendes i én enkelt forespørsel til POST /v1/cards opp til installasjonens grense for opplasting (200 MB som standard). Over det, eller på et nettverk der en lang forespørsel lett blir brutt, laster du opp filen i deler:

  1. Opprett en opplasting: Oppgi navnet, typen og størrelsen på filen.
  2. Send delene, i hvilken som helst rekkefølge og gjerne parallelt. En del som mislykkes, sendes på nytt for seg.
  3. Fullfør opplastingen med SHA-256 for hele filen. Sursa setter sammen filen og kontrollerer den.
  4. Bruk opplastingens ID i stedet for filen: til et kort, et arkiv eller et dokument i en egendefinert kilde.
GrenseVerdi
FilstørrelseOpptil 5 GB
DelstørrelseVelges av Sursa i part_size, fra 5 MiB til 100 MiB; den siste delen kan være mindre
Antall delerOpptil 1000
Tid til fullføring24 timer etter at opplastingen er opprettet
Tid til å bruke en fullført opplasting24 timer

1. Opprett opplastingen

POST /v1/uploads tar navnet, typen og størrelsen på filen og hva du skal bruke den til:

FILE=training-video-transcripts.zip
SIZE=$(wc -c < "$FILE" | tr -d ' ')

curl -X POST https://api.sursa.com/v1/uploads \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d "{\"filename\": \"$FILE\", \"content_type\": \"application/zip\", \"size\": $SIZE, \"purpose\": \"archive\"}"
{
  "id": "upl_5d3c2b1a0f9e8d7c6b5a493827161504",
  "status": "pending",
  "part_size": 8388608,
  "part_count": 23,
  "expires_at": "2026-10-25T10:00:00Z"
}
purposeOpplastingen skal brukes til
card_fileEt kort: POST /v1/cards.
archiveEt arkiv med dokumenter: POST /v1/cards/archive.
import_attachmentEn fil til et dokument i en egendefinert kilde, i upload_id eller attachments.

Nøkkelen trenger scopet for stedet opplastingen skal til: cards:write for card_file og archive, import:write for import_attachment.

Sursa velger part_size og forteller deg hvor mange deler du skal sende. Hvis du kjenner SHA-256 for filen på forhånd, kan du også sende den som sha256 her.

2. Send delene

Del filen i part_count biter på part_size byte (den siste er resten), og send hver av dem med PUT /v1/uploads/{upload_id}/parts/{n}. Delene nummereres fra 1. Brødteksten er de rå bytene i delen.

UPLOAD=upl_5d3c2b1a0f9e8d7c6b5a493827161504
split -b 8388608 -a 4 "$FILE" part-

n=1
for part in part-*; do
  curl -X PUT "https://api.sursa.com/v1/uploads/$UPLOAD/parts/$n" \
    -H "Authorization: Bearer $SURSA_API_KEY" \
    -H "Content-Type: application/octet-stream" \
    --data-binary "@$part" --retry 5 --fail
  n=$((n + 1))
done

Hvis du sender en del på nytt, erstattes den, så en del som mislyktes eller fikk tidsavbrudd, sendes ganske enkelt på nytt. Opptil 4 deler parallelt fyller vanligvis en rask tilkobling.

Kontroller delene

Hver del går gjennom API-et: Det finnes ingen direkte lenker til lagringen. Svaret på en del har headeren ETag, SHA-256 for delen i anførselstegn, og den samme verdien i etag i JSON-brødteksten. GET /v1/uploads/{upload_id} viser delene som er mottatt så langt, i parts, så etter et avbrudd sender du bare de som mangler.

3. Fullfør opplastingen

POST /v1/uploads/{upload_id}/complete tar SHA-256 for hele filen som 64 heksadesimale tegn og eventuelt etag for hver del i parts, for å kontrollere delene også:

SHA=$(shasum -a 256 "$FILE" | cut -d' ' -f1)
curl -X POST "https://api.sursa.com/v1/uploads/$UPLOAD/complete" \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  -H "Idempotency-Key: complete-$UPLOAD" \
  -H "Content-Type: application/json" \
  -d "{\"sha256\": \"$SHA\"}"

Sursa setter sammen delene, sammenligner hashen og kontrollerer filen slik den kontrollerer enhver opplasting: formatet og, der det er slått på, virusskanning. Svaret er 200 når kontrollen er ferdig, og 202 mens den fortsatt pågår. Statusen til opplastingen går fra processing til ready eller til rejected med årsaken i reject_reason. Les den med GET /v1/uploads/{upload_id}.

4. Bruk opplastingen

Send upload_id der du ellers ville sendt filen. Hver opplasting brukes én gang: Etterpå er statusen consumed.

For et kort og et arkiv sender du upload_id i en JSON-brødtekst i stedet for skjemaet multipart/form-data. Denne brødteksten tar samlingen etter navnet i collection.

# A card from the uploaded file
curl -X POST https://api.sursa.com/v1/cards \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  -H "Idempotency-Key: card-from-$UPLOAD" \
  -H "Content-Type: application/json" \
  -d "{\"upload_id\": \"$UPLOAD\", \"collection\": \"support\", \"async\": true}"

# An archive of documents from the uploaded ZIP
curl -X POST https://api.sursa.com/v1/cards/archive \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  -H "Idempotency-Key: archive-from-$UPLOAD" \
  -H "Content-Type: application/json" \
  -d "{\"upload_id\": \"$UPLOAD\", \"collection\": \"support\"}"

Et komplett opplastingsprogram

import { createHash } from "node:crypto";
import { createReadStream } from "node:fs";
import { open, stat } from "node:fs/promises";
import { basename } from "node:path";

const BASE = "https://api.sursa.com";
const auth = { Authorization: `Bearer ${process.env.SURSA_API_KEY}` };

async function call(method, path, body, headers = {}) {
  const isJson = body !== undefined && !(body instanceof Uint8Array);
  const response = await fetch(BASE + path, {
    method,
    headers: { ...auth, ...(isJson ? { "Content-Type": "application/json" } : {}), ...headers },
    body: isJson ? JSON.stringify(body) : body,
  });
  if (!response.ok) throw new Error(`${method} ${path}: HTTP ${response.status} ${await response.text()}`);
  return response.json();
}

export async function uploadLargeFile(file, contentType, purpose, parallel = 4) {
  const { size } = await stat(file);
  const upload = await call("POST", "/v1/uploads",
    { filename: basename(file), content_type: contentType, size, purpose },
    { "Idempotency-Key": crypto.randomUUID() });

  const hash = createHash("sha256");
  for await (const block of createReadStream(file)) hash.update(block);

  const handle = await open(file);

  let next = 1;
  async function worker() {
    for (let n = next++; n <= upload.part_count; n = next++) {
      const buffer = new Uint8Array(Math.min(upload.part_size, size - (n - 1) * upload.part_size));
      await handle.read(buffer, 0, buffer.length, (n - 1) * upload.part_size);
      for (let attempt = 1; ; attempt++) {
        try {
          await call("PUT", `/v1/uploads/${upload.id}/parts/${n}`, buffer, { "Content-Type": "application/octet-stream" });
          break;
        } catch (error) {
          if (attempt === 5) throw error;
          await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** attempt));
        }
      }
    }
  }
  await Promise.all(Array.from({ length: parallel }, worker));
  await handle.close();

  await call("POST", `/v1/uploads/${upload.id}/complete`, { sha256: hash.digest("hex") },
    { "Idempotency-Key": `complete-${upload.id}` });
  for (;;) {
    const state = await call("GET", `/v1/uploads/${upload.id}`);
    if (state.status === "ready") return upload.id;
    if (state.status === "rejected") throw new Error(`upload rejected: ${state.reject_reason}`);
    await new Promise((resolve) => setTimeout(resolve, 2000));
  }
}

Feil

KodeStatusÅrsak
uploads.too_large413Filen er større enn 5 GB eller større enn hele lagringsplassen i organisasjonens prisplan.
limits.storage402Organisasjonen har ikke nok ledig lagringsplass igjen til filen.
uploads.part_invalid400Et delnummer utenfor gyldig område eller en del utenfor den tillatte størrelsen.
uploads.checksum_mismatch422SHA-256 for den sammensatte filen er ikke den du sendte. En del ble skadet eller mangler: Send delene på nytt, og fullfør på nytt.
uploads.not_ready409Opplastingen er ikke fullført og kontrollert ennå. Vent på ready.
uploads.already_used409Opplastingen er allerede brukt. Hver opplasting brukes én gang.
uploads.expired410Det har gått mer enn 24 timer. Opprett en ny opplasting.
uploads.rejected422Filen besto ikke kontrollene, for eksempel stemmer ikke formatet med navnet. Meldingen sier hvorfor.
uploads.not_found404Ingen slik opplasting i organisasjonen.