Upload store filer
En fil kan sendes i én anmodning til POST /v1/cards op til installationens grænse
for uploads (som standard 200 MB). Er filen større, eller er du på et netværk, hvor en lang anmodning sandsynligvis
bliver afbrudt, så upload filen i dele:
- Opret en upload: Fortæl Sursa filens navn, type og størrelse.
- Send delene i vilkårlig rækkefølge, gerne parallelt. En del, der mislykkes, sendes igen for sig.
- Afslut uploaden med SHA-256 for hele filen. Sursa samler filen og kontrollerer den.
- Brug uploadens id i stedet for filen: til et kort, et arkiv eller et dokument i en brugerdefineret kilde.
| Grænse | Værdi |
|---|---|
| Filstørrelse | Op til 5 GB |
| Delstørrelse | Vælges af Sursa i part_size, fra 5 MiB til 100 MiB; den sidste del kan være mindre |
| Antal dele | Op til 1000 |
| Frist for afslutning | 24 timer efter, at uploaden er oprettet |
| Frist for brug af en afsluttet upload | 24 timer |
1. Opret uploaden
POST /v1/uploads tager filens navn, type og størrelse og det, du vil bruge 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 | Uploaden bruges 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 brugerdefineret kilde, i upload_id eller attachments. |
Nøglen skal have scopet for det sted, uploaden skal bruges: cards:write til card_file og archive, import:write
til import_attachment.
Sursa vælger part_size og fortæller dig, hvor mange dele du skal sende. Kender du filens SHA-256 på forhånd, kan du
også angive den her som sha256.
2. Send delene
Del filen i part_count stykker på part_size byte (det sidste stykke er resten), og send hvert stykke med
PUT /v1/uploads/{upload_id}/parts/{n}. Delene nummereres fra 1. Brødteksten er
delens rå bytes.
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))
doneSender du en del igen, erstattes den, så en del, der mislykkedes eller fik timeout, blot sendes igen. Op til 4 dele parallelt udnytter som regel en hurtig forbindelse fuldt ud.
Kontrollér delene
Alle dele går gennem API’et: Der er ingen direkte links til lageret. Svaret på en del har headeren ETag, delens
SHA-256 i anførselstegn, og den samme værdi i etag i JSON-brødteksten.
GET /v1/uploads/{upload_id} viser de dele, der er modtaget indtil nu, i parts, så
du efter en afbrydelse kun sender dem, der mangler.
3. Afslut uploaden
POST /v1/uploads/{upload_id}/complete tager SHA-256 for hele filen som 64
hexadecimale tegn og eventuelt etag for hver del i parts for også at kontrollere delene:
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 samler delene, sammenligner hashen og kontrollerer filen, som den kontrollerer enhver upload: dens format og,
hvor det er slået til, antivirus. Svaret er 200, når kontrollen er færdig, og 202, mens den stadig kører. Uploadens
status går fra processing til ready eller til rejected med årsagen i reject_reason. Læs den med
GET /v1/uploads/{upload_id}.
4. Brug uploaden
Angiv upload_id der, hvor du ellers ville sende filen. Hver upload bruges én gang: Derefter er dens status
consumed.
Til et kort og et arkiv sender du upload_id i en JSON-brødtekst i stedet for formularen multipart/form-data. Denne
brødtekst angiver samlingen med dens navn 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 komplet uploadprogram
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));
}
}Fejl
| Kode | Status | Årsag |
|---|---|---|
uploads.too_large | 413 | Filen er større end 5 GB eller større end hele lagerpladsen i organisationens prisplan. |
limits.storage | 402 | Organisationen har ikke nok ledig lagerplads tilbage til filen. |
uploads.part_invalid | 400 | Et delnummer uden for intervallet eller en del uden for den tilladte størrelse. |
uploads.checksum_mismatch | 422 | SHA-256 for den samlede fil er ikke den, du sendte. En del er beskadiget eller mangler: Send delene igen, og afslut igen. |
uploads.not_ready | 409 | Uploaden er endnu ikke afsluttet og kontrolleret. Vent på ready. |
uploads.already_used | 409 | Uploaden er allerede brugt. Hver upload bruges én gang. |
uploads.expired | 410 | Der er gået mere end 24 timer. Opret en ny upload. |
uploads.rejected | 422 | Filen bestod ikke kontrollen, fx passer dens format ikke til navnet. Meddelelsen fortæller hvorfor. |
uploads.not_found | 404 | Ingen sådan upload i organisationen. |