Skip to content

S3 Cloud Storage

FOTOhub provides three tiers of object storage — from simple managed buckets to enterprise-grade AWS S3 with CDN, replication, lifecycle policies, and more.

Base URL: https://apis.fotohub.app

Authentication: Bearer JWT token in the Authorization header.


Storage Tiers

TierPrefixBackendBest For
Simple Buckets/v1/bucketsSupabase StorageQuick file storage, small projects
S3 Enterprise/v1/storage/s3Real AWS S3Production apps, large-scale storage
Rental Packages/v1/storageManaged S3Teams, shared workspaces

Simple Buckets

Lightweight bucket management backed by Supabase Storage. Auth via API key.

Endpoints

MethodPathDescription
GET/v1/bucketsList buckets
POST/v1/bucketsCreate bucket
GET/v1/buckets/:idGet bucket
PATCH/v1/buckets/:idUpdate bucket
DELETE/v1/buckets/:idDelete bucket + files

Create Bucket

POST /v1/buckets
json
{
  "name": "my-project-assets",
  "is_public": false,
  "max_file_size_mb": 100,
  "allowed_mime_types": ["image/png", "image/jpeg", "video/mp4"]
}

Response:

json
{
  "id": "bucket_abc123",
  "name": "my-project-assets",
  "path_prefix": "user_123/my-project-assets",
  "is_active": true,
  "is_public": false,
  "max_file_size_mb": 100,
  "allowed_mime_types": ["image/png", "image/jpeg", "video/mp4"],
  "created_at": "2026-07-18T12:00:00Z"
}

TIP

Bucket names are sanitized to [a-zA-Z0-9_-] and limited to 50 characters.


S3 Enterprise

Full AWS S3 with 70+ API endpoints covering every S3 feature. Auth via JWT.

Base prefix: https://apis.fotohub.app/v1/storage/s3

Plan Limits

PlanMax S3 Buckets
Free1
Developer / Starter5
Startup / Medium10
Pro / Business25
Enterprise1000

Storage Classes and Pricing

ClassUSD/GB/monthBest For
STANDARD$0.0245Frequently accessed data
STANDARD_IA$0.0135Infrequent access (30-day minimum)
ONEZONE_IA$0.0108Non-critical infrequent access
INTELLIGENT_TIERING$0.0245Unknown access patterns
GLACIER_IR$0.005Archive with instant retrieval
GLACIER$0.0045Long-term archive (minutes retrieval)
DEEP_ARCHIVE$0.00205Compliance archives (hours retrieval)

Billing Modes

ModeDescription
walletCredits deducted from your FOTOhub wallet. A reservation deposit of approximately 1.5 days of estimated storage cost is held at creation (minimum 0.50 PLN). Hourly reconciliation via billing engine.
invoice_monthlyBilled monthly on your invoice. Available for Pro and Enterprise plans.

WARNING

Wallet mode requires sufficient balance for the reservation deposit. If your wallet balance is too low, bucket creation will fail.


Bucket CRUD

Provision a Bucket

POST /v1/storage/s3/buy

Creates a real AWS S3 bucket with IAM credentials. Requires wallet balance for the reservation deposit.

Request:

json
{
  "display_name": "production-media",
  "description": "Media assets for production app",
  "region": "eu-central-1",
  "default_storage_class": "STANDARD",
  "quota_gb": 100,
  "versioning_enabled": true,
  "encryption_type": "SSE-S3",
  "encryption_kms_key_id": null,
  "block_public_access": true,
  "mfa_delete_enabled": false,
  "billing_mode": "wallet",
  "tags": { "env": "production", "team": "media" }
}

Request Fields:

FieldTypeRequiredDefaultDescription
display_namestringYesBucket display name (1-100 chars)
descriptionstringNonullOptional description
regionstringNoeu-central-1AWS region (see Available Regions)
default_storage_classstringNoSTANDARDDefault storage class for objects
quota_gbintegerNonullStorage quota (1 - 1,048,576 GB)
versioning_enabledbooleanNofalseEnable object versioning
encryption_typestringNoSSE-S3SSE-S3 or aws:kms
encryption_kms_key_idstringNonullCustom KMS key ARN (required if aws:kms)
block_public_accessbooleanNotrueBlock all public access
mfa_delete_enabledbooleanNofalseRequire MFA for deletes
billing_modestringNowalletwallet or invoice_monthly
tagsobjectNonullKey-value tags (max 50 tags)

Response:

json
{
  "bucket": {
    "id": "s3_bucket_xyz",
    "aws_bucket_name": "fotohub-customer-abc123-production-media",
    "region": "eu-central-1",
    "status": "active",
    "credentials": {
      "access_key_id": "AKIA...",
      "secret_access_key": "wJalr...",
      "endpoint": "https://s3.eu-central-1.amazonaws.com"
    }
  },
  "reservation_pln": 0.50
}

List Buckets

GET /v1/storage/s3/buckets

Returns all S3 buckets owned by the authenticated user.

Response:

json
{
  "buckets": [
    {
      "id": "s3_bucket_xyz",
      "display_name": "production-media",
      "region": "eu-central-1",
      "status": "active",
      "storage_class": "STANDARD",
      "quota_gb": 100,
      "used_bytes": 5368709120,
      "created_at": "2026-07-01T10:00:00Z"
    }
  ]
}

Get Bucket Details

GET /v1/storage/s3/buckets/{bucket_id}

Returns full details for a single bucket including configuration and usage.

Upgrade Bucket

POST /v1/storage/s3/buckets/{bucket_id}/upgrade

Increase quota or change storage class on an existing bucket.

Request:

json
{
  "quota_gb": 500,
  "default_storage_class": "INTELLIGENT_TIERING"
}
FieldTypeDescription
quota_gbintegerNew quota (must be larger than current)
default_storage_classstringNew default storage class

Delete Bucket

DELETE /v1/storage/s3/buckets/{bucket_id}

Cancels and deletes the bucket. Bucket must be empty (no objects). Returns remaining wallet reservation credit.

WARNING

This action is irreversible. All credentials associated with the bucket are immediately revoked.


Credentials

List Access Keys

GET /v1/storage/s3/buckets/{bucket_id}/credentials

Returns all active access key pairs for the bucket.

Response:

json
{
  "credentials": [
    {
      "access_key_id": "AKIA...",
      "label": "api-server-v1",
      "created_at": "2026-07-01T10:00:00Z",
      "last_used": "2026-07-18T09:30:00Z",
      "status": "active"
    }
  ]
}

Rotate Keys

POST /v1/storage/s3/buckets/{bucket_id}/regenerate-keys

Generate new credentials with optional grace period for the old ones.

Request:

json
{
  "label": "api-server-v2",
  "revoke_old_immediately": false,
  "grace_period_days": 7
}
FieldTypeDefaultDescription
labelstringNoHuman-readable label for the new key
revoke_old_immediatelybooleanfalseIf true, old key is disabled immediately
grace_period_daysinteger7Days before old key expires (0-30)

Response:

json
{
  "new_credentials": {
    "access_key_id": "AKIA...",
    "secret_access_key": "wJalr...",
    "label": "api-server-v2"
  },
  "old_key_expires_at": "2026-07-25T10:00:00Z"
}

TIP

Use a grace period to rotate keys without downtime. Deploy new credentials to your services, then let the old key expire naturally.


Usage and Metrics

Current Usage

GET /v1/storage/s3/buckets/{bucket_id}/usage

Returns current storage usage statistics.

Response:

json
{
  "bucket_id": "s3_bucket_xyz",
  "total_objects": 15420,
  "total_bytes": 53687091200,
  "total_gb": 50.0,
  "quota_gb": 100,
  "usage_percent": 50.0,
  "by_storage_class": {
    "STANDARD": { "objects": 12000, "bytes": 42949672960 },
    "GLACIER": { "objects": 3420, "bytes": 10737418240 }
  },
  "last_updated": "2026-07-18T12:00:00Z"
}

CloudWatch Metrics

GET /v1/storage/s3/buckets/{bucket_id}/metrics

Query Parameters:

ParameterTypeDefaultDescription
metricstringBucketSizeBytesMetric name
startstring7 days agoISO 8601 start time
endstringnowISO 8601 end time
periodinteger86400Aggregation period in seconds

Available metrics: BucketSizeBytes, NumberOfObjects, AllRequests, GetRequests, PutRequests, 4xxErrors, 5xxErrors, BytesDownloaded, BytesUploaded.

Get Pricing

GET /v1/storage/s3/pricing

Returns the full pricing table for all storage classes and request types.

Cost Estimator

POST /v1/storage/s3/estimate

Estimate monthly costs before provisioning.

Request:

json
{
  "region": "eu-central-1",
  "storage_class": "STANDARD",
  "storage_gb": 500,
  "put_requests": 100000,
  "get_requests": 1000000,
  "egress_gb": 50,
  "retrieval_gb": 0,
  "period_days": 30
}
FieldTypeRequiredDescription
regionstringNoAWS region
storage_classstringYesStorage class to estimate
storage_gbnumberYesStorage amount in GB
put_requestsintegerNoNumber of PUT/POST requests
get_requestsintegerNoNumber of GET requests
egress_gbnumberNoData transfer out in GB
retrieval_gbnumberNoData retrieval in GB (Glacier classes)
period_daysintegerNoEstimation period (1-366, default 30)

Response:

json
{
  "storage_usd": 12.25,
  "requests_usd": 0.95,
  "egress_usd": 4.50,
  "retrieval_usd": 0.00,
  "total_usd": 17.70,
  "total_pln": 71.69,
  "period_days": 30
}

List Regions

GET /v1/storage/s3/regions

Returns available AWS regions for bucket creation.


Object Operations

List Objects

POST /v1/storage/s3/buckets/{bucket_id}/objects/list

Request:

json
{
  "prefix": "uploads/2026/07/",
  "delimiter": "/",
  "max_keys": 100,
  "continuation_token": null
}
FieldTypeDefaultDescription
prefixstring""Filter by key prefix
continuation_tokenstringnullPagination token from previous response
max_keysinteger1000Results per page (1-1000)
delimiterstringnullDelimiter for hierarchy (typically /)

Response:

json
{
  "objects": [
    {
      "key": "uploads/2026/07/photo-001.jpg",
      "size": 2048576,
      "last_modified": "2026-07-18T12:00:00Z",
      "storage_class": "STANDARD",
      "etag": "\"d41d8cd98f00b204e9800998ecf8427e\""
    }
  ],
  "common_prefixes": ["uploads/2026/07/thumbnails/"],
  "is_truncated": false,
  "continuation_token": null
}

Presigned Upload

Generate a presigned URL for direct-to-S3 upload (bypasses the API server):

POST /v1/storage/s3/buckets/{bucket_id}/objects/presign-upload

Request:

json
{
  "key": "media/video-001.mp4",
  "content_type": "video/mp4",
  "content_length": 104857600,
  "expires_in": 3600,
  "storage_class": "STANDARD",
  "metadata": { "uploaded_by": "api", "project": "xyz" }
}
FieldTypeRequiredDescription
keystringYesObject key (path)
content_typestringNoMIME type
content_lengthintegerNoExpected file size in bytes
expires_inintegerNoURL expiry in seconds (60-604,800)
storage_classstringNoTarget storage class
metadataobjectNoCustom metadata key-value pairs

Response:

json
{
  "upload_url": "https://s3.eu-central-1.amazonaws.com/fotohub-customer-abc123/media/video-001.mp4?X-Amz-...",
  "expires_at": "2026-07-18T13:00:00Z",
  "headers": {
    "Content-Type": "video/mp4",
    "x-amz-meta-uploaded_by": "api",
    "x-amz-meta-project": "xyz"
  }
}

TIP

Use presigned URLs to upload large files directly to S3 without routing through the API server. This reduces latency and avoids request size limits.

Presigned Download

POST /v1/storage/s3/buckets/{bucket_id}/objects/presign-download

Request:

json
{
  "key": "media/video-001.mp4",
  "version_id": null,
  "expires_in": 3600,
  "response_content_disposition": "attachment; filename=\"video.mp4\""
}
FieldTypeRequiredDescription
keystringYesObject key
version_idstringNoSpecific version (if versioning enabled)
expires_inintegerNoURL expiry in seconds (60-604,800)
response_content_dispositionstringNoOverride Content-Disposition header

Response:

json
{
  "url": "https://s3.eu-central-1.amazonaws.com/...",
  "expires_at": "2026-07-18T13:00:00Z"
}

Batch Delete

POST /v1/storage/s3/buckets/{bucket_id}/objects/delete

Request:

json
{
  "keys": ["tmp/file1.txt", "tmp/file2.txt", "tmp/file3.txt"],
  "version_ids": null
}
FieldTypeRequiredDescription
keysstring[]YesObject keys to delete (max 1000)
version_idsstring[]NoCorresponding version IDs (same length as keys)

Response:

json
{
  "deleted": ["tmp/file1.txt", "tmp/file2.txt", "tmp/file3.txt"],
  "errors": []
}

Copy Object

POST /v1/storage/s3/buckets/{bucket_id}/objects/copy

Request:

json
{
  "source_key": "uploads/original.jpg",
  "dest_key": "processed/thumbnail.jpg",
  "metadata_directive": "COPY"
}
FieldTypeRequiredDescription
source_keystringYesSource object key
dest_keystringYesDestination object key
metadata_directivestringNoCOPY (keep metadata) or REPLACE (use new metadata)

Multipart Upload

For files larger than 100 MB, use multipart upload for reliability and resumability. Each part can be 5 MB to 5 GB.

Flow

  1. Create - get an upload_id
  2. Presign parts - upload each chunk directly to S3
  3. Complete - assemble all parts into the final object
  4. (Optional) Abort - cancel an incomplete upload

Initiate Multipart Upload

POST /v1/storage/s3/buckets/{bucket_id}/multipart/create

Request:

json
{
  "key": "large-video.mp4",
  "content_type": "video/mp4",
  "storage_class": "STANDARD",
  "metadata": { "duration": "3600" }
}
FieldTypeRequiredDescription
keystringYesObject key
content_typestringNoMIME type
storage_classstringNoTarget storage class
metadataobjectNoCustom metadata

Response:

json
{
  "upload_id": "abc123xyz",
  "key": "large-video.mp4",
  "bucket_id": "s3_bucket_xyz"
}

Presign Part

POST /v1/storage/s3/buckets/{bucket_id}/multipart/presign-part

Request:

json
{
  "upload_id": "abc123xyz",
  "key": "large-video.mp4",
  "part_number": 1
}
FieldTypeRequiredDescription
upload_idstringYesUpload ID from create step
keystringYesObject key
part_numberintegerYesPart number (1-10,000)

Response:

json
{
  "url": "https://s3.eu-central-1.amazonaws.com/...?partNumber=1&uploadId=abc123xyz&X-Amz-...",
  "part_number": 1,
  "expires_at": "2026-07-18T13:00:00Z"
}

Complete Multipart Upload

POST /v1/storage/s3/buckets/{bucket_id}/multipart/complete

Request:

json
{
  "upload_id": "abc123xyz",
  "key": "large-video.mp4",
  "parts": [
    { "part_number": 1, "etag": "\"a54357aff0632cce46d942af68356b38\"" },
    { "part_number": 2, "etag": "\"0dc9c85be847e1f3f788d65e49cdd67b\"" }
  ]
}
FieldTypeRequiredDescription
upload_idstringYesUpload ID
keystringYesObject key
partsarrayYesList of parts with part_number and etag (max 10,000)

Abort Multipart Upload

POST /v1/storage/s3/buckets/{bucket_id}/multipart/abort

Request:

json
{
  "upload_id": "abc123xyz",
  "key": "large-video.mp4"
}

Aborts an in-progress multipart upload and frees any uploaded parts.


Permissions

Bucket Policy

GET    /v1/storage/s3/buckets/{bucket_id}/policy
PUT    /v1/storage/s3/buckets/{bucket_id}/policy
DELETE /v1/storage/s3/buckets/{bucket_id}/policy

PUT Request:

json
{
  "policy_json": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Sid": "AllowPublicRead",
        "Effect": "Allow",
        "Principal": "*",
        "Action": "s3:GetObject",
        "Resource": "arn:aws:s3:::bucket-name/*"
      }
    ]
  }
}

Validate Policy

POST /v1/storage/s3/buckets/{bucket_id}/policy/validate

Validates a policy document before applying it. Returns validation errors if the policy is malformed or contains unsupported actions.

Request:

json
{
  "policy_json": { ... }
}

Response:

json
{
  "valid": true,
  "warnings": []
}

CORS Configuration

GET    /v1/storage/s3/buckets/{bucket_id}/cors
PUT    /v1/storage/s3/buckets/{bucket_id}/cors
DELETE /v1/storage/s3/buckets/{bucket_id}/cors

PUT Request:

json
{
  "rules": [
    {
      "allowed_methods": ["GET", "PUT", "POST"],
      "allowed_origins": ["https://myapp.com", "https://staging.myapp.com"],
      "allowed_headers": ["*"],
      "expose_headers": ["ETag", "x-amz-meta-custom-header"],
      "max_age_seconds": 3600
    }
  ]
}

Public Access Block

GET /v1/storage/s3/buckets/{bucket_id}/public-access-block
PUT /v1/storage/s3/buckets/{bucket_id}/public-access-block

PUT Request:

json
{
  "block_public_acls": true,
  "ignore_public_acls": true,
  "block_public_policy": true,
  "restrict_public_buckets": true
}
FieldTypeDescription
block_public_aclsbooleanBlock public ACLs on objects
ignore_public_aclsbooleanIgnore existing public ACLs
block_public_policybooleanBlock public bucket policies
restrict_public_bucketsbooleanRestrict cross-account access

Object Ownership

GET /v1/storage/s3/buckets/{bucket_id}/ownership
PUT /v1/storage/s3/buckets/{bucket_id}/ownership

PUT Request:

json
{
  "ownership": "BucketOwnerEnforced"
}

Values: BucketOwnerEnforced | BucketOwnerPreferred | ObjectWriter

ACL (Read-Only)

GET /v1/storage/s3/buckets/{bucket_id}/acl

Returns the current access control list. ACLs are read-only; use bucket policies for access control.


Security

Versioning

GET /v1/storage/s3/buckets/{bucket_id}/versioning
PUT /v1/storage/s3/buckets/{bucket_id}/versioning

PUT Request:

json
{
  "status": "Enabled",
  "mfa_delete": "Disabled"
}
FieldTypeDescription
statusstringEnabled or Suspended
mfa_deletestringEnabled or Disabled

List Object Versions

GET /v1/storage/s3/buckets/{bucket_id}/versions

Query Parameters:

ParameterTypeDefaultDescription
prefixstring""Filter by key prefix
max_keysinteger1000Results per page (1-1000)
key_markerstringnullPagination: start after this key
version_id_markerstringnullPagination: start after this version

Response:

json
{
  "versions": [
    {
      "key": "document.pdf",
      "version_id": "v1_abc123",
      "is_latest": true,
      "size": 1048576,
      "last_modified": "2026-07-18T12:00:00Z"
    },
    {
      "key": "document.pdf",
      "version_id": "v1_xyz789",
      "is_latest": false,
      "size": 524288,
      "last_modified": "2026-07-15T10:00:00Z"
    }
  ],
  "is_truncated": false
}

Restore Version

POST /v1/storage/s3/buckets/{bucket_id}/versions/restore

Request:

json
{
  "key": "document.pdf",
  "version_id": "v1_xyz789"
}

Creates a new version by copying the specified old version as the latest.

Delete Version

POST /v1/storage/s3/buckets/{bucket_id}/versions/delete

Request:

json
{
  "key": "document.pdf",
  "version_id": "v1_xyz789"
}

Permanently deletes a specific version. Cannot be undone.

Encryption

GET    /v1/storage/s3/buckets/{bucket_id}/encryption
PUT    /v1/storage/s3/buckets/{bucket_id}/encryption
DELETE /v1/storage/s3/buckets/{bucket_id}/encryption

PUT Request:

json
{
  "algorithm": "aws:kms",
  "kms_master_key_id": "arn:aws:kms:eu-central-1:123456:key/abcdef",
  "bucket_key_enabled": true
}
FieldTypeDescription
algorithmstringAES256, aws:kms, or aws:kms:dsse
kms_master_key_idstringKMS key ARN (required for aws:kms)
bucket_key_enabledbooleanReduce KMS request costs with bucket keys

Object Lock (WORM)

GET /v1/storage/s3/buckets/{bucket_id}/object-lock
PUT /v1/storage/s3/buckets/{bucket_id}/object-lock

PUT Request:

json
{
  "enabled": true,
  "mode": "COMPLIANCE",
  "retention_days": 365
}
FieldTypeDescription
enabledbooleanEnable object lock
modestringGOVERNANCE (overridable) or COMPLIANCE (immutable)
retention_daysintegerRetention period in days (1-36,500)
retention_yearsintegerAlternative: retention in years (1-100)

WARNING

COMPLIANCE mode locks cannot be shortened or removed by any user, including the account owner. Use GOVERNANCE mode for testing.


Behavior Configuration

Tags

GET    /v1/storage/s3/buckets/{bucket_id}/tags
PUT    /v1/storage/s3/buckets/{bucket_id}/tags
DELETE /v1/storage/s3/buckets/{bucket_id}/tags

PUT Request:

json
{
  "tags": [
    { "key": "Environment", "value": "production" },
    { "key": "Team", "value": "media" },
    { "key": "CostCenter", "value": "CC-1234" }
  ]
}

Access Logging

GET /v1/storage/s3/buckets/{bucket_id}/logging
PUT /v1/storage/s3/buckets/{bucket_id}/logging

PUT Request:

json
{
  "enabled": true,
  "target_bucket": "s3_bucket_logs",
  "target_prefix": "access-logs/production-media/"
}

Transfer Acceleration

GET /v1/storage/s3/buckets/{bucket_id}/accelerate
PUT /v1/storage/s3/buckets/{bucket_id}/accelerate

PUT Request:

json
{
  "status": "Enabled"
}

Values: Enabled | Suspended

TIP

Transfer Acceleration uses CloudFront edge locations to speed up uploads from distant clients. Ideal for global user bases uploading to a single region.

Request Payment

GET /v1/storage/s3/buckets/{bucket_id}/request-payment
PUT /v1/storage/s3/buckets/{bucket_id}/request-payment

PUT Request:

json
{
  "payer": "Requester"
}

Values: BucketOwner | Requester

Intelligent Tiering

GET    /v1/storage/s3/buckets/{bucket_id}/intelligent-tiering
PUT    /v1/storage/s3/buckets/{bucket_id}/intelligent-tiering
DELETE /v1/storage/s3/buckets/{bucket_id}/intelligent-tiering/{config_id}

PUT Request:

json
{
  "id": "archive-config",
  "status": "Enabled",
  "prefix": "data/",
  "tag": { "key": "archive", "value": "true" },
  "tierings": [
    { "days": 90, "access_tier": "ARCHIVE_ACCESS" },
    { "days": 180, "access_tier": "DEEP_ARCHIVE_ACCESS" }
  ]
}
FieldTypeDescription
idstringConfiguration identifier
statusstringEnabled or Disabled
prefixstringFilter by key prefix
tagobjectFilter by tag {key, value}
tieringsarrayTier transitions (days 90-9999)
tierings[].access_tierstringARCHIVE_ACCESS or DEEP_ARCHIVE_ACCESS

Lifecycle Rules

GET    /v1/storage/s3/buckets/{bucket_id}/lifecycle
PUT    /v1/storage/s3/buckets/{bucket_id}/lifecycle
DELETE /v1/storage/s3/buckets/{bucket_id}/lifecycle

Define rules for automatic object transitions and expiration.

PUT Request:

json
{
  "rules": [
    {
      "id": "archive-old-media",
      "status": "Enabled",
      "prefix": "media/",
      "transitions": [
        { "days": 30, "storage_class": "STANDARD_IA" },
        { "days": 90, "storage_class": "GLACIER" }
      ],
      "expiration": { "days": 365 },
      "noncurrent_version_transitions": [
        { "noncurrent_days": 30, "storage_class": "GLACIER" }
      ],
      "noncurrent_version_expiration": { "noncurrent_days": 90 },
      "abort_incomplete_multipart_upload": { "days_after_initiation": 7 }
    }
  ]
}

TIP

Always include an abort_incomplete_multipart_upload rule to clean up failed uploads and avoid unnecessary storage charges.


Website Hosting

GET    /v1/storage/s3/buckets/{bucket_id}/website
PUT    /v1/storage/s3/buckets/{bucket_id}/website
DELETE /v1/storage/s3/buckets/{bucket_id}/website

Configure a bucket as a static website.

PUT Request:

json
{
  "index_document": "index.html",
  "error_document": "404.html",
  "redirect_all_requests_to": null,
  "routing_rules": [
    {
      "condition": { "key_prefix_equals": "docs/" },
      "redirect": { "replace_key_prefix_with": "documentation/" }
    }
  ]
}
FieldTypeDescription
index_documentstringDefault index page
error_documentstringCustom error page
redirect_all_requests_tostringRedirect all traffic to this URL
routing_rulesarrayConditional routing rules

Notifications

GET /v1/storage/s3/buckets/{bucket_id}/notifications
PUT /v1/storage/s3/buckets/{bucket_id}/notifications

Configure event notifications for object operations.

PUT Request:

json
{
  "sns": [
    {
      "arn": "arn:aws:sns:eu-central-1:123456:my-topic",
      "events": ["s3:ObjectCreated:*"],
      "filter_prefix": "uploads/",
      "filter_suffix": ".jpg"
    }
  ],
  "sqs": [],
  "lambda": [
    {
      "arn": "arn:aws:lambda:eu-central-1:123456:function:process-upload",
      "events": ["s3:ObjectCreated:Put", "s3:ObjectCreated:CompleteMultipartUpload"],
      "filter_prefix": "raw/"
    }
  ],
  "event_bridge_enabled": true
}
FieldTypeDescription
snsarraySNS topic notifications
sqsarraySQS queue notifications
lambdaarrayLambda function triggers
event_bridge_enabledbooleanSend all events to EventBridge

Access Points

Named network endpoints with individual policies for shared bucket access.

List Access Points

GET /v1/storage/s3/buckets/{bucket_id}/access-points

Create Access Point

POST /v1/storage/s3/buckets/{bucket_id}/access-points

Request:

json
{
  "name": "analytics-team",
  "vpc_id": "vpc-abc123",
  "block_public_access": true
}
FieldTypeRequiredDescription
namestringYesAccess point name (3-50 chars, lowercase alphanumeric + hyphens)
vpc_idstringNoRestrict to a specific VPC
block_public_accessbooleanNoBlock public access through this endpoint

Get Access Point

GET /v1/storage/s3/buckets/{bucket_id}/access-points/{name}

Delete Access Point

DELETE /v1/storage/s3/buckets/{bucket_id}/access-points/{name}

Access Point Policy

GET    /v1/storage/s3/buckets/{bucket_id}/access-points/{name}/policy
PUT    /v1/storage/s3/buckets/{bucket_id}/access-points/{name}/policy
DELETE /v1/storage/s3/buckets/{bucket_id}/access-points/{name}/policy

PUT Request:

json
{
  "policy_json": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": { "AWS": "arn:aws:iam::123456:role/analytics" },
        "Action": ["s3:GetObject"],
        "Resource": "arn:aws:s3:eu-central-1:123456:accesspoint/analytics-team/object/*"
      }
    ]
  }
}

Replication

GET    /v1/storage/s3/buckets/{bucket_id}/replication
PUT    /v1/storage/s3/buckets/{bucket_id}/replication
DELETE /v1/storage/s3/buckets/{bucket_id}/replication

Configure cross-region replication for disaster recovery or compliance.

PUT Request:

json
{
  "rules": [
    {
      "id": "replicate-to-us",
      "status": "Enabled",
      "priority": 1,
      "prefix": "",
      "destination": {
        "bucket_arn": "arn:aws:s3:::fotohub-customer-abc123-backup",
        "region": "us-east-1",
        "storage_class": "STANDARD_IA"
      },
      "delete_marker_replication": true,
      "replica_modifications": true
    }
  ]
}

WARNING

Both source and destination buckets must have versioning enabled for replication to work.


CDN (CloudFront)

Attach a CloudFront distribution to your bucket for global low-latency delivery.

Request SSL Certificate

POST /v1/storage/s3/buckets/{bucket_id}/cdn/certificate

Request:

json
{
  "domain_names": ["cdn.myapp.com", "assets.myapp.com"]
}

Maximum 10 domain names per certificate. Certificate is issued via AWS ACM and requires DNS validation.

FieldTypeDescription
domain_namesstring[]Custom domains (max 10)

Response:

json
{
  "certificate_arn": "arn:aws:acm:us-east-1:123456:certificate/abc-123",
  "status": "PENDING_VALIDATION",
  "validation_records": [
    {
      "domain": "cdn.myapp.com",
      "type": "CNAME",
      "name": "_abc123.cdn.myapp.com",
      "value": "_xyz789.acm-validations.aws"
    }
  ]
}

Get Certificate Status

GET /v1/storage/s3/buckets/{bucket_id}/cdn/certificate?certificate_arn=arn:aws:acm:...

Delete Certificate

DELETE /v1/storage/s3/buckets/{bucket_id}/cdn/certificate?certificate_arn=arn:aws:acm:...

Create Distribution

POST /v1/storage/s3/buckets/{bucket_id}/cdn

Request:

json
{
  "custom_domain_names": ["cdn.myapp.com"],
  "certificate_arn": "arn:aws:acm:us-east-1:123456:certificate/abc-123",
  "default_root_object": "index.html",
  "price_class": "PriceClass_100",
  "comment": "Production CDN for media assets"
}
FieldTypeRequiredDescription
custom_domain_namesstring[]NoCustom domains (certificate must cover them)
certificate_arnstringNoACM certificate ARN (required for custom domains)
default_root_objectstringNoDefault root document
price_classstringNoPriceClass_All, PriceClass_200, PriceClass_100
commentstringNoDistribution description

Response:

json
{
  "distribution": {
    "id": "E1234ABCDEF",
    "domain_name": "d1234abcdef.cloudfront.net",
    "status": "InProgress",
    "custom_domains": ["cdn.myapp.com"],
    "price_class": "PriceClass_100"
  }
}

Get/List Distributions

GET /v1/storage/s3/buckets/{bucket_id}/cdn
GET /v1/storage/s3/buckets/{bucket_id}/cdn?distribution_id=E1234ABCDEF

Invalidate Cache

POST /v1/storage/s3/buckets/{bucket_id}/cdn/invalidate

Request:

json
{
  "distribution_id": "E1234ABCDEF",
  "paths": ["/images/*", "/css/main.css"]
}
FieldTypeDescription
distribution_idstringCloudFront distribution ID
pathsstring[]Paths to invalidate (max 1000, supports wildcards)

Disable Distribution

DELETE /v1/storage/s3/buckets/{bucket_id}/cdn?distribution_id=E1234ABCDEF

Disables the distribution. Must wait for status to become Deployed before finalizing deletion.

Finalize Delete Distribution

DELETE /v1/storage/s3/buckets/{bucket_id}/cdn/finalize?distribution_id=E1234ABCDEF

Permanently deletes a disabled distribution. Only callable after the distribution status is Deployed (disabled).


Inventory

Schedule automated inventory reports for object auditing.

List Inventory Configurations

GET /v1/storage/s3/buckets/{bucket_id}/inventory

Create/Update Inventory

PUT /v1/storage/s3/buckets/{bucket_id}/inventory

Request:

json
{
  "id": "weekly-full-inventory",
  "destination_bucket_arn": "arn:aws:s3:::fotohub-customer-abc123-inventory",
  "destination_prefix": "inventory/production-media/",
  "destination_format": "CSV",
  "frequency": "Weekly",
  "included_object_versions": "All",
  "optional_fields": ["Size", "LastModifiedDate", "StorageClass", "ETag", "IsMultipartUploaded"],
  "enabled": true,
  "filter_prefix": "media/"
}
FieldTypeDescription
idstringConfiguration identifier
destination_bucket_arnstringTarget bucket for inventory files
destination_prefixstringPrefix in destination bucket
destination_formatstringCSV, ORC, or Parquet
frequencystringDaily or Weekly
included_object_versionsstringAll or Current
optional_fieldsstring[]Additional fields to include
enabledbooleanEnable/disable the configuration
filter_prefixstringOnly inventory objects with this prefix

Delete Inventory Configuration

DELETE /v1/storage/s3/buckets/{bucket_id}/inventory/{config_id}

Rental Packages

Managed storage with simple monthly pricing. No AWS knowledge required.

List Packages

GET /v1/storage/packages

Returns available packages with fixed monthly pricing.

Rent Storage

POST /v1/storage/rent
json
{
  "package": "storage-50gb",
  "name": "my-project-assets",
  "region": "eu"
}

Response:

json
{
  "rental": {
    "id": "rental_abc",
    "name": "my-project-assets",
    "size_gb": 50,
    "tier": "standard",
    "price_monthly_pln": 9.99,
    "region": "eu",
    "status": "active",
    "used_bytes": 0
  },
  "package": { "slug": "storage-50gb", "gb": 50, "tier": "standard" },
  "message": "Successfully rented 50 GB standard storage"
}

Shared Spaces

Create storage accessible by multiple users or API keys:

POST /v1/storage/shared/create
json
{
  "name": "team-assets",
  "size_gb": 100,
  "allowed_users": ["user-id-1", "user-id-2"],
  "permissions": "write"
}

Permissions: read | write | admin

Storage Stats

GET /v1/storage/stats

Response:

json
{
  "total_files": 1234,
  "total_bytes": 5368709120,
  "total_gb": 5.0,
  "buckets_count": 3,
  "buckets": [
    { "id": "...", "name": "assets", "rental": false, "shared": false },
    { "id": "...", "name": "team-media", "rental": true, "shared": false }
  ]
}

Code Examples

Python: Full S3 Workflow

python
import httpx

API = "https://apis.fotohub.app/v1/storage/s3"
HEADERS = {"Authorization": "Bearer YOUR_JWT"}

# 1. Estimate costs
estimate = httpx.post(f"{API}/estimate", headers=HEADERS, json={
    "storage_class": "STANDARD",
    "storage_gb": 100,
    "put_requests": 50000,
    "get_requests": 200000,
    "egress_gb": 20,
    "period_days": 30
}).json()
print(f"Estimated cost: ${estimate['total_usd']}/month")

# 2. Create bucket
bucket = httpx.post(f"{API}/buy", headers=HEADERS, json={
    "display_name": "media-production",
    "region": "eu-central-1",
    "default_storage_class": "INTELLIGENT_TIERING",
    "quota_gb": 100,
    "versioning_enabled": True,
    "encryption_type": "SSE-S3"
}).json()

bucket_id = bucket["bucket"]["id"]
creds = bucket["bucket"]["credentials"]

# 3. Upload file via presigned URL
presign = httpx.post(
    f"{API}/buckets/{bucket_id}/objects/presign-upload",
    headers=HEADERS,
    json={"key": "photos/hero.jpg", "content_type": "image/jpeg", "expires_in": 600}
).json()

with open("hero.jpg", "rb") as f:
    httpx.put(presign["upload_url"], content=f.read(),
              headers={"Content-Type": "image/jpeg"})

# 4. Generate download link
download = httpx.post(
    f"{API}/buckets/{bucket_id}/objects/presign-download",
    headers=HEADERS,
    json={"key": "photos/hero.jpg", "expires_in": 86400}
).json()
print(f"Download: {download['url']}")

Python: Multipart Upload

python
import httpx

API = "https://apis.fotohub.app/v1/storage/s3"
HEADERS = {"Authorization": "Bearer YOUR_JWT"}
BUCKET_ID = "your-bucket-id"
BASE = f"{API}/buckets/{BUCKET_ID}"

# 1. Initiate multipart upload
mp = httpx.post(f"{BASE}/multipart/create", headers=HEADERS, json={
    "key": "large-video.mp4",
    "content_type": "video/mp4",
    "storage_class": "STANDARD"
}).json()

upload_id = mp["upload_id"]

# 2. Upload parts (5 MB each)
PART_SIZE = 5 * 1024 * 1024
parts = []

with open("large-video.mp4", "rb") as f:
    part_num = 1
    while chunk := f.read(PART_SIZE):
        # Get presigned URL for this part
        presign = httpx.post(f"{BASE}/multipart/presign-part", headers=HEADERS, json={
            "upload_id": upload_id,
            "key": "large-video.mp4",
            "part_number": part_num
        }).json()

        # Upload directly to S3
        resp = httpx.put(presign["url"], content=chunk)
        parts.append({"part_number": part_num, "etag": resp.headers["etag"]})
        part_num += 1

# 3. Complete
httpx.post(f"{BASE}/multipart/complete", headers=HEADERS, json={
    "upload_id": upload_id,
    "key": "large-video.mp4",
    "parts": parts
})

print(f"Uploaded {part_num - 1} parts successfully")

TypeScript: Set Up CDN

typescript
const API = 'https://apis.fotohub.app/v1/storage/s3';
const headers = {
  Authorization: `Bearer ${jwt}`,
  'Content-Type': 'application/json'
};
const bucketId = 'your-bucket-id';

// 1. Request SSL certificate
const cert = await fetch(`${API}/buckets/${bucketId}/cdn/certificate`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ domain_names: ['cdn.myapp.com'] })
}).then(r => r.json());

console.log('Add these DNS records for validation:');
cert.validation_records.forEach((record: any) => {
  console.log(`  ${record.type} ${record.name} -> ${record.value}`);
});

// 2. Poll for certificate validation (wait for DNS propagation)
let certStatus = 'PENDING_VALIDATION';
while (certStatus === 'PENDING_VALIDATION') {
  await new Promise(resolve => setTimeout(resolve, 30000));
  const status = await fetch(
    `${API}/buckets/${bucketId}/cdn/certificate?certificate_arn=${cert.certificate_arn}`,
    { headers }
  ).then(r => r.json());
  certStatus = status.status;
}

// 3. Create CloudFront distribution
const cdn = await fetch(`${API}/buckets/${bucketId}/cdn`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    custom_domain_names: ['cdn.myapp.com'],
    certificate_arn: cert.certificate_arn,
    default_root_object: 'index.html',
    price_class: 'PriceClass_100'
  })
}).then(r => r.json());

console.log(`CDN domain: ${cdn.distribution.domain_name}`);
console.log('Add CNAME: cdn.myapp.com -> ' + cdn.distribution.domain_name);

TypeScript: Lifecycle and Versioning

typescript
const API = 'https://apis.fotohub.app/v1/storage/s3';
const headers = {
  Authorization: `Bearer ${jwt}`,
  'Content-Type': 'application/json'
};
const bucketId = 'your-bucket-id';

// Enable versioning
await fetch(`${API}/buckets/${bucketId}/versioning`, {
  method: 'PUT',
  headers,
  body: JSON.stringify({ status: 'Enabled', mfa_delete: 'Disabled' })
});

// Set lifecycle rules
await fetch(`${API}/buckets/${bucketId}/lifecycle`, {
  method: 'PUT',
  headers,
  body: JSON.stringify({
    rules: [
      {
        id: 'optimize-storage-costs',
        status: 'Enabled',
        prefix: '',
        transitions: [
          { days: 30, storage_class: 'STANDARD_IA' },
          { days: 90, storage_class: 'GLACIER_IR' },
          { days: 365, storage_class: 'DEEP_ARCHIVE' }
        ],
        noncurrent_version_expiration: { noncurrent_days: 30 },
        abort_incomplete_multipart_upload: { days_after_initiation: 3 }
      }
    ]
  })
});

// Set up intelligent tiering for unpredictable workloads
await fetch(`${API}/buckets/${bucketId}/intelligent-tiering`, {
  method: 'PUT',
  headers,
  body: JSON.stringify({
    id: 'auto-archive',
    status: 'Enabled',
    prefix: 'data/',
    tierings: [
      { days: 90, access_tier: 'ARCHIVE_ACCESS' },
      { days: 180, access_tier: 'DEEP_ARCHIVE_ACCESS' }
    ]
  })
});

Available Regions

RegionCodeS3CDN
Frankfurteu-central-1YesYes
Irelandeu-west-1YesYes
Virginiaus-east-1YesYes
Oregonus-west-2YesYes
Singaporeap-southeast-1YesYes

TIP

Choose the region closest to your users for lowest latency. Pair with Transfer Acceleration or CDN for global distribution.


Error Handling

All endpoints return standard HTTP status codes with JSON error bodies:

json
{
  "error": "bucket_quota_exceeded",
  "message": "Storage quota of 100 GB exceeded. Current usage: 100.5 GB.",
  "details": {
    "quota_gb": 100,
    "used_gb": 100.5
  }
}
StatusMeaning
400Bad request (validation error, invalid parameters)
401Missing or invalid JWT token
403Insufficient plan or permissions
404Bucket or object not found
409Conflict (bucket name taken, operation in progress)
422Unprocessable (e.g., insufficient wallet balance)
429Rate limited
500Internal server error

Rate Limits

Endpoint GroupRate Limit
Bucket CRUD10 requests/minute
Object operations100 requests/minute
Presigned URLs200 requests/minute
Metrics/usage30 requests/minute
CDN operations5 requests/minute

Endpoint Reference

Complete list of all S3 Enterprise endpoints:

Bucket Management

MethodPathDescription
POST/v1/storage/s3/buyProvision a new S3 bucket
GET/v1/storage/s3/bucketsList all buckets
GET/v1/storage/s3/buckets/{bucket_id}Get bucket details
POST/v1/storage/s3/buckets/{bucket_id}/upgradeUpgrade bucket
DELETE/v1/storage/s3/buckets/{bucket_id}Delete bucket

Credentials

MethodPathDescription
GET/v1/storage/s3/buckets/{bucket_id}/credentialsList access keys
POST/v1/storage/s3/buckets/{bucket_id}/regenerate-keysRotate keys

Usage and Pricing

MethodPathDescription
GET/v1/storage/s3/buckets/{bucket_id}/usageCurrent usage stats
GET/v1/storage/s3/buckets/{bucket_id}/metricsCloudWatch metrics
GET/v1/storage/s3/pricingPricing table
POST/v1/storage/s3/estimateCost estimator
GET/v1/storage/s3/regionsAvailable regions

Object Operations

MethodPathDescription
POST/v1/storage/s3/buckets/{bucket_id}/objects/listList objects
POST/v1/storage/s3/buckets/{bucket_id}/objects/presign-uploadPresigned upload URL
POST/v1/storage/s3/buckets/{bucket_id}/objects/presign-downloadPresigned download URL
POST/v1/storage/s3/buckets/{bucket_id}/objects/deleteBatch delete objects
POST/v1/storage/s3/buckets/{bucket_id}/objects/copyCopy object

Multipart Upload

MethodPathDescription
POST/v1/storage/s3/buckets/{bucket_id}/multipart/createInitiate upload
POST/v1/storage/s3/buckets/{bucket_id}/multipart/presign-partPresign part URL
POST/v1/storage/s3/buckets/{bucket_id}/multipart/completeComplete upload
POST/v1/storage/s3/buckets/{bucket_id}/multipart/abortAbort upload

Permissions

MethodPathDescription
GET/PUT/DELETE/v1/storage/s3/buckets/{bucket_id}/policyBucket policy
POST/v1/storage/s3/buckets/{bucket_id}/policy/validateValidate policy
GET/PUT/DELETE/v1/storage/s3/buckets/{bucket_id}/corsCORS configuration
GET/PUT/v1/storage/s3/buckets/{bucket_id}/public-access-blockPublic access block
GET/PUT/v1/storage/s3/buckets/{bucket_id}/ownershipObject ownership
GET/v1/storage/s3/buckets/{bucket_id}/aclACL (read-only)

Security

MethodPathDescription
GET/PUT/v1/storage/s3/buckets/{bucket_id}/versioningVersioning
GET/v1/storage/s3/buckets/{bucket_id}/versionsList versions
POST/v1/storage/s3/buckets/{bucket_id}/versions/restoreRestore version
POST/v1/storage/s3/buckets/{bucket_id}/versions/deleteDelete version
GET/PUT/DELETE/v1/storage/s3/buckets/{bucket_id}/encryptionEncryption
GET/PUT/v1/storage/s3/buckets/{bucket_id}/object-lockObject lock

Behavior

MethodPathDescription
GET/PUT/DELETE/v1/storage/s3/buckets/{bucket_id}/tagsTags
GET/PUT/v1/storage/s3/buckets/{bucket_id}/loggingAccess logging
GET/PUT/v1/storage/s3/buckets/{bucket_id}/accelerateTransfer acceleration
GET/PUT/v1/storage/s3/buckets/{bucket_id}/request-paymentRequest payment
GET/PUT/v1/storage/s3/buckets/{bucket_id}/intelligent-tieringIntelligent tiering
DELETE/v1/storage/s3/buckets/{bucket_id}/intelligent-tiering/{config_id}Delete tiering config

Lifecycle

MethodPathDescription
GET/PUT/DELETE/v1/storage/s3/buckets/{bucket_id}/lifecycleLifecycle rules

Website Hosting

MethodPathDescription
GET/PUT/DELETE/v1/storage/s3/buckets/{bucket_id}/websiteStatic website config

Notifications

MethodPathDescription
GET/PUT/v1/storage/s3/buckets/{bucket_id}/notificationsEvent notifications

Access Points

MethodPathDescription
GET/v1/storage/s3/buckets/{bucket_id}/access-pointsList access points
POST/v1/storage/s3/buckets/{bucket_id}/access-pointsCreate access point
GET/v1/storage/s3/buckets/{bucket_id}/access-points/{name}Get access point
DELETE/v1/storage/s3/buckets/{bucket_id}/access-points/{name}Delete access point
GET/PUT/DELETE/v1/storage/s3/buckets/{bucket_id}/access-points/{name}/policyAccess point policy

Replication

MethodPathDescription
GET/PUT/DELETE/v1/storage/s3/buckets/{bucket_id}/replicationCross-region replication

CDN (CloudFront)

MethodPathDescription
POST/v1/storage/s3/buckets/{bucket_id}/cdn/certificateRequest SSL certificate
GET/v1/storage/s3/buckets/{bucket_id}/cdn/certificateGet certificate status
DELETE/v1/storage/s3/buckets/{bucket_id}/cdn/certificateDelete certificate
POST/v1/storage/s3/buckets/{bucket_id}/cdnCreate distribution
GET/v1/storage/s3/buckets/{bucket_id}/cdnGet/list distributions
POST/v1/storage/s3/buckets/{bucket_id}/cdn/invalidateInvalidate cache
DELETE/v1/storage/s3/buckets/{bucket_id}/cdnDisable distribution
DELETE/v1/storage/s3/buckets/{bucket_id}/cdn/finalizeFinalize delete

Inventory

MethodPathDescription
GET/v1/storage/s3/buckets/{bucket_id}/inventoryList inventory configs
PUT/v1/storage/s3/buckets/{bucket_id}/inventoryCreate/update inventory
DELETE/v1/storage/s3/buckets/{bucket_id}/inventory/{config_id}Delete inventory config