Pāriet uz saturu
Sursa Developers

Lielu failu augšupielāde

Failu vienā POST /v1/cards pieprasījumā var nosūtīt līdz instalācijas augšupielādes limitam (pēc noklusējuma 200 MB). Lielākus failus, kā arī tīklā, kur garš pieprasījums, visticamāk, pārtrūks, augšupielādējiet pa daļām:

  1. Izveidojiet augšupielādi: paziņojiet Sursai faila nosaukumu, tipu un izmēru.
  2. Nosūtiet daļas jebkurā secībā, ja vēlaties – paralēli. Neizdevusies daļa tiek nosūtīta vēlreiz atsevišķi.
  3. Pabeidziet augšupielādi ar visa faila SHA-256. Sursa saliek failu un pārbauda to.
  4. Izmantojiet augšupielādes ID faila vietā: kartītei, arhīvam vai pielāgota avota dokumentam.
LimitsVērtība
Faila izmērsLīdz 5 GB
Daļas izmērsTo izvēlas Sursa laukā part_size, no 5 MiB līdz 100 MiB; pēdējā daļa var būt mazāka
Daļu skaitsLīdz 1000
Laiks pabeigšanai24 stundas pēc augšupielādes izveides
Laiks pabeigtas augšupielādes izmantošanai24 stundas

1. Izveidojiet augšupielādi

POST /v1/uploads pieņem faila nosaukumu, tipu un izmēru, kā arī to, kam failu izmantosiet:

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"
}
purposeAugšupielāde tiks izmantota
card_fileKartītei: POST /v1/cards.
archiveDokumentu arhīvam: POST /v1/cards/archive.
import_attachmentPielāgota avota dokumenta failam laukā upload_id vai attachments.

Atslēgai vajadzīgs tās vietas tvērums, kur augšupielāde tiks izmantota: cards:write vērtībām card_file un archive, import:write vērtībai import_attachment.

Sursa izvēlas part_size un paziņo, cik daļu jānosūta. Ja faila SHA-256 jums ir zināms jau iepriekš, varat to nodot arī šeit kā sha256.

2. Nosūtiet daļas

Sadaliet failu part_count gabalos pa part_size baitiem (pēdējais ir atlikums) un katru nosūtiet ar PUT /v1/uploads/{upload_id}/parts/{n}. Daļas numurē no 1. Saturs ir daļas neapstrādātie baiti.

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

Atkārtoti nosūtīta daļa aizstāj iepriekšējo, tāpēc daļu, kuras nosūtīšana neizdevās vai beidzās ar noildzi, vienkārši nosūta vēlreiz. Ātru savienojumu parasti pilnībā noslogo līdz 4 paralēli sūtītām daļām.

Pārbaudiet daļas

Katra daļa iet caur API: tiešu saišu uz krātuvi nav. Atbildē uz daļu ir tās galvene ETag – daļas SHA-256 pēdiņās – un tā pati vērtība JSON satura laukā etag. GET /v1/uploads/{upload_id} laukā parts uzskaita līdz šim saņemtās daļas, tāpēc pēc pārtraukuma jūs nosūtāt tikai trūkstošās.

3. Pabeidziet augšupielādi

POST /v1/uploads/{upload_id}/complete pieņem visa faila SHA-256 kā 64 heksadecimālas rakstzīmes un, ja vēlaties, arī katras daļas etag laukā parts, lai pārbaudītu arī daļas:

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 saliek daļas, salīdzina jaucējvērtību un pārbauda failu tāpat kā jebkuru augšupielādi: tā formātu un, ja tas ir ieslēgts, ar pretvīrusu programmu. Atbilde ir 200, kad pārbaude ir pabeigta, un 202, kamēr tā vēl notiek. Augšupielādes statuss mainās no processing uz ready vai uz rejected ar iemeslu laukā reject_reason. Nolasiet to ar GET /v1/uploads/{upload_id}.

4. Izmantojiet augšupielādi

Nododiet upload_id tur, kur sūtītu failu. Katra augšupielāde tiek izmantota vienreiz: pēc tam tās statuss ir consumed.

Kartītei un arhīvam nosūtiet upload_id JSON saturā, nevis multipart/form-data formā. Šajā saturā kolekciju norāda ar tās nosaukumu laukā 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\"}"

Pilns augšupielādes rīks

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

Kļūdas

KodsStatussCēlonis
uploads.too_large413Fails ir lielāks par 5 GB vai par visu organizācijas plāna krātuves apjomu.
limits.storage402Organizācijai nav pietiekami daudz brīvas krātuves šim failam.
uploads.part_invalid400Daļas numurs ir ārpus diapazona, vai daļas izmērs nav atļautajās robežās.
uploads.checksum_mismatch422Saliktā faila SHA-256 nesakrīt ar nosūtīto. Kāda daļa ir bojāta vai trūkst: nosūtiet daļas vēlreiz un vēlreiz pabeidziet augšupielādi.
uploads.not_ready409Augšupielāde vēl nav pabeigta un pārbaudīta. Gaidiet ready.
uploads.already_used409Augšupielāde jau ir izmantota. Katru augšupielādi izmanto vienreiz.
uploads.expired410Pagājušas vairāk nekā 24 stundas. Izveidojiet jaunu augšupielādi.
uploads.rejected422Fails neizturēja pārbaudes, piemēram, tā formāts neatbilst nosaukumam. Ziņojumā ir norādīts iemesls.
uploads.not_found404Organizācijā šādas augšupielādes nav.