Pujar fitxers grans
Un fitxer es pot enviar en una sola sol·licitud de POST /v1/cards fins al límit de
pujada de la instal·lació (200 MB per defecte). Per sobre d’aquesta mida, o en una xarxa en què és probable que una
sol·licitud llarga es talli, pugeu el fitxer per parts:
- Creeu una pujada: indiqueu a Sursa el nom, el tipus i la mida del fitxer.
- Envieu les parts, en qualsevol ordre i, si voleu, en paral·lel. Una part que falla es torna a enviar tota sola.
- Completeu la pujada amb el SHA-256 de tot el fitxer. Sursa munta el fitxer i el comprova.
- Feu servir l’ID de la pujada en lloc del fitxer: per a una entrada, un arxiu comprimit o un document d’una font personalitzada.
| Límit | Valor |
|---|---|
| Mida del fitxer | Fins a 5 GB |
| Mida de la part | La tria Sursa a part_size, de 5 MiB a 100 MiB; l’última part pot ser més petita |
| Nombre de parts | Fins a 1000 |
| Temps per completar la pujada | 24 hores després de crear-la |
| Temps per fer servir una pujada completada | 24 hores |
1. Crear la pujada
POST /v1/uploads rep el nom, el tipus i la mida del fitxer, i la finalitat per a
la qual el fareu servir:
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 | La pujada es farà servir per a |
|---|---|
card_file | Una entrada: POST /v1/cards. |
archive | Un arxiu comprimit de documents: POST /v1/cards/archive. |
import_attachment | Un fitxer d’un document d’una font personalitzada, a upload_id o attachments. |
La clau necessita l’àmbit del lloc on va la pujada: cards:write per a card_file i archive, import:write per a
import_attachment.
Sursa tria part_size i us indica quantes parts heu d’enviar. Si coneixeu per endavant el SHA-256 del fitxer, també
el podeu passar aquí com a sha256.
2. Enviar les parts
Talleu el fitxer en part_count trossos de part_size bytes (l’últim és la resta) i envieu-los un per un amb
PUT /v1/uploads/{upload_id}/parts/{n}. Les parts es numeren a partir d’1. El cos
són els bytes en brut de la part.
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))
doneSi torneu a enviar una part, la substituïu, de manera que una part que ha fallat o que ha esgotat el temps d’espera simplement es torna a enviar. Amb fins a 4 parts en paral·lel, normalment se satura una connexió ràpida.
Comprovar les parts
Totes les parts passen per l’API: no hi ha enllaços directes a l’emmagatzematge. La resposta a una part té la
capçalera ETag, el SHA-256 de la part entre cometes, i el mateix valor a l’etag del cos JSON.
GET /v1/uploads/{upload_id} llista a parts les parts rebudes fins ara, de manera
que després d’una interrupció només envieu les que falten.
3. Completar la pujada
POST /v1/uploads/{upload_id}/complete rep el SHA-256 de tot el fitxer en 64
caràcters hexadecimals i, opcionalment, l’etag de cada part a parts, per comprovar també les 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 munta les parts, compara el hash i comprova el fitxer com comprova qualsevol pujada: el format i, on està
activada, l’anàlisi antivirus. La resposta és 200 quan la comprovació ha acabat i 202 mentre encara s’executa.
L’estat de la pujada passa de processing a ready, o a rejected amb el motiu a reject_reason. Consulteu-lo amb
GET /v1/uploads/{upload_id}.
4. Fer servir la pujada
Passeu upload_id on enviaríeu el fitxer. Cada pujada es fa servir una sola vegada: després, el seu estat és
consumed.
Per a una entrada i un arxiu comprimit, envieu upload_id en un cos JSON en lloc del formulari
multipart/form-data. Aquest cos rep la col·lecció pel seu nom a 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 programa de pujada complet
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));
}
}Errors
| Codi | Estat | Causa |
|---|---|---|
uploads.too_large | 413 | El fitxer supera els 5 GB o tot l’emmagatzematge del pla de l’organització. |
limits.storage | 402 | A l’organització no li queda prou emmagatzematge lliure per al fitxer. |
uploads.part_invalid | 400 | Un número de part fora de l’interval o una part fora de la mida permesa. |
uploads.checksum_mismatch | 422 | El SHA-256 del fitxer muntat no és el que heu enviat. Una part s’ha malmès o falta: torneu a enviar les parts i torneu a completar la pujada. |
uploads.not_ready | 409 | La pujada encara no s’ha completat i comprovat. Espereu l’estat ready. |
uploads.already_used | 409 | La pujada ja s’ha fet servir. Cada pujada es fa servir una sola vegada. |
uploads.expired | 410 | Han passat més de 24 hores. Creeu una pujada nova. |
uploads.rejected | 422 | El fitxer no ha superat les comprovacions, per exemple perquè el format no correspon al nom. El missatge n’indica el motiu. |
uploads.not_found | 404 | No hi ha cap pujada amb aquest ID a l’organització. |