Büyük dosyaları yükleme
Bir dosya, kurulumun yükleme sınırına kadar (varsayılan olarak 200 MB) tek bir
POST /v1/cards isteğiyle gönderilebilir. Bu sınırın üzerinde ya da uzun bir isteğin
kopma olasılığının yüksek olduğu bir ağda dosyayı parçalar hâlinde yükleyin:
- Bir yükleme oluşturun: Sursa’ya dosyanın adını, türünü ve boyutunu bildirin.
- Parçaları gönderin; isterseniz herhangi bir sırayla ve paralel olarak. Başarısız olan bir parça tek başına yeniden gönderilir.
- Yüklemeyi dosyanın tamamının SHA-256 değeriyle tamamlayın. Sursa dosyayı birleştirir ve denetler.
- Dosyanın yerine yükleme kimliğini kullanın: bir kart, bir arşiv ya da bir özel kaynak belgesi için.
| Sınır | Değer |
|---|---|
| Dosya boyutu | En fazla 5 GB |
| Parça boyutu | Sursa tarafından part_size içinde seçilir, 5 MiB ile 100 MiB arası; son parça daha küçük olabilir |
| Parça sayısı | En fazla 1.000 |
| Tamamlama süresi | Yükleme oluşturulduktan sonra 24 saat |
| Tamamlanmış bir yüklemeyi kullanma süresi | 24 saat |
1. Yüklemeyi oluşturun
POST /v1/uploads dosyanın adını, türünü, boyutunu ve dosyayı ne için
kullanacağınızı alır:
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 | Yüklemenin kullanılacağı yer |
|---|---|
card_file | Bir kart: POST /v1/cards. |
archive | Bir belge arşivi: POST /v1/cards/archive. |
import_attachment | Bir özel kaynak belgesinin upload_id ya da attachments içindeki bir dosyası. |
Anahtarın, yüklemenin gideceği yerin kapsamına sahip olması gerekir: card_file ve archive için cards:write,
import_attachment için import:write.
Sursa part_size değerini seçer ve kaç parça göndermeniz gerektiğini bildirir. Dosyanın SHA-256 değerini önceden
biliyorsanız onu burada da sha256 olarak gönderebilirsiniz.
2. Parçaları gönderin
Dosyayı part_size bayt boyutunda part_count parçaya bölün (sonuncusu kalan kısımdır) ve her birini
PUT /v1/uploads/{upload_id}/parts/{n} ile gönderin. Parçalar 1’den başlayarak
numaralandırılır. Gövde, parçanın ham baytlarıdır.
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))
doneBir parçayı yeniden göndermek onu değiştirir; bu yüzden başarısız olan ya da zaman aşımına uğrayan bir parça yalnızca yeniden gönderilir. Paralel olarak en fazla 4 parça göndermek genellikle hızlı bir bağlantıyı tam kapasiteyle kullanmaya yeter.
Parçaları kontrol etme
Her parça API üzerinden gider: depolamaya doğrudan bağlantılar yoktur. Bir parçanın yanıtında ETag başlığı (tırnak
içinde parçanın SHA-256 değeri) ve JSON gövdesinin etag alanında aynı değer bulunur.
GET /v1/uploads/{upload_id} o ana kadar alınan parçaları parts içinde listeler;
böylece bir kopmadan sonra yalnızca eksik parçaları gönderirsiniz.
3. Yüklemeyi tamamlayın
POST /v1/uploads/{upload_id}/complete dosyanın tamamının SHA-256 değerini
64 onaltılık karakter olarak ve isteğe bağlı olarak, parçaları da denetlemek için parts içinde her parçanın etag
değerini alır:
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 parçaları birleştirir, özeti karşılaştırır ve dosyayı her yüklemeyi denetlediği gibi denetler: biçimini ve
etkinse virüs taramasıyla. Denetim bittiğinde yanıt 200, hâlâ sürüyorsa 202 olur. Yüklemenin durumu
processing değerinden ready değerine ya da nedeni reject_reason içinde olmak üzere rejected değerine geçer.
Durumu GET /v1/uploads/{upload_id} ile okuyun.
4. Yüklemeyi kullanın
Dosyayı göndereceğiniz yerde upload_id gönderin. Her yükleme bir kez kullanılır: kullanıldıktan sonra durumu
consumed olur.
Kart ve arşiv için upload_id değerini multipart/form-data formu yerine bir JSON gövdesinde gönderin. Bu gövde
koleksiyonu adıyla, collection alanında alır.
# 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\"}"Eksiksiz bir yükleyici
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));
}
}Hatalar
| Kod | Durum | Neden |
|---|---|---|
uploads.too_large | 413 | Dosya 5 GB sınırını ya da kuruluşun planındaki depolama alanının tamamını aşıyor. |
limits.storage | 402 | Kuruluşun bu dosya için yeterli boş depolama alanı kalmadı. |
uploads.part_invalid | 400 | Aralık dışında bir parça numarası ya da izin verilen boyutun dışında bir parça. |
uploads.checksum_mismatch | 422 | Birleştirilen dosyanın SHA-256 değeri gönderdiğiniz değerle aynı değil. Bir parça bozulmuş ya da eksik: parçaları yeniden gönderin ve yüklemeyi yeniden tamamlayın. |
uploads.not_ready | 409 | Yükleme henüz tamamlanmadı ve denetlenmedi. ready durumunu bekleyin. |
uploads.already_used | 409 | Yükleme zaten kullanıldı. Her yükleme bir kez kullanılır. |
uploads.expired | 410 | 24 saatten fazla zaman geçti. Yeni bir yükleme oluşturun. |
uploads.rejected | 422 | Dosya denetimlerden geçemedi; örneğin biçimi adıyla uyuşmuyor. İleti nedenini belirtir. |
uploads.not_found | 404 | Kuruluşta böyle bir yükleme yok. |