Μεταφόρτωση μεγάλων αρχείων
Ένα αρχείο μπορεί να σταλεί σε ένα μόνο αίτημα του POST /v1/cards έως το όριο
μεταφόρτωσης της εγκατάστασης (200 MB από προεπιλογή). Πάνω από αυτό ή σε ένα δίκτυο όπου ένα μεγάλο αίτημα πιθανότατα
θα διακοπεί, μεταφορτώστε το αρχείο σε τμήματα:
- Δημιουργήστε μια μεταφόρτωση: πείτε στη Sursa το όνομα, τον τύπο και το μέγεθος του αρχείου.
- Στείλτε τα τμήματα, με οποιαδήποτε σειρά και, αν θέλετε, παράλληλα. Ένα τμήμα που απέτυχε στέλνεται ξανά μόνο του.
- Ολοκληρώστε τη μεταφόρτωση με το SHA-256 ολόκληρου του αρχείου. Η Sursa συναρμολογεί το αρχείο και το ελέγχει.
- Χρησιμοποιήστε το 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_large | 413 | Το αρχείο είναι μεγαλύτερο από 5 GB ή από ολόκληρο τον χώρο αποθήκευσης του πακέτου του οργανισμού. |
limits.storage | 402 | Ο οργανισμός δεν έχει αρκετό ελεύθερο χώρο αποθήκευσης για το αρχείο. |
uploads.part_invalid | 400 | Αριθμός τμήματος εκτός εύρους ή τμήμα εκτός του επιτρεπόμενου μεγέθους. |
uploads.checksum_mismatch | 422 | Το SHA-256 του συναρμολογημένου αρχείου δεν είναι αυτό που στείλατε. Ένα τμήμα αλλοιώθηκε ή λείπει: στείλτε ξανά τα τμήματα και ολοκληρώστε ξανά. |
uploads.not_ready | 409 | Η μεταφόρτωση δεν έχει ακόμη ολοκληρωθεί και ελεγχθεί. Περιμένετε το ready. |
uploads.already_used | 409 | Η μεταφόρτωση έχει ήδη χρησιμοποιηθεί. Κάθε μεταφόρτωση χρησιμοποιείται μία φορά. |
uploads.expired | 410 | Έχουν περάσει περισσότερες από 24 ώρες. Δημιουργήστε νέα μεταφόρτωση. |
uploads.rejected | 422 | Το αρχείο δεν πέρασε τους ελέγχους του, για παράδειγμα η μορφή του δεν αντιστοιχεί στο όνομά του. Το μήνυμα λέει γιατί. |
uploads.not_found | 404 | Δεν υπάρχει τέτοια μεταφόρτωση στον οργανισμό. |