Siirry sisältöön
Sursa Developers

Suurten tiedostojen lataaminen

Tiedoston voi lähettää yhdessä POST /v1/cards -pyynnössä asennuksen latausrajaan asti (oletuksena 200 Mt). Sitä suuremmat tiedostot sekä tiedostot, jotka lähetetään verkossa, jossa pitkä pyyntö todennäköisesti katkeaa, kannattaa ladata osissa:

  1. Luo lataus: kerro Sursalle tiedoston nimi, tyyppi ja koko.
  2. Lähetä osat missä järjestyksessä tahansa ja halutessasi rinnakkain. Epäonnistunut osa lähetetään uudelleen yksinään.
  3. Viimeistele lataus koko tiedoston SHA-256-tiivisteellä. Sursa kokoaa tiedoston ja tarkistaa sen.
  4. Käytä latauksen tunnusta tiedoston sijaan: kortissa, arkistossa tai mukautetun lähteen asiakirjassa.
RajaArvo
Tiedoston kokoEnintään 5 Gt
Osan kokoSursa valitsee sen part_size-kentässä, 5–100 MiB; viimeinen osa voi olla pienempi
Osien määräEnintään 1 000
Aika viimeistelyyn24 tuntia latauksen luomisesta
Aika viimeistellyn latauksen käyttämiseen24 tuntia

1. Luo lataus

POST /v1/uploads ottaa tiedoston nimen, tyypin ja koon sekä tiedon siitä, mihin aiot käyttää sitä:

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"
}
purposeLatauksen käyttökohde
card_fileKortti: POST /v1/cards.
archiveAsiakirja-arkisto: POST /v1/cards/archive.
import_attachmentMukautetun lähteen asiakirjan tiedosto upload_id- tai attachments-kentässä.

Avaimella täytyy olla sen kohteen käyttöalue, johon lataus menee: cards:write käyttötarkoituksille card_file ja archive, import:write käyttötarkoitukselle import_attachment.

Sursa valitsee part_size-arvon ja kertoo, montako osaa lähetetään. Jos tiedät tiedoston SHA-256-tiivisteen etukäteen, voit antaa sen jo tässä sha256-kenttänä.

2. Lähetä osat

Jaa tiedosto part_count palaan, joista kunkin koko on part_size tavua (viimeinen on loput), ja lähetä kukin PUT /v1/uploads/{upload_id}/parts/{n} -pyynnöllä. Osat numeroidaan ykkösestä alkaen. Runko on osan raakatavut.

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

Osan lähettäminen uudelleen korvaa sen, joten epäonnistunut tai aikakatkaistu osa yksinkertaisesti lähetetään uudelleen. Enintään 4 rinnakkaista osaa riittää yleensä kuormittamaan nopean yhteyden täysin.

Osien tarkistaminen

Jokainen osa kulkee API:n kautta: suoria linkkejä tallennustilaan ei ole. Osan vastauksessa on ETag-otsake, joka on osan SHA-256-tiiviste lainausmerkeissä, ja sama arvo on JSON-rungon etag-kentässä. GET /v1/uploads/{upload_id} listaa tähän mennessä vastaanotetut osat parts-kentässä, joten katkon jälkeen lähetät vain puuttuvat osat.

3. Viimeistele lataus

POST /v1/uploads/{upload_id}/complete ottaa koko tiedoston SHA-256-tiivisteen 64 heksadesimaalimerkkinä ja valinnaisesti kunkin osan etag-arvon parts-kentässä, jotta myös osat tarkistetaan:

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 kokoaa osat, vertaa tiivistettä ja tarkistaa tiedoston samoin kuin minkä tahansa latauksen: sen muodon ja, jos se on käytössä, virustarkistuksella. Vastaus on 200, kun tarkistus on valmis, ja 202, kun se on vielä kesken. Latauksen tila muuttuu tilasta processing tilaan ready tai tilaan rejected, jolloin syy on reject_reason-kentässä. Lue tila GET /v1/uploads/{upload_id} -pyynnöllä.

4. Käytä latausta

Anna upload_id siinä kohdassa, jossa lähettäisit tiedoston. Jokainen lataus käytetään kerran: sen jälkeen sen tila on consumed.

Kortille ja arkistolle lähetä upload_id JSON-rungossa multipart/form-data-lomakkeen sijaan. Tämä runko ottaa kokoelman sen nimen mukaan collection-kentässä.

# 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\"}"

Täydellinen latausohjelma

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));
  }
}

Virheet

KoodiTilaSyy
uploads.too_large413Tiedosto on suurempi kuin 5 Gt tai suurempi kuin organisaation paketin koko tallennustila.
limits.storage402Organisaatiolla ei ole tiedostolle riittävästi vapaata tallennustilaa jäljellä.
uploads.part_invalid400Osan numero on sallitun alueen ulkopuolella, tai osan koko ei ole sallittu.
uploads.checksum_mismatch422Kootun tiedoston SHA-256-tiiviste ei ole se, jonka lähetit. Jokin osa on vioittunut tai puuttuu: lähetä osat uudelleen ja viimeistele lataus uudelleen.
uploads.not_ready409Latausta ei ole vielä viimeistelty ja tarkistettu. Odota tilaa ready.
uploads.already_used409Lataus on jo käytetty. Jokainen lataus käytetään kerran.
uploads.expired410Aikaa on kulunut yli 24 tuntia. Luo uusi lataus.
uploads.rejected422Tiedosto ei läpäissyt tarkistuksiaan, esimerkiksi sen muoto ei vastaa sen nimeä. Virheilmoitus kertoo syyn.
uploads.not_found404Organisaatiossa ei ole tällaista latausta.