Încărcați fișiere mari
Un fișier poate fi trimis într-o singură cerere POST /v1/cards până la limita de
încărcare a instalării (implicit 200 MB). Peste această limită sau într-o rețea în care o cerere lungă se poate
întrerupe ușor, încărcați fișierul în părți:
- Creați o încărcare: comunicați-i Sursa numele, tipul și dimensiunea fișierului.
- Trimiteți părțile, în orice ordine și, dacă doriți, în paralel. O parte eșuată se trimite din nou separat.
- Finalizați încărcarea cu SHA-256 al întregului fișier. Sursa asamblează fișierul și îl verifică.
- Folosiți identificatorul încărcării în locul fișierului: pentru o fișă, o arhivă sau un document al unei surse personalizate.
| Limită | Valoare |
|---|---|
| Dimensiunea fișierului | Cel mult 5 GB |
| Dimensiunea unei părți | Aleasă de Sursa în part_size, între 5 MiB și 100 MiB; ultima parte poate fi mai mică |
| Numărul de părți | Cel mult 1.000 |
| Termen de finalizare | 24 de ore de la crearea încărcării |
| Termen de folosire a unei încărcări finalizate | 24 de ore |
1. Creați încărcarea
POST /v1/uploads primește numele, tipul și dimensiunea fișierului, precum și
scopul pentru care îl veți folosi:
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 | Încărcarea va fi folosită pentru |
|---|---|
card_file | O fișă: POST /v1/cards. |
archive | O arhivă de documente: POST /v1/cards/archive. |
import_attachment | Un fișier al unui document dintr-o sursă personalizată, în upload_id sau attachments. |
Cheia are nevoie de domeniul locului în care merge încărcarea: cards:write pentru card_file și archive,
import:write pentru import_attachment.
Sursa alege part_size și vă spune câte părți trebuie trimise. Dacă știți dinainte SHA-256 al fișierului, îl puteți
transmite și aici, ca sha256.
2. Trimiteți părțile
Împărțiți fișierul în part_count bucăți de câte part_size octeți (ultima conține restul) și trimiteți-le pe
fiecare cu PUT /v1/uploads/{upload_id}/parts/{n}. Părțile sunt numerotate de
la 1. Corpul conține octeții bruți ai părții.
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))
doneTrimiterea din nou a unei părți o înlocuiește, așa că o parte care a eșuat sau a expirat se trimite pur și simplu din nou. De obicei, cel mult 4 părți în paralel saturează o conexiune rapidă.
Verificați părțile
Fiecare parte trece prin API: nu există linkuri directe către spațiul de stocare. Răspunsul la o parte are antetul
ETag, adică SHA-256 al părții între ghilimele, și aceeași valoare în etag din corpul JSON.
GET /v1/uploads/{upload_id} enumeră în parts părțile primite până acum, așa că
după o întrerupere trimiteți doar părțile care lipsesc.
3. Finalizați încărcarea
POST /v1/uploads/{upload_id}/complete primește SHA-256 al întregului
fișier ca 64 de caractere hexazecimale și, opțional, etag al fiecărei părți în parts, ca să fie verificate și
părțile:
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 asamblează părțile, compară hash-ul și verifică fișierul la fel ca orice încărcare: formatul lui și, acolo unde
este activată, scanarea antivirus. Răspunsul este 200 când verificarea s-a încheiat și 202 cât timp încă rulează.
Statusul încărcării trece de la processing la ready sau la rejected, cu motivul în reject_reason. Citiți-l cu
GET /v1/uploads/{upload_id}.
4. Folosiți încărcarea
Transmiteți upload_id acolo unde ați trimite fișierul. Fiecare încărcare se folosește o singură dată: după
aceea, statusul ei este consumed.
Pentru o fișă și o arhivă, trimiteți upload_id într-un corp JSON în locul formularului multipart/form-data. Acest
corp primește colecția după numele ei, în 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\"}"Un program complet de încărcare
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));
}
}Erori
| Cod | Status | Cauză |
|---|---|---|
uploads.too_large | 413 | Fișierul depășește 5 GB sau întregul spațiu de stocare al planului organizației. |
limits.storage | 402 | Organizației nu i-a rămas destul spațiu de stocare liber pentru fișier. |
uploads.part_invalid | 400 | Un număr de parte în afara intervalului sau o parte cu o dimensiune nepermisă. |
uploads.checksum_mismatch | 422 | SHA-256 al fișierului asamblat nu este cel trimis. O parte a fost deteriorată sau lipsește: trimiteți din nou părțile și finalizați din nou. |
uploads.not_ready | 409 | Încărcarea nu este încă finalizată și verificată. Așteptați ready. |
uploads.already_used | 409 | Încărcarea a fost deja folosită. Fiecare încărcare se folosește o singură dată. |
uploads.expired | 410 | Au trecut mai mult de 24 de ore. Creați o încărcare nouă. |
uploads.rejected | 422 | Fișierul nu a trecut verificările, de exemplu formatul lui nu corespunde numelui. Mesajul spune motivul. |
uploads.not_found | 404 | Nu există o astfel de încărcare în organizație. |