1373 lines
44 KiB
YAML
1373 lines
44 KiB
YAML
openapi: 3.0.3
|
|
info:
|
|
title: Insforge Storage API
|
|
version: 2.0.0
|
|
description: Bucket-based storage system similar to S3
|
|
|
|
paths:
|
|
/api/storage/buckets:
|
|
get:
|
|
summary: List All Buckets
|
|
tags:
|
|
- Admin
|
|
security:
|
|
- apiKey: []
|
|
responses:
|
|
'200':
|
|
description: List of bucket names
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
buckets:
|
|
type: array
|
|
items:
|
|
type: string
|
|
example: ["avatars", "documents", "uploads"]
|
|
example:
|
|
buckets: ["avatars", "documents", "uploads", "public", "private"]
|
|
|
|
post:
|
|
summary: Create New Bucket
|
|
tags:
|
|
- Admin
|
|
security:
|
|
- apiKey: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required:
|
|
- bucketName
|
|
properties:
|
|
bucketName:
|
|
type: string
|
|
pattern: '^[a-zA-Z0-9_-]+$'
|
|
description: Bucket name (alphanumeric, underscore, and hyphen only)
|
|
example: avatars
|
|
isPublic:
|
|
type: boolean
|
|
description: Whether the bucket is publicly accessible
|
|
default: true
|
|
example: true
|
|
responses:
|
|
'201':
|
|
description: Bucket created successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
message:
|
|
type: string
|
|
example: "Bucket created successfully"
|
|
bucketName:
|
|
type: string
|
|
example: avatars
|
|
example:
|
|
message: "Bucket created successfully"
|
|
bucket: "avatars"
|
|
'400':
|
|
description: Invalid bucket name
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "INVALID_BUCKET_NAME"
|
|
message: "Bucket name must contain only alphanumeric characters, underscores, and hyphens"
|
|
statusCode: 400
|
|
nextActions: "Use a valid bucket name format"
|
|
'409':
|
|
description: Bucket already exists
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "BUCKET_EXISTS"
|
|
message: "Bucket 'avatars' already exists"
|
|
statusCode: 409
|
|
nextActions: "Choose a different bucket name"
|
|
|
|
/api/storage/buckets/{bucketName}:
|
|
delete:
|
|
summary: Delete Bucket
|
|
description: Delete an entire bucket and all its objects
|
|
tags:
|
|
- Admin
|
|
security:
|
|
- apiKey: []
|
|
parameters:
|
|
- name: bucketName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[a-zA-Z0-9_-]+$'
|
|
example: avatars
|
|
responses:
|
|
'200':
|
|
description: Bucket deleted successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
message:
|
|
type: string
|
|
nextActions:
|
|
type: string
|
|
example:
|
|
message: "Bucket deleted successfully"
|
|
nextActions: "You can use POST /api/storage/buckets to create a new bucket, and GET /api/storage/buckets/:bucketName/objects to list the objects in the bucket."
|
|
'404':
|
|
description: Bucket not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "BUCKET_NOT_FOUND"
|
|
message: "Bucket 'nonexistent' does not exist"
|
|
statusCode: 404
|
|
nextActions: "Check bucket name and try again"
|
|
|
|
patch:
|
|
summary: Update Bucket
|
|
tags:
|
|
- Admin
|
|
security:
|
|
- apiKey: []
|
|
parameters:
|
|
- name: bucketName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[a-zA-Z0-9_-]+$'
|
|
example: avatars
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required:
|
|
- is_public
|
|
properties:
|
|
isPublic:
|
|
type: boolean
|
|
description: Whether the bucket should be publicly accessible
|
|
example: true
|
|
responses:
|
|
'200':
|
|
description: Bucket visibility updated successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
message:
|
|
type: string
|
|
example: "Bucket visibility updated"
|
|
bucket:
|
|
type: string
|
|
example: avatars
|
|
isPublic:
|
|
type: boolean
|
|
example: true
|
|
example:
|
|
message: "Bucket visibility updated"
|
|
bucket: "avatars"
|
|
isPublic: true
|
|
'404':
|
|
description: Bucket not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "BUCKET_NOT_FOUND"
|
|
message: "Bucket 'nonexistent' does not exist"
|
|
statusCode: 404
|
|
nextActions: "Check bucket name and try again"
|
|
|
|
/api/storage/buckets/{bucketName}/objects:
|
|
get:
|
|
summary: List Objects in Bucket
|
|
tags:
|
|
- Admin
|
|
security:
|
|
- apiKey: []
|
|
parameters:
|
|
- name: bucketName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[a-zA-Z0-9_-]+$'
|
|
example: avatars
|
|
- name: prefix
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
description: Filter objects by key prefix
|
|
example: users/
|
|
- name: limit
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 1000
|
|
default: 100
|
|
- name: offset
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
minimum: 0
|
|
default: 0
|
|
- name: search
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
description: Search objects by key (partial match)
|
|
example: profile
|
|
responses:
|
|
'200':
|
|
description: List of objects in bucket
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/StoredFile'
|
|
pagination:
|
|
type: object
|
|
properties:
|
|
offset:
|
|
type: integer
|
|
example: 0
|
|
limit:
|
|
type: integer
|
|
example: 100
|
|
total:
|
|
type: integer
|
|
example: 2
|
|
nextActions:
|
|
type: string
|
|
example: "You can use PUT /api/storage/buckets/:bucketName/objects/:objectKey to upload with a specific key, or POST /api/storage/buckets/:bucketName/objects to upload with auto-generated key, and GET /api/storage/buckets/:bucketName/objects/:objectKey to download an object."
|
|
example:
|
|
data:
|
|
- bucket: "avatars"
|
|
key: "users/user123.jpg"
|
|
size: 102400
|
|
mimeType: "image/jpeg"
|
|
uploadedAt: "2024-01-15T10:30:00Z"
|
|
url: "/api/storage/buckets/avatars/objects/users/user123.jpg"
|
|
- bucket: "avatars"
|
|
key: "users/user456.png"
|
|
size: 204800
|
|
mimeType: "image/png"
|
|
uploadedAt: "2024-01-16T11:00:00Z"
|
|
url: "/api/storage/buckets/avatars/objects/users/user456.png"
|
|
pagination:
|
|
offset: 0
|
|
limit: 100
|
|
total: 2
|
|
nextActions: "You can use PUT /api/storage/buckets/:bucketName/objects/:objectKey to upload with a specific key, or POST /api/storage/buckets/:bucketName/objects to upload with auto-generated key, and GET /api/storage/buckets/:bucketName/objects/:objectKey to download an object."
|
|
|
|
post:
|
|
summary: Upload Object with Auto-Generated Key
|
|
tags:
|
|
- Client
|
|
security:
|
|
- apiKey: []
|
|
parameters:
|
|
- name: bucketName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[a-zA-Z0-9_-]+$'
|
|
example: avatars
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
multipart/form-data:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
file:
|
|
type: string
|
|
format: binary
|
|
description: File to upload
|
|
required:
|
|
- file
|
|
responses:
|
|
'201':
|
|
description: Object uploaded successfully with auto-generated key
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/StoredFile'
|
|
example:
|
|
bucket: "avatars"
|
|
key: "image-1737546841234-a3f2b1.jpg"
|
|
size: 102400
|
|
mimeType: "image/jpeg"
|
|
uploadedAt: "2024-01-21T10:30:00Z"
|
|
url: "/api/storage/buckets/avatars/objects/image-1737546841234-a3f2b1.jpg"
|
|
'400':
|
|
description: Invalid bucket name or file
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "INVALID_FILE"
|
|
message: "No file provided in the request"
|
|
statusCode: 400
|
|
nextActions: "Include a file in the multipart form data"
|
|
'404':
|
|
description: Bucket not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "BUCKET_NOT_FOUND"
|
|
message: "Bucket 'nonexistent' does not exist"
|
|
statusCode: 404
|
|
nextActions: "Create the bucket first"
|
|
|
|
delete:
|
|
summary: Delete Multiple Objects
|
|
description: Deletes up to 1000 objects from a bucket for the authenticated caller. Object row deletion is authorized through storage RLS, and the response reports per-key outcomes for rows deleted from the database, keys not visible or missing, and provider-level failures.
|
|
tags:
|
|
- Client
|
|
security:
|
|
- apiKey: []
|
|
parameters:
|
|
- name: bucketName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[a-zA-Z0-9_-]+$'
|
|
example: avatars
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required:
|
|
- keys
|
|
properties:
|
|
keys:
|
|
type: array
|
|
minItems: 1
|
|
maxItems: 1000
|
|
items:
|
|
type: string
|
|
minLength: 1
|
|
example:
|
|
keys:
|
|
- users/user123.jpg
|
|
- users/user456.png
|
|
responses:
|
|
'200':
|
|
description: Batch delete result
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required:
|
|
- results
|
|
properties:
|
|
results:
|
|
type: array
|
|
items:
|
|
type: object
|
|
required:
|
|
- key
|
|
- status
|
|
properties:
|
|
key:
|
|
type: string
|
|
status:
|
|
type: string
|
|
enum:
|
|
- deleted
|
|
- notFound
|
|
- failed
|
|
message:
|
|
type: string
|
|
description: Present when status is failed.
|
|
example:
|
|
results:
|
|
- key: users/user123.jpg
|
|
status: deleted
|
|
- key: users/missing.png
|
|
status: notFound
|
|
- key: users/locked.png
|
|
status: failed
|
|
message: Failed to delete object
|
|
'400':
|
|
description: Invalid bucket name or object keys
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
'403':
|
|
description: Permission denied
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
/api/storage/buckets/{bucketName}/upload-strategy:
|
|
post:
|
|
summary: Get Upload Strategy (Direct or Presigned URL)
|
|
description: Returns upload strategy based on storage backend (S3 returns presigned URLs, local returns direct upload endpoints)
|
|
tags:
|
|
- Client
|
|
security:
|
|
- apiKey: []
|
|
parameters:
|
|
- name: bucketName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[a-zA-Z0-9_-]+$'
|
|
example: avatars
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required:
|
|
- filename
|
|
properties:
|
|
filename:
|
|
type: string
|
|
description: Original filename for generating unique key
|
|
example: profile-photo.jpg
|
|
contentType:
|
|
type: string
|
|
description: MIME type of the file
|
|
example: image/jpeg
|
|
size:
|
|
type: integer
|
|
description: File size in bytes
|
|
example: 102400
|
|
responses:
|
|
'200':
|
|
description: Upload strategy details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UploadStrategy'
|
|
examples:
|
|
s3:
|
|
summary: S3 Backend Response
|
|
value:
|
|
method: presigned
|
|
uploadUrl: https://s3-bucket.amazonaws.com/
|
|
fields:
|
|
bucket: my-s3-bucket
|
|
key: app-key/avatars/profile-photo-1234567890-abc123.jpg
|
|
X-Amz-Algorithm: AWS4-HMAC-SHA256
|
|
X-Amz-Credential: AKIA.../20250905/us-east-2/s3/aws4_request
|
|
X-Amz-Date: 20250905T000000Z
|
|
Policy: eyJ...
|
|
X-Amz-Signature: abc123...
|
|
key: profile-photo-1234567890-abc123.jpg
|
|
confirmRequired: true
|
|
confirmUrl: /api/storage/buckets/avatars/objects/profile-photo-1234567890-abc123.jpg/confirm-upload
|
|
expiresAt: "2025-09-05T01:00:00Z"
|
|
local:
|
|
summary: Local Storage Response
|
|
value:
|
|
method: direct
|
|
uploadUrl: /api/storage/buckets/avatars/objects/profile-photo-1234567890-abc123.jpg
|
|
key: profile-photo-1234567890-abc123.jpg
|
|
confirmRequired: false
|
|
'404':
|
|
description: Bucket not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
/api/storage/buckets/{bucketName}/objects/{objectKey}:
|
|
put:
|
|
summary: Upload Object
|
|
tags:
|
|
- Client
|
|
security:
|
|
- apiKey: []
|
|
parameters:
|
|
- name: bucketName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[a-zA-Z0-9_-]+$'
|
|
example: avatars
|
|
- name: objectKey
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
example: user123.jpg
|
|
description: Object key (can include forward slashes for pseudo-folders)
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
multipart/form-data:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
file:
|
|
type: string
|
|
format: binary
|
|
description: File to upload
|
|
required:
|
|
- file
|
|
responses:
|
|
'201':
|
|
description: Object uploaded successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/StoredFile'
|
|
example:
|
|
bucket: "avatars"
|
|
key: "user123.jpg"
|
|
size: 102400
|
|
mimeType: "image/jpeg"
|
|
uploadedAt: "2024-01-21T10:30:00Z"
|
|
url: "/api/storage/buckets/avatars/objects/user123.jpg"
|
|
'400':
|
|
description: Invalid bucket name, key, or file
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "INVALID_FILE"
|
|
message: "No file provided in the request"
|
|
statusCode: 400
|
|
nextActions: "Include a file in the multipart form data"
|
|
'404':
|
|
description: Bucket not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "BUCKET_NOT_FOUND"
|
|
message: "Bucket 'nonexistent' does not exist"
|
|
statusCode: 404
|
|
nextActions: "Create the bucket first"
|
|
|
|
get:
|
|
summary: Download Object
|
|
tags:
|
|
- Client
|
|
security:
|
|
- apiKey: []
|
|
parameters:
|
|
- name: bucketName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[a-zA-Z0-9_-]+$'
|
|
example: avatars
|
|
- name: key
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
example: user123.jpg
|
|
responses:
|
|
'200':
|
|
description: File content
|
|
content:
|
|
'*/*':
|
|
schema:
|
|
type: string
|
|
format: binary
|
|
headers:
|
|
Content-Type:
|
|
schema:
|
|
type: string
|
|
description: MIME type of the file
|
|
Content-Length:
|
|
schema:
|
|
type: integer
|
|
description: Size of the file in bytes
|
|
'404':
|
|
description: Object not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "OBJECT_NOT_FOUND"
|
|
message: "Object 'user123.jpg' not found in bucket 'avatars'"
|
|
statusCode: 404
|
|
nextActions: "Check the bucket and key combination"
|
|
|
|
delete:
|
|
summary: Delete Object
|
|
tags:
|
|
- Client
|
|
security:
|
|
- apiKey: []
|
|
parameters:
|
|
- name: bucketName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[a-zA-Z0-9_-]+$'
|
|
example: avatars
|
|
- name: key
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
example: user123.jpg
|
|
responses:
|
|
'200':
|
|
description: Object deleted successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
message:
|
|
type: string
|
|
example:
|
|
message: "Object deleted successfully"
|
|
'404':
|
|
description: Object not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "OBJECT_NOT_FOUND"
|
|
message: "Object 'user123.jpg' not found in bucket 'avatars'"
|
|
statusCode: 404
|
|
nextActions: "Check the bucket and key combination"
|
|
|
|
/api/storage/buckets/{bucketName}/objects/{objectKey}/confirm-upload:
|
|
post:
|
|
summary: Confirm Presigned Upload
|
|
description: Confirms that a file was successfully uploaded to S3 using presigned URL
|
|
tags:
|
|
- Client
|
|
security:
|
|
- apiKey: []
|
|
parameters:
|
|
- name: bucketName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[a-zA-Z0-9_-]+$'
|
|
example: avatars
|
|
- name: objectKey
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
example: profile-photo-1234567890-abc123.jpg
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required:
|
|
- size
|
|
properties:
|
|
size:
|
|
type: integer
|
|
description: File size in bytes
|
|
example: 102400
|
|
contentType:
|
|
type: string
|
|
description: MIME type of the file
|
|
example: image/jpeg
|
|
etag:
|
|
type: string
|
|
description: S3 ETag of the uploaded object (optional)
|
|
example: "9bb58f26192e4ba00f01e2e7b136bbd8"
|
|
responses:
|
|
'201':
|
|
description: Upload confirmed successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/StoredFile'
|
|
'404':
|
|
description: Upload not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "UPLOAD_NOT_FOUND"
|
|
message: "Upload not found for key 'profile-photo.jpg' in bucket 'avatars'"
|
|
statusCode: 404
|
|
'409':
|
|
description: Already confirmed
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "ALREADY_CONFIRMED"
|
|
message: "File 'profile-photo.jpg' already confirmed in bucket 'avatars'"
|
|
statusCode: 409
|
|
|
|
/api/storage/buckets/{bucketName}/download-strategy/objects/{objectKey}:
|
|
get:
|
|
summary: Get Download Strategy (Direct or Presigned URL)
|
|
description: |
|
|
Returns download strategy based on storage backend and bucket visibility.
|
|
- S3 with public bucket: Direct URLs (no presigning, better performance)
|
|
- S3 with private bucket: Presigned URLs with expiration
|
|
- Local storage: Always direct endpoints
|
|
|
|
Expiry (when presigned) is auto-calculated server-side from bucket
|
|
visibility; no request body is accepted.
|
|
|
|
Object keys may contain `/` (pseudo-folders); the path is matched
|
|
with a wildcard, so callers should NOT percent-encode `/` inside
|
|
`objectKey`.
|
|
tags:
|
|
- Client
|
|
security:
|
|
- apiKey: []
|
|
parameters:
|
|
- name: bucketName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[a-zA-Z0-9_-]+$'
|
|
example: avatars
|
|
- name: objectKey
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
example: profile-photo.jpg
|
|
responses:
|
|
'200':
|
|
description: Download strategy details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/DownloadStrategy'
|
|
examples:
|
|
s3-public:
|
|
summary: S3 Public Bucket Response
|
|
value:
|
|
method: direct
|
|
url: https://s3-bucket.s3.us-east-2.amazonaws.com/app-key/public-assets/logo.png
|
|
s3-private:
|
|
summary: S3 Private Bucket Response
|
|
value:
|
|
method: presigned
|
|
url: https://s3-bucket.s3.us-east-2.amazonaws.com/app-key/avatars/profile.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...
|
|
expiresAt: "2025-09-05T01:00:00Z"
|
|
local:
|
|
summary: Local Storage Response
|
|
value:
|
|
method: direct
|
|
url: /api/storage/buckets/avatars/objects/profile-photo.jpg
|
|
'404':
|
|
description: Object not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
/api/storage/buckets/{bucketName}/objects/{objectKey}/download-strategy:
|
|
post:
|
|
deprecated: true
|
|
summary: Get Download Strategy (Deprecated — use GET on the canonical path)
|
|
description: |
|
|
Deprecated alias of
|
|
`GET /api/storage/buckets/{bucketName}/download-strategy/objects/{objectKey}`.
|
|
Retained at the original path/method for backward compatibility with
|
|
SDK releases that already shipped against this POST endpoint. Any
|
|
request body is ignored.
|
|
tags:
|
|
- Client
|
|
security:
|
|
- apiKey: []
|
|
parameters:
|
|
- name: bucketName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[a-zA-Z0-9_-]+$'
|
|
example: avatars
|
|
- name: objectKey
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
example: profile-photo.jpg
|
|
responses:
|
|
'200':
|
|
description: Download strategy details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/DownloadStrategy'
|
|
'404':
|
|
description: Object not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
/api/storage/s3/config:
|
|
get:
|
|
summary: Get S3 Gateway Config
|
|
description: |
|
|
Return the externally-reachable endpoint URL and the SigV4 signing
|
|
region for the S3 protocol gateway. The endpoint is assembled from
|
|
the server's `VITE_API_BASE_URL` env var plus `/storage/v1/s3`, and
|
|
the region is `AWS_REGION` (default `us-east-2`).
|
|
|
|
Intended for Dashboard display so the UI doesn't make any
|
|
backend-topology assumptions client-side.
|
|
tags:
|
|
- S3 Access Keys
|
|
security:
|
|
- apiKey: []
|
|
responses:
|
|
'200':
|
|
description: Gateway config
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/S3GatewayConfig'
|
|
example:
|
|
data:
|
|
endpoint: https://proj.insforge.app/storage/v1/s3
|
|
region: us-east-2
|
|
'401':
|
|
description: Missing or invalid API key / admin token
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
/api/storage/s3/access-keys:
|
|
get:
|
|
summary: List S3 Access Keys
|
|
description: |
|
|
Return every S3 access key configured for this project. Plaintext
|
|
secrets are **never** returned here — the secret is only shown once
|
|
in the response of `POST /api/storage/s3/access-keys`.
|
|
tags:
|
|
- S3 Access Keys
|
|
security:
|
|
- apiKey: []
|
|
responses:
|
|
'200':
|
|
description: List of access keys (without secrets)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/S3AccessKey'
|
|
'401':
|
|
description: Missing or invalid API key / admin token
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
'429':
|
|
description: Rate limit exceeded (20 requests / 15 min / IP)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
post:
|
|
summary: Create S3 Access Key
|
|
description: |
|
|
Mint a new S3 credential pair usable against the `/storage/v1/s3`
|
|
protocol gateway. The plaintext `secretAccessKey` in the response
|
|
is returned **exactly once** — it is encrypted at rest and can
|
|
never be retrieved again. If you lose it, revoke and re-create.
|
|
|
|
Limits:
|
|
- 50 keys per project (hard cap, enforced transactionally).
|
|
- Rate-limited to 20 management requests per 15 min per IP.
|
|
tags:
|
|
- S3 Access Keys
|
|
security:
|
|
- apiKey: []
|
|
requestBody:
|
|
required: false
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateS3AccessKeyRequest'
|
|
responses:
|
|
'201':
|
|
description: Access key created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/S3AccessKeyWithSecret'
|
|
example:
|
|
data:
|
|
id: 11111111-1111-1111-1111-111111111111
|
|
accessKeyId: INSFABC123DEF456GH78
|
|
secretAccessKey: x7K2-a_pL9qRs4N8vYzWcE1fH5gJ3mUtBoD6ViXk
|
|
description: backup-script
|
|
createdAt: "2026-04-22T00:00:00Z"
|
|
lastUsedAt: null
|
|
'400':
|
|
description: Validation error or 50-key cap reached
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
'401':
|
|
description: Missing or invalid API key / admin token
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
'429':
|
|
description: Rate limit exceeded (20 requests / 15 min / IP)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
/api/storage/s3/access-keys/{id}:
|
|
delete:
|
|
summary: Revoke S3 Access Key
|
|
description: |
|
|
Revoke an S3 access key by its id. The in-memory LRU cache is
|
|
invalidated synchronously so subsequent S3 protocol requests with
|
|
this credential return `403 InvalidAccessKeyId`.
|
|
tags:
|
|
- S3 Access Keys
|
|
security:
|
|
- apiKey: []
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
description: UUID of the access key to revoke (from the create/list response)
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
responses:
|
|
'204':
|
|
description: Access key revoked
|
|
'401':
|
|
description: Missing or invalid API key / admin token
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
'404':
|
|
description: Access key not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
'429':
|
|
description: Rate limit exceeded (20 requests / 15 min / IP)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
/storage/v1/s3/{path}:
|
|
parameters:
|
|
- name: path
|
|
in: path
|
|
required: true
|
|
description: |
|
|
Standard path-style S3 path, e.g. `my-bucket/object-key`,
|
|
`my-bucket` (bucket-level ops), or empty for ListBuckets.
|
|
schema:
|
|
type: string
|
|
description: |
|
|
**AWS S3-compatible protocol gateway.** Speak the S3 protocol directly
|
|
from any SigV4 client (AWS SDKs, `aws` CLI, `rclone`, Terraform, etc.)
|
|
against this endpoint. Only path-style URLs are supported; SDKs must
|
|
be configured with `forcePathStyle: true` and the signing region
|
|
`us-east-2` (or the value of `AWS_REGION` on this backend).
|
|
|
|
Supported operations: ListBuckets, CreateBucket, DeleteBucket,
|
|
HeadBucket, ListObjectsV2, PutObject (streaming + UNSIGNED +
|
|
pre-hashed), GetObject (incl. Range), HeadObject, DeleteObject,
|
|
DeleteObjects (batch), CopyObject, CreateMultipartUpload,
|
|
UploadPart, CompleteMultipartUpload, AbortMultipartUpload,
|
|
ListParts, plus `GetBucketLocation` / `GetBucketVersioning` stubs.
|
|
|
|
Buckets and objects are shared with the REST API — an object uploaded
|
|
via S3 protocol appears immediately in `GET /api/storage/buckets/{bucket}/objects`
|
|
and vice versa.
|
|
|
|
Example (AWS SDK v3):
|
|
|
|
```
|
|
new S3Client({
|
|
endpoint: 'https://<host>/storage/v1/s3',
|
|
region: 'us-east-2',
|
|
forcePathStyle: true,
|
|
credentials: { accessKeyId, secretAccessKey },
|
|
});
|
|
```
|
|
|
|
Errors use S3-style XML (`<Error><Code>...</Code>...</Error>`).
|
|
get:
|
|
summary: S3 protocol (GET)
|
|
description: Dispatched to ListBuckets / ListObjectsV2 / GetObject / HeadObject / ListParts / GetBucketLocation / GetBucketVersioning based on path shape and query.
|
|
tags:
|
|
- S3 Protocol
|
|
security:
|
|
- s3SigV4: []
|
|
responses:
|
|
'200':
|
|
description: Success
|
|
'206':
|
|
description: Partial content (Range GetObject)
|
|
'403':
|
|
description: SignatureDoesNotMatch / InvalidAccessKeyId / RequestTimeTooSkewed
|
|
'404':
|
|
description: NoSuchBucket / NoSuchKey
|
|
'501':
|
|
description: NotImplemented (backend is not S3-compatible, or op unsupported)
|
|
put:
|
|
summary: S3 protocol (PUT)
|
|
description: Dispatched to PutObject / UploadPart / CopyObject / CreateBucket based on path shape, query, and headers.
|
|
tags:
|
|
- S3 Protocol
|
|
security:
|
|
- s3SigV4: []
|
|
responses:
|
|
'200':
|
|
description: Success
|
|
'400':
|
|
description: EntityTooLarge / InvalidArgument / InvalidBucketName / MalformedXML
|
|
'403':
|
|
description: SignatureDoesNotMatch / AuthorizationHeaderMalformed
|
|
'409':
|
|
description: BucketAlreadyOwnedByYou
|
|
post:
|
|
summary: S3 protocol (POST)
|
|
description: Dispatched to CreateMultipartUpload / CompleteMultipartUpload / DeleteObjects based on query.
|
|
tags:
|
|
- S3 Protocol
|
|
security:
|
|
- s3SigV4: []
|
|
responses:
|
|
'200':
|
|
description: Success
|
|
delete:
|
|
summary: S3 protocol (DELETE)
|
|
description: Dispatched to DeleteObject / DeleteBucket / AbortMultipartUpload based on path and query.
|
|
tags:
|
|
- S3 Protocol
|
|
security:
|
|
- s3SigV4: []
|
|
responses:
|
|
'204':
|
|
description: Success
|
|
'409':
|
|
description: BucketNotEmpty
|
|
head:
|
|
summary: S3 protocol (HEAD)
|
|
description: Dispatched to HeadBucket / HeadObject based on path shape.
|
|
tags:
|
|
- S3 Protocol
|
|
security:
|
|
- s3SigV4: []
|
|
responses:
|
|
'200':
|
|
description: Success
|
|
'404':
|
|
description: NoSuchBucket / NoSuchKey
|
|
|
|
components:
|
|
securitySchemes:
|
|
apiKey:
|
|
type: apiKey
|
|
in: header
|
|
name: x-api-key
|
|
s3SigV4:
|
|
type: http
|
|
scheme: aws4-hmac-sha256
|
|
description: |
|
|
AWS SigV4 authentication using S3 access keys minted by
|
|
`POST /api/storage/s3/access-keys`. Region must match the server's
|
|
`AWS_REGION` (default `us-east-2`); service scope must be `s3`.
|
|
|
|
schemas:
|
|
StoredFile:
|
|
type: object
|
|
properties:
|
|
bucket:
|
|
type: string
|
|
example: avatars
|
|
description: Name of the bucket containing the object
|
|
key:
|
|
type: string
|
|
example: user123.jpg
|
|
description: Unique key identifying the object within the bucket
|
|
size:
|
|
type: integer
|
|
example: 102400
|
|
description: Size of the file in bytes
|
|
mimeType:
|
|
type: string
|
|
example: image/jpeg
|
|
description: MIME type of the file
|
|
uploadedAt:
|
|
type: string
|
|
format: date-time
|
|
example: "2024-01-15T10:30:00Z"
|
|
description: ISO timestamp when the file was uploaded
|
|
url:
|
|
type: string
|
|
example: "/api/storage/buckets/avatars/objects/user123.jpg"
|
|
description: URL to download the file
|
|
required:
|
|
- bucket
|
|
- key
|
|
- size
|
|
- uploadedAt
|
|
- url
|
|
|
|
Pagination:
|
|
type: object
|
|
properties:
|
|
limit:
|
|
type: integer
|
|
example: 100
|
|
offset:
|
|
type: integer
|
|
example: 0
|
|
total:
|
|
type: integer
|
|
example: 1
|
|
required:
|
|
- limit
|
|
- offset
|
|
- total
|
|
|
|
UploadStrategy:
|
|
type: object
|
|
required:
|
|
- method
|
|
- uploadUrl
|
|
- key
|
|
- confirmRequired
|
|
properties:
|
|
method:
|
|
type: string
|
|
enum: [presigned, direct]
|
|
description: Upload method - presigned for S3, direct for local storage
|
|
example: presigned
|
|
uploadUrl:
|
|
type: string
|
|
description: URL to upload the file to
|
|
example: https://s3-bucket.amazonaws.com/
|
|
fields:
|
|
type: object
|
|
description: Form fields for presigned POST (S3 only)
|
|
additionalProperties: true
|
|
example:
|
|
bucket: my-s3-bucket
|
|
key: app-key/avatars/profile.jpg
|
|
X-Amz-Algorithm: AWS4-HMAC-SHA256
|
|
key:
|
|
type: string
|
|
description: Generated unique key for the file
|
|
example: profile-photo-1234567890-abc123.jpg
|
|
confirmRequired:
|
|
type: boolean
|
|
description: Whether upload confirmation is required
|
|
example: true
|
|
confirmUrl:
|
|
type: string
|
|
description: URL to confirm the upload (if confirmRequired is true)
|
|
example: /api/storage/buckets/avatars/objects/profile.jpg/confirm-upload
|
|
expiresAt:
|
|
type: string
|
|
format: date-time
|
|
description: Expiration time for presigned URL (S3 only)
|
|
example: "2025-09-05T01:00:00Z"
|
|
|
|
DownloadStrategy:
|
|
type: object
|
|
required:
|
|
- method
|
|
- url
|
|
properties:
|
|
method:
|
|
type: string
|
|
enum: [presigned, direct]
|
|
description: |
|
|
Download method:
|
|
- `direct`: Direct URL access (S3 public buckets or local storage)
|
|
- `presigned`: Secure URL with signature and expiration (S3 private buckets)
|
|
example: direct
|
|
url:
|
|
type: string
|
|
description: URL to download the file from
|
|
example: https://s3-bucket.s3.us-east-2.amazonaws.com/app-key/public-assets/logo.png
|
|
expiresAt:
|
|
type: string
|
|
format: date-time
|
|
description: Expiration time for presigned URLs (only present when method is 'presigned')
|
|
example: "2025-09-05T01:00:00Z"
|
|
headers:
|
|
type: object
|
|
description: Optional headers to include in the download request
|
|
additionalProperties: true
|
|
|
|
ErrorResponse:
|
|
type: object
|
|
required:
|
|
- error
|
|
- message
|
|
- statusCode
|
|
properties:
|
|
error:
|
|
type: string
|
|
description: Error code for programmatic handling
|
|
example: "VALIDATION_ERROR"
|
|
message:
|
|
type: string
|
|
description: Human-readable error message
|
|
example: "Invalid request"
|
|
statusCode:
|
|
type: integer
|
|
description: HTTP status code
|
|
example: 400
|
|
nextActions:
|
|
type: string
|
|
description: Suggested action to resolve the error
|
|
example: "Check your request parameters"
|
|
|
|
S3GatewayConfig:
|
|
type: object
|
|
description: Read-only config of the S3 protocol gateway.
|
|
required:
|
|
- endpoint
|
|
- region
|
|
properties:
|
|
endpoint:
|
|
type: string
|
|
description: Full URL clients should configure as their S3 endpoint.
|
|
example: https://proj.insforge.app/storage/v1/s3
|
|
region:
|
|
type: string
|
|
description: SigV4 signing region the gateway validates incoming requests against.
|
|
example: us-east-2
|
|
|
|
S3AccessKey:
|
|
type: object
|
|
description: An S3 access key record (without the plaintext secret).
|
|
required:
|
|
- id
|
|
- accessKeyId
|
|
- description
|
|
- createdAt
|
|
- lastUsedAt
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
description: Internal UUID of the access key record
|
|
example: 11111111-1111-1111-1111-111111111111
|
|
accessKeyId:
|
|
type: string
|
|
pattern: '^INSF[A-Z0-9]{16}$'
|
|
description: 20-character access key id with fixed `INSF` prefix
|
|
example: INSFABC123DEF456GH78
|
|
description:
|
|
type: string
|
|
nullable: true
|
|
description: User-supplied label (max 200 chars)
|
|
example: backup-script
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
example: "2026-04-22T00:00:00Z"
|
|
lastUsedAt:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: Updated asynchronously after each successful SigV4 verification
|
|
example: null
|
|
|
|
S3AccessKeyWithSecret:
|
|
allOf:
|
|
- $ref: '#/components/schemas/S3AccessKey'
|
|
- type: object
|
|
required:
|
|
- secretAccessKey
|
|
properties:
|
|
secretAccessKey:
|
|
type: string
|
|
description: |
|
|
40-character base64url secret. **Returned only once** in the
|
|
create response — retrieve, store, and keep it secret.
|
|
example: x7K2-a_pL9qRs4N8vYzWcE1fH5gJ3mUtBoD6ViXk
|
|
|
|
CreateS3AccessKeyRequest:
|
|
type: object
|
|
properties:
|
|
description:
|
|
type: string
|
|
maxLength: 200
|
|
description: Optional label to help you identify the key later
|
|
example: backup-script
|