Μετάβαση στο περιεχόμενο
Sursa Developers

Μεταφόρτωση μεγάλων αρχείων

Ένα αρχείο μπορεί να σταλεί σε ένα μόνο αίτημα του POST /v1/cards έως το όριο μεταφόρτωσης της εγκατάστασης (200 MB από προεπιλογή). Πάνω από αυτό ή σε ένα δίκτυο όπου ένα μεγάλο αίτημα πιθανότατα θα διακοπεί, μεταφορτώστε το αρχείο σε τμήματα:

  1. Δημιουργήστε μια μεταφόρτωση: πείτε στη Sursa το όνομα, τον τύπο και το μέγεθος του αρχείου.
  2. Στείλτε τα τμήματα, με οποιαδήποτε σειρά και, αν θέλετε, παράλληλα. Ένα τμήμα που απέτυχε στέλνεται ξανά μόνο του.
  3. Ολοκληρώστε τη μεταφόρτωση με το SHA-256 ολόκληρου του αρχείου. Η Sursa συναρμολογεί το αρχείο και το ελέγχει.
  4. Χρησιμοποιήστε το ID της μεταφόρτωσης στη θέση του αρχείου: για μια κάρτα, ένα αρχείο ZIP με έγγραφα ή ένα έγγραφο μιας προσαρμοσμένης πηγής.
ΌριοΤιμή
Μέγεθος αρχείουΈως 5 GB
Μέγεθος τμήματοςΕπιλέγεται από τη Sursa στο part_size, από 5 MiB έως 100 MiB· το τελευταίο τμήμα μπορεί να είναι μικρότερο
Αριθμός τμημάτωνΈως 1000
Χρόνος για ολοκλήρωση24 ώρες από τη δημιουργία της μεταφόρτωσης
Χρόνος για χρήση μιας ολοκληρωμένης μεταφόρτωσης24 ώρες

1. Δημιουργήστε τη μεταφόρτωση

Το POST /v1/uploads δέχεται το όνομα, τον τύπο και το μέγεθος του αρχείου, καθώς και το τι σκοπεύετε να κάνετε με αυτό:

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"
}
purposeΗ μεταφόρτωση θα χρησιμοποιηθεί για
card_fileΜια κάρτα: POST /v1/cards.
archiveΈνα αρχείο ZIP με έγγραφα: POST /v1/cards/archive.
import_attachmentΈνα αρχείο ενός εγγράφου μιας προσαρμοσμένης πηγής, στο upload_id ή στο attachments.

Το κλειδί χρειάζεται την εμβέλεια του σημείου στο οποίο πηγαίνει η μεταφόρτωση: cards:write για τα card_file και archive, import:write για το import_attachment.

Η Sursa επιλέγει το part_size και σας λέει πόσα τμήματα να στείλετε. Αν γνωρίζετε εκ των προτέρων το SHA-256 του αρχείου, μπορείτε να το περάσετε και εδώ ως sha256.

2. Στείλτε τα τμήματα

Κόψτε το αρχείο σε part_count κομμάτια των part_size byte (το τελευταίο είναι το υπόλοιπο) και στείλτε το καθένα με PUT /v1/uploads/{upload_id}/parts/{n}. Τα τμήματα αριθμούνται από το 1. Το σώμα είναι τα ακατέργαστα byte του τμήματος.

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

Η εκ νέου αποστολή ενός τμήματος το αντικαθιστά, οπότε ένα τμήμα που απέτυχε ή έληξε το χρονικό του όριο απλώς στέλνεται ξανά. Έως 4 τμήματα παράλληλα συνήθως αξιοποιούν πλήρως μια γρήγορη σύνδεση.

Έλεγχος των τμημάτων

Κάθε τμήμα περνά από το API: δεν υπάρχουν απευθείας σύνδεσμοι προς τον χώρο αποθήκευσης. Η απόκριση σε ένα τμήμα έχει την κεφαλίδα ETag, δηλαδή το SHA-256 του τμήματος σε εισαγωγικά, και την ίδια τιμή στο etag του σώματος JSON. Το GET /v1/uploads/{upload_id} δείχνει στο parts τα τμήματα που έχουν ληφθεί μέχρι στιγμής, οπότε μετά από μια διακοπή στέλνετε μόνο όσα λείπουν.

3. Ολοκληρώστε τη μεταφόρτωση

Το POST /v1/uploads/{upload_id}/complete δέχεται το SHA-256 ολόκληρου του αρχείου ως 64 δεκαεξαδικούς χαρακτήρες και, προαιρετικά, το etag κάθε τμήματος στο parts, για να ελεγχθούν και τα τμήματα:

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 συναρμολογεί τα τμήματα, συγκρίνει την τιμή κατακερματισμού (hash) και ελέγχει το αρχείο όπως ελέγχει κάθε μεταφόρτωση: τη μορφή του και, όπου είναι ενεργοποιημένος, τον έλεγχο για ιούς. Η απόκριση είναι 200, όταν ο έλεγχος έχει ολοκληρωθεί, και 202, όσο εκτελείται ακόμη. Η κατάσταση της μεταφόρτωσης περνά από processing σε ready ή σε rejected με την αιτία στο reject_reason. Διαβάστε τη με GET /v1/uploads/{upload_id}.

4. Χρησιμοποιήστε τη μεταφόρτωση

Περάστε το upload_id εκεί όπου θα στέλνατε το αρχείο. Κάθε μεταφόρτωση χρησιμοποιείται μία φορά: μετά από αυτό, η κατάστασή της είναι consumed.

Για μια κάρτα και ένα αρχείο ZIP, στείλτε το upload_id σε σώμα JSON αντί για τη φόρμα multipart/form-data. Αυτό το σώμα δέχεται τη συλλογή με το όνομά της στο 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\"}"

Ένα πλήρες πρόγραμμα μεταφόρτωσης

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

Σφάλματα

ΚωδικόςΚατάστασηΑιτία
uploads.too_large413Το αρχείο είναι μεγαλύτερο από 5 GB ή από ολόκληρο τον χώρο αποθήκευσης του πακέτου του οργανισμού.
limits.storage402Ο οργανισμός δεν έχει αρκετό ελεύθερο χώρο αποθήκευσης για το αρχείο.
uploads.part_invalid400Αριθμός τμήματος εκτός εύρους ή τμήμα εκτός του επιτρεπόμενου μεγέθους.
uploads.checksum_mismatch422Το SHA-256 του συναρμολογημένου αρχείου δεν είναι αυτό που στείλατε. Ένα τμήμα αλλοιώθηκε ή λείπει: στείλτε ξανά τα τμήματα και ολοκληρώστε ξανά.
uploads.not_ready409Η μεταφόρτωση δεν έχει ακόμη ολοκληρωθεί και ελεγχθεί. Περιμένετε το ready.
uploads.already_used409Η μεταφόρτωση έχει ήδη χρησιμοποιηθεί. Κάθε μεταφόρτωση χρησιμοποιείται μία φορά.
uploads.expired410Έχουν περάσει περισσότερες από 24 ώρες. Δημιουργήστε νέα μεταφόρτωση.
uploads.rejected422Το αρχείο δεν πέρασε τους ελέγχους του, για παράδειγμα η μορφή του δεν αντιστοιχεί στο όνομά του. Το μήνυμα λέει γιατί.
uploads.not_found404Δεν υπάρχει τέτοια μεταφόρτωση στον οργανισμό.