Last opp store filer
En fil kan sendes i én enkelt forespørsel til POST /v1/cards opp til
installasjonens grense for opplasting (200 MB som standard). Over det, eller på et nettverk der en lang forespørsel
lett blir brutt, laster du opp filen i deler:
- Opprett en opplasting: Oppgi navnet, typen og størrelsen på filen.
- Send delene, i hvilken som helst rekkefølge og gjerne parallelt. En del som mislykkes, sendes på nytt for seg.
- Fullfør opplastingen med SHA-256 for hele filen. Sursa setter sammen filen og kontrollerer den.
- Bruk opplastingens ID i stedet for filen: til et kort, et arkiv eller et dokument i en egendefinert kilde.
| Grense | Verdi |
|---|---|
| Filstørrelse | Opptil 5 GB |
| Delstørrelse | Velges av Sursa i part_size, fra 5 MiB til 100 MiB; den siste delen kan være mindre |
| Antall deler | Opptil 1000 |
| Tid til fullføring | 24 timer etter at opplastingen er opprettet |
| Tid til å bruke en fullført opplasting | 24 timer |
1. Opprett opplastingen
POST /v1/uploads tar navnet, typen og størrelsen på filen og hva du skal bruke
den til:
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 | Opplastingen skal brukes til |
|---|---|
card_file | Et kort: POST /v1/cards. |
archive | Et arkiv med dokumenter: POST /v1/cards/archive. |
import_attachment | En fil til et dokument i en egendefinert kilde, i upload_id eller attachments. |
Nøkkelen trenger scopet for stedet opplastingen skal til: cards:write for card_file og archive, import:write
for import_attachment.
Sursa velger part_size og forteller deg hvor mange deler du skal sende. Hvis du kjenner SHA-256 for filen på
forhånd, kan du også sende den som sha256 her.
2. Send delene
Del filen i part_count biter på part_size byte (den siste er resten), og send hver av dem med
PUT /v1/uploads/{upload_id}/parts/{n}. Delene nummereres fra 1. Brødteksten er de
rå bytene i delen.
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))
doneHvis du sender en del på nytt, erstattes den, så en del som mislyktes eller fikk tidsavbrudd, sendes ganske enkelt på nytt. Opptil 4 deler parallelt fyller vanligvis en rask tilkobling.
Kontroller delene
Hver del går gjennom API-et: Det finnes ingen direkte lenker til lagringen. Svaret på en del har headeren ETag,
SHA-256 for delen i anførselstegn, og den samme verdien i etag i JSON-brødteksten.
GET /v1/uploads/{upload_id} viser delene som er mottatt så langt, i parts, så
etter et avbrudd sender du bare de som mangler.
3. Fullfør opplastingen
POST /v1/uploads/{upload_id}/complete tar SHA-256 for hele filen som 64
heksadesimale tegn og eventuelt etag for hver del i parts, for å kontrollere delene også:
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 setter sammen delene, sammenligner hashen og kontrollerer filen slik den kontrollerer enhver opplasting:
formatet og, der det er slått på, virusskanning. Svaret er 200 når kontrollen er ferdig, og 202 mens den fortsatt
pågår. Statusen til opplastingen går fra processing til ready eller til rejected med årsaken i reject_reason.
Les den med GET /v1/uploads/{upload_id}.
4. Bruk opplastingen
Send upload_id der du ellers ville sendt filen. Hver opplasting brukes én gang: Etterpå er statusen consumed.
For et kort og et arkiv sender du upload_id i en JSON-brødtekst i stedet for skjemaet multipart/form-data. Denne
brødteksten tar samlingen etter navnet i 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\"}"Et komplett opplastingsprogram
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));
}
}Feil
| Kode | Status | Årsak |
|---|---|---|
uploads.too_large | 413 | Filen er større enn 5 GB eller større enn hele lagringsplassen i organisasjonens prisplan. |
limits.storage | 402 | Organisasjonen har ikke nok ledig lagringsplass igjen til filen. |
uploads.part_invalid | 400 | Et delnummer utenfor gyldig område eller en del utenfor den tillatte størrelsen. |
uploads.checksum_mismatch | 422 | SHA-256 for den sammensatte filen er ikke den du sendte. En del ble skadet eller mangler: Send delene på nytt, og fullfør på nytt. |
uploads.not_ready | 409 | Opplastingen er ikke fullført og kontrollert ennå. Vent på ready. |
uploads.already_used | 409 | Opplastingen er allerede brukt. Hver opplasting brukes én gang. |
uploads.expired | 410 | Det har gått mer enn 24 timer. Opprett en ny opplasting. |
uploads.rejected | 422 | Filen besto ikke kontrollene, for eksempel stemmer ikke formatet med navnet. Meldingen sier hvorfor. |
uploads.not_found | 404 | Ingen slik opplasting i organisasjonen. |