QQwibikDocumentation
Qwibik.com

Qwibik Cloud

Object Storage

Organization-owned object storage for application files, user media, document archives, and backups.


Qwibik Cloud Object Storage provides secure, scalable storage for unstructured data directly connected to your Organization. It is designed to support application assets, customer-uploaded files, automated PDF invoices, database backups, and internal business records.


1. Introduction and Use Cases

Unlike general-purpose storage providers that treat storage as disconnected utility accounts with complex multi-cloud IAM policies, Qwibik Object Storage is native to your Organization. Every bucket, object, access token, and byte of storage is scoped to the Organization boundary.

Common Use Cases

  • User-Generated Content & Media: Store user profile pictures, uploaded attachments, media galleries, and audio clips.
  • Transactional & Compliance Documents: Safely archive generated invoices, contracts, receipts, and tax statements with private signed retrieval.
  • Automated Backups & Snapshots: Stream automated database dumps, system state snapshots, and disaster-recovery bundles via write-only API tokens.
  • Application Attachments in Qwibik One: Store files and attachments created in Qwibik One business operations (CRM, Projects, Workforce, Finance) in your Organization's dedicated buckets.

2. Activating Object Storage

Object Storage is governed by your Organization's commercial entitlements in Qwibik Cloud.

Self-Service Activation Steps

  1. Sign in to Qwibik Cloud Console.
  2. Select your Organization from the switcher.
  3. In the sidebar navigation, click Storage.
  4. If Object Storage is not yet enabled for your Organization, you will see the Current offer banner for the Starter plan ($0 / month recurring during Public Beta).
  5. Click Enable Object Storage.

Your Organization will immediately be provisioned with the object_storage.enabled entitlement and the Starter plan quota:

  • 10 GB storage allowance (10,737,418,240 bytes)
  • 10 active buckets
  • Unlimited scoped programmatic API tokens

3. Creating a Bucket

Buckets provide logical namespaces for organizing your objects by application, environment, or purpose.

In the Cloud Console

  1. Navigate to Storage > Buckets.
  2. Click Create Bucket.
  3. Enter a Bucket Name (e.g. customer-documents, production-media).
  4. Select the Access Mode (default is Private).
  5. Click Create.

Bucket Naming Rules

  • Length: Between 3 and 63 characters.
  • Characters: Lowercase letters (a-z), numbers (0-9), and hyphens (-).
  • Cannot start or end with a hyphen.
  • Must be unique within your Organization.

4. Uploading and Managing Files

Qwibik Object Storage uses a high-performance two-phase upload protocol. Instead of proxying multi-megabyte file uploads through your application servers, uploads are streamed directly to the underlying storage provider using time-limited presigned PUT URLs.

Upload Flow Overview

  1. Reservation: Your client or application requests an upload reservation from the Qwibik API, providing the target key, file size, and MIME content type.
  2. Direct Upload: The client streams the raw file binary directly to the returned presigned uploadUrl via HTTP PUT.
  3. Finalization: Your client calls the finalize endpoint. Qwibik verifies the object's physical existence and exact byte size on the storage provider before marking the object as finalized and updating your storage quota.

Object Key Conventions

Object keys represent the path within the bucket:

  • Maximum length: 1,024 characters.
  • Supports hierarchical path prefixes (e.g. avatars/user-9812/profile.jpg or invoices/2026/10/inv-0042.pdf).
  • Forward slashes (/) define folder structures in the Cloud console bucket explorer.

5. Private vs. Public Access

Security is enforced at the bucket level:

Private Access (Default)

All buckets are created Private by default.

  • Objects in private buckets cannot be downloaded without authorization.
  • Consumers must authenticate using an API token with objects:read permission or use a time-limited Presigned Download URL generated by an authorized actor.
  • Presigned download URLs include an expiration window (configurable from 60 seconds up to several hours). Once expired, the URL is automatically invalidated.

Public Access

  • Buckets configured for Public access allow objects to be accessed over public HTTPS endpoints when public delivery is configured.
  • Recommended for public website static assets, open documentation files, or public media.

6. Creating Scoped API Tokens

Programmatic access from your backends, worker processes, CI/CD pipelines, or third-party services is authenticated using Scoped Access Tokens.

Generating a Token in the Console

  1. Navigate to Storage > Access tokens.
  2. Click Create Access Token.
  3. Enter a descriptive Name (e.g. billing-worker-uploader, backup-cron-job).
  4. Select the Bucket Scope:
    • All buckets in Organization: Token can access any bucket in the Organization.
    • Specific bucket: Restrict the token exclusively to a single bucket (e.g. app-backups).
  5. Choose the Permission Scopes:
    • objects:read: Download files and read object metadata.
    • objects:write: Request upload reservations and finalize uploads.
    • objects:delete: Delete objects.
    • objects:list: Discover accessible buckets and list objects.
  6. Optional: Specify an Expiration Date.
  7. Click Create Token.

7. Programmatic Upload Workflow

Here is how to upload a file programmatically using your scoped API token.

Step 1: Request an Upload Reservation

Send a POST request to /v1/object-storage/buckets/:bucket/uploads:

curl -X POST "https://api.qwibik.com/v1/object-storage/buckets/production-assets/uploads" \
  -H "Authorization: Bearer qws_live_a1b2c3d4e5f6..." \
  -H "Content-Type: application/json" \
  -d '{
    "key": "reports/q3-audit.pdf",
    "sizeBytes": 204800,
    "contentType": "application/pdf",
    "originalFileName": "q3-audit.pdf"
  }'

Response (201 Created):

{
  "data": {
    "objectId": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
    "organizationId": "550e8400-e29b-41d4-a716-446655440000",
    "bucketId": "e1f2a3b4-c5d6-7e8f-9a0b-1c2d3e4f5a6b",
    "key": "reports/q3-audit.pdf",
    "contentType": "application/pdf",
    "sizeBytes": 204800,
    "expiresAt": "2026-10-16T12:15:00.000Z",
    "uploadIntent": {
      "uploadUrl": "https://storage.provider.url/upload?signature=...",
      "method": "PUT",
      "headers": {
        "content-type": "application/pdf"
      },
      "expiresAt": "2026-10-16T12:15:00.000Z"
    }
  }
}

Step 2: Upload File Directly to the Presigned URL

Use HTTP PUT to stream the binary file data directly to uploadIntent.uploadUrl:

curl -X PUT "https://storage.provider.url/upload?signature=..." \
  -H "Content-Type: application/pdf" \
  --data-binary @q3-audit.pdf

Step 3: Finalize the Upload

Notify Qwibik to verify the uploaded bytes and commit the object record:

curl -X POST "https://api.qwibik.com/v1/object-storage/uploads/9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d/finalize" \
  -H "Authorization: Bearer qws_live_a1b2c3d4e5f6..."

Response (200 OK):

{
  "data": {
    "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
    "organizationId": "550e8400-e29b-41d4-a716-446655440000",
    "bucketId": "e1f2a3b4-c5d6-7e8f-9a0b-1c2d3e4f5a6b",
    "key": "reports/q3-audit.pdf",
    "contentType": "application/pdf",
    "sizeBytes": 204800,
    "lifecycleStatus": "finalized",
    "createdAt": "2026-10-16T12:00:00.000Z",
    "updatedAt": "2026-10-16T12:00:05.000Z"
  }
}

TypeScript / Node.js Complete Example

import { readFileSync, statSync } from "node:fs";

async function uploadFile(
  filePath: string,
  bucketName: string,
  destinationKey: string,
  apiToken: string
) {
  const fileStats = statSync(filePath);
  const fileBuffer = readFileSync(filePath);

  // 1. Request upload reservation
  const reserveResponse = await fetch(
    `https://api.qwibik.com/v1/object-storage/buckets/${encodeURIComponent(bucketName)}/uploads`,
    {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${apiToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        key: destinationKey,
        sizeBytes: fileStats.size,
        contentType: "application/pdf",
        originalFileName: "document.pdf",
      }),
    }
  );

  if (!reserveResponse.ok) {
    const errorBody = await reserveResponse.json();
    throw new Error(`Upload reservation failed: ${errorBody.message || errorBody.error}`);
  }

  const { data: reservation } = await reserveResponse.json();

  // 2. Stream directly to presigned URL
  const uploadResponse = await fetch(reservation.uploadIntent.uploadUrl, {
    method: "PUT",
    headers: reservation.uploadIntent.headers,
    body: fileBuffer,
  });

  if (!uploadResponse.ok) {
    throw new Error(`Binary upload to storage provider failed: ${uploadResponse.statusText}`);
  }

  // 3. Finalize upload
  const finalizeResponse = await fetch(
    `https://api.qwibik.com/v1/object-storage/uploads/${encodeURIComponent(reservation.objectId)}/finalize`,
    {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${apiToken}`,
      },
    }
  );

  if (!finalizeResponse.ok) {
    const errorBody = await finalizeResponse.json();
    throw new Error(`Finalization failed: ${errorBody.message || errorBody.error}`);
  }

  const { data: finalizedObject } = await finalizeResponse.json();
  return finalizedObject;
}

8. Downloading and Deleting Objects

Generating a Presigned Download URL

To safely download a file from a private bucket, request a download intent:

curl -X GET "https://api.qwibik.com/v1/object-storage/buckets/production-assets/objects/reports%2Fq3-audit.pdf/download?expiresInSeconds=600" \
  -H "Authorization: Bearer qws_live_a1b2c3d4e5f6..."

Response (200 OK):

{
  "data": {
    "objectId": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
    "downloadUrl": "https://storage.provider.url/download?signature=...",
    "expiresAt": "2026-10-16T12:10:00.000Z",
    "accessMode": "private"
  }
}

Clients can then download the file directly from downloadUrl with standard HTTP GET requests before expiresAt.

Deleting an Object

To permanently delete an object:

curl -X DELETE "https://api.qwibik.com/v1/object-storage/buckets/production-assets/objects/reports%2Fq3-audit.pdf" \
  -H "Authorization: Bearer qws_live_a1b2c3d4e5f6..."

Response (200 OK):

{
  "data": {
    "success": true
  }
}

The storage quota is immediately credited for the deleted bytes.


9. Usage and Quota Management

You can inspect your Organization's real-time storage usage via the console or the control plane API.

Checking Usage via API

curl -X GET "https://api.qwibik.com/v1/cloud/organizations/:organizationId/object-storage/overview" \
  -H "Authorization: Bearer <session_token>"

Response:

{
  "data": {
    "enabled": true,
    "usedBytes": 2599944192,
    "maxBytesAllowance": 10737418240,
    "remainingBytes": 8137474048,
    "bucketCount": 3,
    "maxBucketsAllowance": 10,
    "remainingBuckets": 7,
    "objectCount": 1420,
    "publicDeliveryConfigured": false
  }
}

Quota Enforcement

If a proposed upload reservation would exceed the Organization's maxBytesAllowance, the API rejects the request with HTTP 409 Conflict and error code quota_exceeded.


10. Error Handling and Troubleshooting

HTTP StatusError CodeDescription / Remedy
400 Bad Requestinvalid_object_storage_inputMissing or invalid parameters in request body (e.g. invalid bucket name format or non-positive sizeBytes).
401 Unauthorizedauthentication_requiredMissing or invalid bearer authorization token. Check that the header has Bearer qws_....
403 Forbiddenobject_storage_forbiddenThe access token lacks the required permission scope (e.g. attempting to delete without objects:delete, or token is scoped to a different bucket).
403 Forbiddenentitlement_requiredObject Storage is not enabled for this Organization. Activate the Starter offer in the Cloud console.
404 Not Foundobject_storage_not_foundThe requested bucket or object does not exist, or has already been deleted.
409 Conflictquota_exceededOrganization storage byte limit exceeded. Delete unneeded files or contact Qwibik support for an increased quota.
409 Conflictentitlement_limit_exceededThe maximum number of allowed buckets (10 on Starter) has been reached.
409 Conflictbucket_not_emptyCannot delete a bucket that still contains objects. Delete all objects in the bucket before deleting the bucket.
422 Unprocessableprovider_evidence_mismatchFinalization failed because the file was not uploaded to the presigned URL, or the actual uploaded size did not match the reserved sizeBytes.
503 Service Unavailableobject_storage_provider_unavailableUnderlying storage provider is temporarily unreachable. Retry after a backoff interval.

11. API Endpoint Reference

All programmatic data plane requests use Bearer authentication with a scoped token (Authorization: Bearer qws_...).

Data Plane (https://api.qwibik.com/v1/object-storage)

  • GET /v1/object-storage/buckets — List all buckets accessible to this token.
  • GET /v1/object-storage/buckets/:bucket/objects?prefix=&limit=&cursor= — List objects in bucket.
  • POST /v1/object-storage/buckets/:bucket/uploads — Reserve a direct upload.
  • POST /v1/object-storage/uploads/:uploadId/finalize — Finalize and verify an upload.
  • GET /v1/object-storage/buckets/:bucket/objects/:objectId — Get object metadata.
  • GET /v1/object-storage/buckets/:bucket/objects/:objectId/download?expiresInSeconds=300 — Generate presigned download URL.
  • DELETE /v1/object-storage/buckets/:bucket/objects/:objectId — Delete an object.

Control Plane (https://api.qwibik.com/v1/cloud/organizations/:orgId/object-storage)

Used by the Cloud console and Organization administrative workflows:

  • GET /overview — Get overall quota, bucket counts, and storage metrics.
  • GET /usage — Detailed storage usage breakdown.
  • GET /buckets — List Organization buckets with cursor pagination.
  • POST /buckets — Create a new bucket.
  • DELETE /buckets/:bucketId — Delete an empty bucket.
  • PATCH /buckets/:bucketId/access-mode — Update bucket access mode (private / public).
  • GET /access-tokens — List active programmatic access tokens.
  • POST /access-tokens — Generate a new scoped access token.
  • DELETE /access-tokens/:tokenId — Revoke an access token.