Uploading objects
Create a resumable upload session, send the bytes over tus, and let Gigadrive Network verify and finalize the object.
Every upload through the Gigadrive Network API is a session: you declare the key, the exact byte length and the SHA-256 up front, then send the bytes to a signed URL that speaks tus 1.0.0. The SDK does all of it in one call, and the protocol underneath is documented here for clients that drive it directly.
Uploading a file
The following example runs inside a deployed Function, where the client needs no configuration because the platform injects credentials. See OIDC federation for what it reads and how to get the same credentials outside a deployment.
import { GigadriveClient } from '@gigadrive/sdk';
const client = new GigadriveClient();
const { object, url } = await client.storage.upload({
bucket: 'user-uploads',
key: 'avatars/user-123.png',
path: './avatar.png',
waitForCompletion: true,
});
console.log(url, object?.contentLength);Over REST it is two calls: create the session, then PATCH the bytes to the signed upload.url the response hands back. One PATCH covers a small file; a large one is sent as a series of them. Creating a session needs the network:storage_objects:write scope, and the object appears in the bucket as soon as the last chunk lands, without a second call from you.
What a session declares
| Field | Required | Notes |
|---|---|---|
key | Yes | 1 to 1024 characters |
contentLength | Yes | Exact size in bytes, and the hard cap for the upload |
checksumSha256 | Yes | Lowercase hex, 64 characters |
contentType | No | Inferred from the key when omitted |
checksumSha1, checksumMd5 | No | Stored on the object, never verified |
The SHA-256 is mandatory because storage is content addressed: it is both the integrity check and the identity the bytes are stored under. Send the wrong one and the upload is rejected at the end rather than silently stored. SHA-1 and MD5 are carried through to the object record for clients that expect them, and nothing compares them against the bytes.
Session lifetime and states
A session and the upload URL issued with it expire 15 minutes after creation. session.expiresAt is the expiry of both, because the signed token inside the URL carries the same instant.
| State | Meaning |
|---|---|
pending | Transient, while backend staging is set up. Create returns ready. |
ready | The URL accepts bytes |
completed | Verified and finalized, the object exists |
failed | The upload was terminated, or the backend rejected it |
expired | The window closed before the last chunk landed |
Past expiresAt the token is refused with a 401 and the bytes already sent are lost. A session that was terminated or failed answers 410 instead. Neither can be revived: create a new session and upload again. GET /applications/{applicationId}/storage/buckets/{bucketRef}/uploads/{sessionId} reads the current state, which is what client.storage.upload({ waitForCompletion: true }) polls.
The upload endpoint
The URL points at /files/<sessionId> on the API host and carries its own credential as a query parameter:
https://api.gigadrive.network/files/0197b2f6-7082-712d-9f7e-777777777777?upload_token=eyJ2ZXJzaW9uIjox...That token authorizes exactly one key, one length and one digest, which is what makes the URL safe to hand to a browser. It can also travel as Authorization: Bearer <token> if you would rather keep it out of the URL. The session id in the path has to match the one inside the token.
| Method | What it does |
|---|---|
HEAD | Returns Upload-Offset and Upload-Length. A GET is answered the same way. |
PATCH | Appends bytes at Upload-Offset, and answers 204 with the new offset. |
DELETE | Terminates the upload and marks the session failed. A finished upload gets a 400. |
OPTIONS | Advertises the protocol version, the extensions, and Tus-Max-Size. |
Every request except OPTIONS carries Tus-Resumable: 1.0.0, and one that does not is answered with a 412. A PATCH also needs Upload-Offset and Content-Type: application/offset+octet-stream.
Supported tus extensions
Tus-Extension advertises creation, creation-with-upload, creation-defer-length and termination. The creation extensions are of no use to a client here: the upload resource exists before you receive its URL, and there is no collection endpoint to post to, since every request has to name a session that its token also names. To terminate the upload, delete its URL.
The checksum extension is not implemented. Upload-Checksum is accepted and ignored, and integrity comes from the SHA-256 declared on the session instead. The expiration extension is not implemented either, so no response carries Upload-Expires; read expiresAt off the session. Concatenation is not supported.
| Status | Cause |
|---|---|
| 400 | A tus header failed validation, or the token names a different session |
| 401 | The upload token is missing or past its expiry |
| 403 | A PATCH without Upload-Offset, or without a content type |
| 404 | No session with that id |
| 409 | Upload-Offset disagrees with the offset the server holds |
| 410 | The session was terminated or has failed |
| 412 | Tus-Resumable is missing |
| 413 | The bytes would exceed the declared contentLength |
Chunking and resuming
The server offset advances only when a whole chunk has been stored, so an interrupted transfer costs you the chunk in flight and nothing before it. Ask for the offset, then continue from there:
curl -I "$UPLOAD_URL" -H 'Tus-Resumable: 1.0.0'
# HTTP/1.1 200 OK
# Upload-Offset: 5242880
# Upload-Length: 18874368
curl -X PATCH "$UPLOAD_URL" \
-H 'Tus-Resumable: 1.0.0' \
-H 'Upload-Offset: 5242880' \
-H 'Content-Type: application/offset+octet-stream' \
--data-binary @rest-of-file.binEvery chunk but the last needs 5 MiB
Chunks land as parts of a multipart upload underneath, and a non-final part below 5 MiB is refused. A 1 MiB chunk size therefore fails on the first chunk of anything larger than 1 MiB. The SDK sends 50 MiB chunks when it streams from a path or a stream, and 5 MiB is the smallest value that works.
In-memory data goes up as a single chunk unless you set chunkSize, which means an interrupted transfer restarts from zero. The SDK retries a failed chunk after 0, 1, 3 and 5 seconds, and resume: true stores a fingerprint so a later call for the same bytes and the same URL picks up where it stopped.
Finalization
The last PATCH does not answer 204 until the object exists. Before creating it, Gigadrive Network checks what was stored: the length has to match what you declared, a declared content type that contradicts the stored one is rejected, and the SHA-256 is verified, by re-reading the whole object when the storage layer does not report a digest it trusts. A mismatch fails the session and no object appears.
Uploading to a key that already holds an object replaces it. There is no object versioning, so the previous bytes are gone once nothing else references them. Stored bytes accrue storage_bytes_hours from that point; sending the bytes costs nothing, because only delivery out of File Storage is metered as transfer.
Object keys
A key is the full path inside the bucket. Listing derives folders from the key with S3-style prefix and delimiter semantics, so avatars/user-123.png shows up under avatars/ without any directory ever being created.
| Rule | Value |
|---|---|
| Length | Up to 1024 characters |
| Leading slashes | Stripped before storage |
| Path traversal | A .. segment is rejected |
| Control characters | Rejected |
Other ways in
A browser can send bytes to a bucket without them passing through your own server, which Client-side uploads walks through end to end. Classic S3 multipart is available through the S3-compatible API rather than the REST API: parts other than the last must be at least 5 MiB, the highest part number is 10,000, and an unfinished multipart upload stays resumable for 7 days.
