Guide to Integrating S3/MinIO Storage API

VPTS Team Content has been moderated
Published date:21/6/2026 Update:24/7/2026 Views:138 Reviews: 4.7/592reviews
VPTS on

Documentation for S3/MinIO storage API integration following S3-Compatible standards: endpoint configuration, AWS Signature V4 authentication, presigned URLs, AWS CLI, MinIO Client, Node.js/PHP SDKs, CORS, security policies, and handover checklist.

Guide S3 StorageMinIOAPIObject Storage

1. Overview

MinIO is an object storage system compatible with Amazon S3. Client applications can upload,

download, list, delete files, and generate temporary download links via S3 API.

Important note:requests called directly to the API must be signed using AWS Signature Version 4.

Do not use standard Basic Auth or Bearer Tokens for S3 APIs.

Key concepts

  • Bucket: file container, similar to a root folder.
  • Object: file stored in the bucket.
  • Object key: the file path, for example invoices/2026/INV001.pdf.
  • Presigned URL: A time-limited link for uploading or downloading without exposing secret keys.

Common usage flow

  1. Backend system receives files from users.
  2. The backend uploads the file to MinIO/S3 using the access key and secret key.
  3. The backend records the bucket, object key, mime type, and file size in the database.
  4. When downloading a file is needed, the backend generates a presigned URL and returns it to the client.

2. Connection information

Replace the sample values below with real information before handing over to the client.

InformationSample valueNote
API Endpointhttps://s3.example.comEndpoint used for S3 API. HTTPS should be enabled.
Console Endpointhttps://console-s3.example.comMinIO admin panel, granted only to authorized operators.
Regionus-east-1MinIO usually uses this value if no specific region is configured.
Access KeyProvided privately via secure channelsDo not send together in public documents.
Secret KeyProvided privately via secure channelsDo not commit to source code, do not send via public chat.
Default bucketcustomer-filesBuckets can be separated by environment: dev, staging, production.
Path styletrueShould enable forcePathStyle when using MinIO with AWS SDK.

Recommended object key structure

{module}/{yyyy}/{mm}/{uuid}-{safe-file-name}

Example:

orders/2026/06/8f3c9d7a-invoice-001.pdf

avatars/2026/06/user-1024.png

backups/2026/06/database-2026-06-20.sql.gz

Handover specifications to finalize before integration

GroupValue to provideActual entry example
Production environmentEndpoint, bucket, region, file size limithttps://s3.company.vn, crm-prod, maximum 50MB/file
Staging environmentEndpoint, test bucket, dedicated access keycrm-staging, do not share the production key
Allowed file typesMIME type listimage/jpeg, image/png, application/pdf
Link expirationPresigned URL lifespanUpload: 10 minutes, Download: 15-60 minutes
Retention/backupFile retention period and deletion policyKeep contract files for at least 5 years; temporary files are deleted after 7 days

Recommended permission matrix

Account/keyPermissionsUsed forShould not grant
app-uploaders3:PutObject, s3:GetObject, s3:ListBucketFile upload and download link generator appDo not grant user admin permissions; do not grant access to all other buckets
app-readonlys3:GetObject, s3:ListBucketRead-only file service, data synchronizationDo not grant s3:DeleteObject
backup-services3:PutObject, s3:GetObject, s3:DeleteObjectPeriodic backups, scheduled cleanup of old filesDo not share with web applications

Example S3 REST request

The example below illustrates the path and header. The Authorization value must be signed by an SDK or library

request generated, do not copy this raw sample to run in production.

PUT /customer-files/orders/2026/06/invoice.pdf HTTP/1.1 Host: s3.example.com Content-Type: application/pdf x-amz-date: 20260620T030000Z x-amz-content-sha256: <sha256-payload> Authorization: AWS4-HMAC-SHA256 Credential=<access-key>/20260620/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=<signature> Body: Nội dung file PDF dạng binary được gửi ở đây

The Body section in the example represents real file data. When using the SDK or fetch to upload a file, this section corresponds to the file/blob variable sent to MinIO.

Sample response upon successful upload

HTTP/1.1 200 OK ETag: "9b2cf535f27731c974343645a3985328" x-amz-request-id: 183A6F4A9D1C4B2E Date: Sat, 20 Jun 2026 03:00:00 GMT

3. Recommended backend API

This is an API layer that should be placed within the client's backend system. The backend is responsible for authenticating users,

verify business permissions, write to database, and generate presigned URLs. Frontend does not call MinIO using secret keys.

1. Create Upload Link

InformationDetails
EndpointPOST /api/files/presign-upload
PurposeCreate a temporary URL for the frontend to upload files directly to MinIO.
Suggested duration5-10 minutes.

Request

{ "module": "orders", "fileName": "invoice-001.pdf", "contentType": "application/pdf", "size": 245760, "ownerType": "order", "ownerId": "ORD-1001" }

Response

{ "success": true, "data": { "bucket": "customer-files", "objectKey": "orders/2026/06/8f3c9d7a-invoice-001.pdf", "uploadUrl": "https://s3.example.com/customer-files/orders/2026/06/8f3c9d7a-invoice-001.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256...", "method": "PUT", "expiresIn": 600, "requiredHeaders": { "Content-Type": "application/pdf" } } }

Frontend uploads file using signed URL

await fetch(uploadUrl, { method: "PUT", headers: { "Content-Type": "application/pdf" }, body: file });

2. Confirm upload and save metadata

InformationDetails
EndpointPOST /api/files/complete
PurposeThe backend checks that the object exists on MinIO, then saves the file record to the database.

Request

{ "bucket": "customer-files", "objectKey": "orders/2026/06/8f3c9d7a-invoice-001.pdf", "originalName": "invoice-001.pdf", "contentType": "application/pdf", "ownerType": "order", "ownerId": "ORD-1001" }

Response

{ "success": true, "data": { "id": "file_01J0YQACV8K7Z1P6D4X9", "bucket": "customer-files", "objectKey": "orders/2026/06/8f3c9d7a-invoice-001.pdf", "size": 245760, "etag": "9b2cf535f27731c974343645a3985328", "createdAt": "2026-06-20T10:00:00+07:00" } }

3. Create download link

InformationDetails
EndpointGET /api/files/{id}/download
PurposeReturn a temporary file download link after verifying user permissions.

Response

{ "success": true, "data": { "downloadUrl": "https://s3.example.com/customer-files/orders/2026/06/8f3c9d7a-invoice-001.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256...", "expiresIn": 900, "fileName": "invoice-001.pdf", "contentType": "application/pdf" } }

4. Delete file

InformationDetails
EndpointDELETE /api/files/{id}
PurposeDelete objects on MinIO or flag as deleted in the database depending on data policy.

Operational recommendations:if files relate to invoices, contracts, or legal data, use

soft delete in database first. Only permanently delete the actual object after the mandatory retention period expires.

Metadata table recommended to be stored in database

files - id UUID/string primary key - bucket varchar(100) - object_key varchar(1024) - original_name varchar(255) - content_type varchar(120) - size bigint - etag varchar(100) - owner_type varchar(50) - owner_id varchar(100) - uploaded_by user id - status uploaded | deleted | failed - created_at datetime - deleted_at datetime nullable

4. Authentication

The system uses an accessKeyId and secretAccessKey pair. Each S3 request must be signed with

AWS Signature V4. In practice, AWS SDK or MinIO SDK should be used so that the SDK automatically signs requests.

Do not call the S3 API directly from the browser using secret keys.

If the frontend needs to upload/download, the backend should generate a short-lived presigned URL and return it to the frontend.

Recommended environment variables

S3_ENDPOINT=https://s3.example.com S3_REGION=us-east-1 S3_BUCKET=customer-files S3_ACCESS_KEY=your-access-key S3_SECRET_KEY=your-secret-key S3_FORCE_PATH_STYLE=true

Authentication headers when calling S3 API directly

If the client calls HTTP directly without an SDK, the request must include headers conforming to the AWS Signature V4 standard.

This method is more error-prone than the SDK and should only be used if the customer's system already has a stable request-signing library.

Header/ParameterExampleMeaning
Hosts3.example.comDomain endpoint S3/MinIO.
x-amz-date20260620T030000ZUTC time used to sign the request.
x-amz-content-sha256UNSIGNED-PAYLOAD or SHA256 payloadHash of request payload. Some SDKs use UNSIGNED-PAYLOAD for presigned URLs.
AuthorizationAWS4-HMAC-SHA256 Credential=...The signature is calculated from the method, path, query, header, and secret key.

How Presigned URLs work

  1. Frontend requests backend to generate an upload/download link for a specific file.
  2. The backend verifies user permissions within the customer's system.
  3. The backend uses the secret key to generate a short-lived presigned URL.
  4. The frontend uses that URL to upload/download directly with MinIO.
  5. When the URL expires, the client must request a new URL and not reuse the old one.

Recommendation:secret keys remain strictly on the backend. Frontend/mobile apps must never store secret keys,

even when the application is packaged or obfuscated.

5. Commonly used S3 APIs

The endpoints below follow S3 standards. When using the SDK, simply call the corresponding command; the SDK will automatically generate

method, path, headers, and valid signature.

Business OperationsHTTPPathNote
Upload filePUT/{bucket}/{objectKey}Send file content in the body. Content-Type should be set.
Download fileGET/{bucket}/{objectKey}A private bucket requires a signed request or presigned URL.
Check metadataHEAD/{bucket}/{objectKey}Used to check file existence, size, and mime type.
Delete fileDELETE/{bucket}/{objectKey}Permissions should be checked before deleting.
List filesGET/{bucket}?list-type=2&prefix={prefix}Use prefix to filter by logical folder.

Common headers used during upload

HeaderExamplePurpose
Content-Typeapplication/pdfHelps browsers display or download files in the correct format.
Content-Dispositionattachment; filename="invoice.pdf"Suggested filename upon download.
x-amz-meta-*x-amz-meta-owner-id: 1024Store custom metadata if needed.

6. AWS CLI Example

AWS CLI can be used to quickly verify connections. All commands require adding --endpoint-url when working

with MinIO.

Profile configuration

aws configure --profile minio # Nhập: # AWS Access Key ID: your-access-key # AWS Secret Access Key: your-secret-key # Default region name: us-east-1 # Default output format: json

List buckets

aws --profile minio \ --endpoint-url https://s3.example.com \ s3 ls

Upload file

aws --profile minio \ --endpoint-url https://s3.example.com \ s3 cp ./invoice.pdf s3://customer-files/orders/2026/06/invoice.pdf \ --content-type application/pdf

Download file

aws --profile minio \ --endpoint-url https://s3.example.com \ s3 cp s3://customer-files/orders/2026/06/invoice.pdf ./invoice.pdf

Create presigned URL to download files

aws --profile minio \ --endpoint-url https://s3.example.com \ s3 presign s3://customer-files/orders/2026/06/invoice.pdf \ --expires-in 3600

7. MinIO Client

mc is the official MinIO command-line tool, suitable for administrative operations and bucket inspection,

user permissions and storage view.

Connect to MinIO using alias

mc alias set company-s3 https://s3.example.com your-access-key your-secret-key # Kiểm tra kết nối mc admin info company-s3 mc ls company-s3

Create bucket and check files

mc mb company-s3/customer-files mc ls company-s3/customer-files mc cp ./invoice.pdf company-s3/customer-files/orders/2026/06/invoice.pdf mc stat company-s3/customer-files/orders/2026/06/invoice.pdf

Create user for application

mc admin user add company-s3 app-uploader APP_UPLOADER_SECRET_CHANGE_ME

Sample policy for upload/read application

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::customer-files" ] }, { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject" ], "Resource": [ "arn:aws:s3:::customer-files/*" ] } ] }

Attach policy to user

# Lưu policy vào file app-uploader-policy.json mc admin policy create company-s3 app-uploader-policy ./app-uploader-policy.json mc admin policy attach company-s3 app-uploader-policy --user app-uploader

Enable versioning for critical buckets

mc version enable company-s3/customer-files mc version info company-s3/customer-files

Lifecycle delete temporary files after 7 days

{ "Rules": [ { "ID": "delete-temp-files-after-7-days", "Status": "Enabled", "Filter": { "Prefix": "tmp/" }, "Expiration": { "Days": 7 } } ] }
# Lưu nội dung lifecycle vào lifecycle.json mc ilm import company-s3/customer-files < lifecycle.json mc ilm ls company-s3/customer-files

Check bucket storage capacity

mc du company-s3/customer-files mc find company-s3/customer-files --name "*.pdf" --older-than 30d

8. SDK examples

Node.js with AWS SDK v3

import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import fs from "node:fs"; const s3 = new S3Client({ endpoint: process.env.S3_ENDPOINT, region: process.env.S3_REGION || "us-east-1", forcePathStyle: true, credentials: { accessKeyId: process.env.S3_ACCESS_KEY, secretAccessKey: process.env.S3_SECRET_KEY, }, }); const bucket = process.env.S3_BUCKET; const key = "orders/2026/06/invoice.pdf"; await s3.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: fs.createReadStream("./invoice.pdf"), ContentType: "application/pdf", })); const downloadUrl = await getSignedUrl( s3, new GetObjectCommand({ Bucket: bucket, Key: key }), { expiresIn: 3600 } ); console.log(downloadUrl);

Node.js generates presigned URL for frontend upload

import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; const s3 = new S3Client({ endpoint: process.env.S3_ENDPOINT, region: process.env.S3_REGION || "us-east-1", forcePathStyle: true, credentials: { accessKeyId: process.env.S3_ACCESS_KEY, secretAccessKey: process.env.S3_SECRET_KEY, }, }); export async function createUploadUrl({ bucket, key, contentType }) { const command = new PutObjectCommand({ Bucket: bucket, Key: key, ContentType: contentType, }); return getSignedUrl(s3, command, { expiresIn: 600 }); } export async function createDownloadUrl({ bucket, key, fileName }) { const command = new GetObjectCommand({ Bucket: bucket, Key: key, ResponseContentDisposition: `attachment; filename="${fileName}"`, }); return getSignedUrl(s3, command, { expiresIn: 900 }); }

PHP with aws/aws-sdk-php

<?php require __DIR__ . "/vendor/autoload.php"; use Aws\S3\S3Client; use Aws\S3\Exception\S3Exception; $s3 = new S3Client([ "version" => "latest", "region" => getenv("S3_REGION") ?: "us-east-1", "endpoint" => getenv("S3_ENDPOINT"), "use_path_style_endpoint" => true, "credentials" => [ "key" => getenv("S3_ACCESS_KEY"), "secret" => getenv("S3_SECRET_KEY"), ], ]); $bucket = getenv("S3_BUCKET"); $key = "orders/2026/06/invoice.pdf"; try { $s3->putObject([ "Bucket" => $bucket, "Key" => $key, "SourceFile" => __DIR__ . "/invoice.pdf", "ContentType" => "application/pdf", ]); $cmd = $s3->getCommand("GetObject", [ "Bucket" => $bucket, "Key" => $key, ]); $request = $s3->createPresignedRequest($cmd, "+60 minutes"); echo (string) $request->getUri(); } catch (S3Exception $e) { error_log($e->getAwsErrorCode() . ": " . $e->getMessage()); }

9. CORS & proxy

If the frontend uploads directly using a presigned URL, the bucket must have CORS configured for the application domain.

If CORS is not configured, the browser will block requests even if the URL is properly signed.

Sample CORS for private bucket

{ "CORSRules": [ { "AllowedOrigins": [ "https://app.example.com", "https://admin.example.com" ], "AllowedMethods": [ "GET", "PUT", "POST", "HEAD" ], "AllowedHeaders": [ "Authorization", "Content-Type", "x-amz-date", "x-amz-content-sha256", "x-amz-security-token", "x-amz-meta-*" ], "ExposeHeaders": [ "ETag", "Content-Length", "Content-Type" ], "MaxAgeSeconds": 3600 } ] }

Apply CORS using AWS CLI

aws --profile minio \ --endpoint-url https://s3.example.com \ s3api put-bucket-cors \ --bucket customer-files \ --cors-configuration file://cors.json

Apply CORS using MinIO Client

mc cors set company-s3/customer-files cors.json mc cors info company-s3/customer-files

contains private data. Please declare the customer's frontend domain correctly.

Sample Nginx reverse proxy for MinIO API

server { listen 443 ssl http2; server_name s3.example.com; client_max_body_size 200M; location / { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 300; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_pass http://127.0.0.1:9000; } }

Sample Nginx reverse proxy for MinIO Console

server { listen 443 ssl http2; server_name console-s3.example.com; location / { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_pass http://127.0.0.1:9001; } }

10. Conventions&security

Bucket conventions

  • Do not set production buckets to public if they contain customer data, contracts, invoices, or personal information.
  • Separate buckets by environment or by data group if clear permission separation is required.
  • Do not use the original filename as the sole object key as duplicates may occur. Add a UUID or identifier instead.

Access permission conventions

  • The access key used for an application should only have permissions on the required bucket.
  • Upload accounts do not necessarily need delete permissions if the workflow does not require deletion.
  • The secret key must be stored in environment variables or a secret manager, not hardcoded into source code.
  • Presigned URLs should have short lifespans, such as 5 to 60 minutes depending on business logic.

to detect abnormal uploads/downloads.

11. Common Errors

Error codeCommon causesResolution
AccessDeniedAccess key lacks permissions for the bucket or object.Check policy, bucket name, object key, and read/write/delete permissions.
NoSuchBucketThe bucket does not exist or the bucket name is incorrect.Create a bucket or update the S3_BUCKET variable.
NoSuchKeyObject key does not exist.Recheck object path; verify case sensitivity.
SignatureDoesNotMatchWrong secret key, wrong endpoint, wrong region, or request altered after signing.Check credentials, region, server time synchronization, and enable path-style for MinIO.
RequestTimeTooSkewedClient/server time difference is too large.Sync NTP on application servers.
EntityTooLargeFile exceeds the upload limit set by proxy, application, or infrastructure configuration.Check Nginx/Apache, backend upload limits, and consider multipart upload.
CORS errorBrowser blocked because the bucket has not allowed the frontend domain.Check AllowedOrigins, AllowedMethods, AllowedHeaders in CORS.
403 when using presigned URLExpired URL, mismatched method, or upload headers differing from signing time.If signed with Content-Type: application/pdf, the frontend must send that exact header upon upload.
InvalidAccessKeyIdThe access key is incorrect, locked, or does not exist on MinIO.Check the user using mc admin user info and create a new key if needed.
Connection refusedMinIO service is not running or the proxy points to the wrong port.Check API port 9000, console 9001, firewall, and Nginx upstream.

Checklist debug nhanh

  1. Check if endpoint connects via HTTPS: curl -I https://s3.example.com.
  2. Verify credentials using aws s3 ls --endpoint-url ... or mc ls.
  3. Verify bucket name and object key for case sensitivity.
  4. Check if the application server clock drifts from UTC time.
  5. Check that presigned URL is still valid and frontend uses the correct PUT/GET method.
  6. Check CORS if errors only occur in browsers but succeed via CLI.

12. Handover Checklist

CategoryStatusNote
HTTPS API endpoint active☐Verify SSL certificate is still valid.
Production bucket created☐Clearly state the handed-over bucket name.
Issued access key/secret key☐Send secret via a separate secure channel.
Restricted permission policy☐Grant only necessary permissions to the application.
Tested upload/download/delete☐Save logs or screenshots of test results if needed.
Backup/replication mechanism☐Specify the backup schedule and retention period.
Support person in charge☐Name, email, phone number, or ticket channel.

Customer handover information template

API Endpoint: https://s3.example.com Console: https://console-s3.example.com Region: us-east-1 Bucket: customer-files Access Key: gửi riêng Secret Key: gửi riêng Path Style: true Presigned URL: khuyến nghị dùng cho frontend, thời hạn 5-60 phút

Integration acceptance test case

STTScenarioHow to testResult achieved
1Backend generates presigned upload URLCall POST /api/files/presign-upload with a valid PDF file.Receive uploadUrl, objectKey, expiresIn.
2Frontend upload fileSend a PUT file request to uploadUrl.MinIO returns HTTP 200 or 204, with an ETag.
3Backend validates fileCall POST /api/files/complete.Database has file records, correct size, and content type.
4Download file privateCall GET /api/files/{id}/download then open downloadUrl.The downloaded file has the correct content, and the URL expires after the configured period.
5Block invalid file formatsUploaded .exe file or disallowed MIME type.The backend rejects the request before generating a presigned URL.
6Block oversized filesFile upload exceeds the agreed limit.The backend returns a clear error without creating an object on MinIO.
7Check user permissionsUser A attempts to download a file belonging to User B.The backend returns 403 Forbidden without returning a presigned URL.
8Hard delete or soft deleteCall DELETE /api/files/{id}.File status adhering to policy: delete object or mark as deleted.

Internal API error response template

{ "success": false, "error": { "code": "FILE_TYPE_NOT_ALLOWED", "message": "Định dạng file không được hỗ trợ.", "details": { "allowedTypes": ["image/jpeg", "image/png", "application/pdf"] } } }

VPTS – Comprehensive Digital Infrastructure Solutions Provider

VPTS is a digital infrastructure solution provider in Vietnam, serving deployment, operation, and data storage needs for individuals, developers, and enterprises. The service ecosystem includesVietnam VPS, International VPS, VPS Anti DDoS, Hosting cPanel, dedicated server, Rack rental, Proxy IPv4/IPv6, S3 MinIOandCloud Storage.

Besides providing infrastructure resources, VPTS also focuses on assisting customers with deployment, configuration, and practical troubleshooting. Content on the VPTS Blog is drawn from common scenarios in operating VPS, servers, and online systems, helping readers understand root causes and apply clear solutions.

VPTS – Stable infrastructure, flexible solutions, and continuous partnership throughout operations.

VPTS Team Contact Information