Back

Blog details

AIOZ Storage Go SDK Quickstart Using aws-sdk-go-v2

AIOZ Network
8 min readAugust 15, 2026
aioz-storage
AIOZ Storage cover: AIOZ Storage Go SDK Quickstart

The AIOZ Storage Go SDK is the standard AWS SDK for Go v2, aws-sdk-go-v2, pointed at https://s3.aiozstorage.network with path-style requests, and there's no separate AIOZ module to install. This guide covers the client setup, uploads from any io.Reader, listing, downloads, deletes and error handling.

TL;DR:

  • Use github.com/aws/aws-sdk-go-v2/service/s3, the same package you'd use against Amazon S3.
  • Set the endpoint, a us-east-1 signing region and UsePathStyle = true. Bucket subdomains don't resolve in DNS, so virtual-hosted requests fail.
  • PutObjectInput.Body takes any io.Reader. A file or bytes.Reader just works. A pipe or network stream needs ContentLength, or AIOZ Storage answers MissingContentLength.
  • After setup, listing, downloads and deletes are plain aws-sdk-go-v2 calls.

Updated Oct 19, 2026: added a section on PutObjectInput.Body and io.Reader with a real test run, the current BaseEndpoint setup next to the docs' deprecated resolver, the DNS check behind path style, and API error codes.

Is There a Dedicated AIOZ Storage Go SDK?

No. AIOZ Storage speaks the S3 API, so you use the official AWS module. Install the three packages this guide uses:

go get github.com/aws/aws-sdk-go-v2/aws
go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/service/s3

That's the same "standard SDK, different endpoint" pattern as the AIOZ docs for JavaScript and Python.

Configuring the S3 Client for AIOZ Storage

The AIOZ Storage Go docs use this configuration, with the endpoint filled in:

package main

import (
	"context"
	"log"
	"os"
	"strings"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/s3"
)

func main() {
	resolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
		return aws.Endpoint{
			URL:           "https://s3.aiozstorage.network",
			SigningRegion: "us-east-1",
		}, nil
	})

	credentials := aws.CredentialsProviderFunc(func(ctx context.Context) (aws.Credentials, error) {
		return aws.Credentials{
			AccessKeyID:     "YOUR_ACCESS_KEY_ID",
			SecretAccessKey: "YOUR_SECRET_ACCESS_KEY",
		}, nil
	})

	cfg, err := config.LoadDefaultConfig(context.TODO(),
		config.WithCredentialsProvider(credentials),
		config.WithEndpointResolverWithOptions(resolver),
	)
	if err != nil {
		log.Fatal(err)
	}

	s3Client := s3.NewFromConfig(cfg, func(o *s3.Options) {
		o.UsePathStyle = true
	})
}

On current SDK versions, EndpointResolverWithOptionsFunc and config.WithEndpointResolverWithOptions carry a deprecation note: "The global endpoint resolution interface is deprecated." The code still compiles and works, but if you'd rather avoid the warning, set the endpoint on the S3 client instead:

s3Client := s3.NewFromConfig(cfg, func(o *s3.Options) {
	o.BaseEndpoint = aws.String("https://s3.aiozstorage.network")
	o.UsePathStyle = true
})
// cfg comes from config.LoadDefaultConfig(ctx,
//     config.WithRegion("us-east-1"),
//     config.WithCredentialsProvider(credentials))

We ran both styles against a local test server with service/s3 v1.88.0, and they sent identical requests.

Two settings matter most here. SigningRegion: "us-east-1" is the value the docs use, since the SDK needs a region to sign each request. UsePathStyle = true is required, because without it the SDK puts the bucket in the hostname, and on Oct 19, 2026 my-bucket.s3.aiozstorage.network didn't resolve at all. Every request failed with "no such host" before it left your machine.

Uploading an Object

The docs' upload example opens a local file and passes it as the body:

filePath := "YOUR_FILE_PATH"
path := strings.Split(filePath, "/")
fileName := path[len(path)-1]

file, err := os.Open(filePath)
if err != nil {
	log.Fatal(err)
}
defer file.Close()

_, err = s3Client.PutObject(context.TODO(), &s3.PutObjectInput{
	Bucket: aws.String("your-bucket"),
	Key:    aws.String(fileName),
	Body:   file,
})
if err != nil {
	log.Fatal(err)
}

Every call takes a context.Context first, Go's standard way to cancel work or set deadlines. context.TODO() is fine in a script, but in a service you'll want a request-scoped context so a slow upload can be cancelled.

How Does PutObjectInput.Body Work with io.Reader?

In aws-sdk-go-v2, PutObjectInput.Body is declared as io.Reader, documented simply as "Object data." So you can pass a file, a byte slice, a buffer or a stream. The catch is size, because the SDK has to send a Content-Length, and the ContentLength field exists for when "the size of the body cannot be determined automatically."

We tested this on Oct 19, 2026 with Go 1.22.2 and service/s3 v1.88.0. First we pointed the client at a local HTTPS test server and logged what arrived. Then we sent the same uploads to s3.aiozstorage.network with a fake key, which shows whether a request passes the endpoint's header checks.

Body typeWhat the SDK sentAIOZ Storage answer (fake key)
*os.File, *bytes.Reader, *bytes.Buffer, *strings.ReaderContent-Length set, CRC32 checksum trailerInvalidAccessKeyId: passed the header checks
io.Pipe reader, no ContentLengthTransfer-Encoding: chunked, no lengthMissingContentLength
io.Pipe reader with ContentLengthContent-Length setInvalidAccessKeyId: passed the header checks

Here is the raw output of the run against AIOZ Storage:

UsePathStyle=true  Body=strings.Reader -> InvalidAccessKeyId: "The Access Key Id you provided does not exist in our records."
UsePathStyle=true  Body=io.Pipe        -> MissingContentLength: "You must provide the Content-Length HTTP header."
UsePathStyle=true  Body=io.Pipe + ContentLength(13) -> InvalidAccessKeyId
UsePathStyle=true  Body=io.Pipe + ContentLength(13), RequestChecksumCalculation=WhenRequired -> MissingContentLength
UsePathStyle=false Body=strings.Reader -> dial tcp: lookup my-bucket.s3.aiozstorage.network: no such host

So if your body is a stream, set ContentLength yourself:

pr, pw := io.Pipe()
go func() {
	// write your stream here, then close
	pw.Write(payload)
	pw.Close()
}()

_, err := s3Client.PutObject(ctx, &s3.PutObjectInput{
	Bucket:        aws.String("your-bucket"),
	Key:           aws.String("stream.bin"),
	Body:          pr,
	ContentLength: aws.Int64(int64(len(payload))), // required for non-seekable bodies
})

If you don't know the size up front, write the stream to a temporary file first and upload that. One quirk from our run: with RequestChecksumCalculation set to WhenRequired, a pipe with ContentLength still went out chunked and was rejected, so keep the default checksum setting when you stream.

About that default: from service/s3 v1.74.1, the SDK adds a CRC32 checksum to every upload, per AWS's Go developer guide. With seekable bodies and pipes that carry a length, AIOZ Storage accepted the request headers. We had no real key, so we couldn't confirm a full upload end to end.

Close-up of code displayed on a computer screen

Listing and Downloading Objects

Listing and downloading use the standard SDK calls, with nothing AIOZ-specific beyond the client:

func listFiles(ctx context.Context, client *s3.Client, bucket string) ([]string, error) {
	var keys []string
	paginator := s3.NewListObjectsV2Paginator(client, &s3.ListObjectsV2Input{
		Bucket: aws.String(bucket),
	})
	for paginator.HasMorePages() {
		page, err := paginator.NextPage(ctx)
		if err != nil {
			return nil, err
		}
		for _, obj := range page.Contents {
			keys = append(keys, *obj.Key)
		}
	}
	return keys, nil
}

func downloadFile(ctx context.Context, client *s3.Client, bucket, key, destPath string) error {
	result, err := client.GetObject(ctx, &s3.GetObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
	})
	if err != nil {
		return err
	}
	defer result.Body.Close()

	out, err := os.Create(destPath)
	if err != nil {
		return err
	}
	defer out.Close()

	_, err = out.ReadFrom(result.Body)
	return err
}

ListObjectsV2 returns results in pages, so s3.NewListObjectsV2Paginator fetches the next page each time you call NextPage. GetObject returns the body as an io.ReadCloser, so you copy it into a file yourself and close it when you're done.

Deleting an Object

func deleteFile(ctx context.Context, client *s3.Client, bucket, key string) error {
	_, err := client.DeleteObject(ctx, &s3.DeleteObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
	})
	return err
}

On Amazon S3, deleting a key that doesn't exist still succeeds, so a successful call doesn't prove the object was there, so call HeadObject first if you need to know.

Handling Errors

aws-sdk-go-v2 returns typed errors you can match with errors.As, so you never parse error strings. For codes without a typed error, such as MissingContentLength or InvalidAccessKeyId from the run above, use smithy.APIError:

import (
	"errors"

	"github.com/aws/aws-sdk-go-v2/service/s3/types"
	"github.com/aws/smithy-go"
)

_, err := s3Client.GetObject(ctx, &s3.GetObjectInput{
	Bucket: aws.String(bucket),
	Key:    aws.String(key),
})
if err != nil {
	var noKey *types.NoSuchKey
	if errors.As(err, &noKey) {
		log.Printf("object %s does not exist in %s", key, bucket)
	} else {
		log.Fatal(err)
	}
}

// Any S3 error code, including ones without a typed error:
var apiErr smithy.APIError
if errors.As(err, &apiErr) {
	log.Printf("code=%s message=%s", apiErr.ErrorCode(), apiErr.ErrorMessage())
}

How AIOZ Storage Handles Go Clients

We expose one S3 endpoint, https://s3.aiozstorage.network, and your access key and secret come from an access grant in the dashboard. Your Go client is the AWS SDK with three changes: the endpoint, the us-east-1 signing region and path-style addressing. The usage limits page sets 100 requests per second, 100 buckets and 100 sub-users per account, so throttle bulk uploads; our rate limit guide shows how. For vectors, the docs have a separate Go page for the Vector Database API.

What the docs don't cover yet: checksum headers, multipart upload limits and a full error-code list. The test above shows what the endpoint does with the SDK's defaults, as far as we could check without a real key.

Next step: the AIOZ Storage Golang SDK docs have the full example project.

Frequently Asked Questions

What Go module do I need for the AIOZ Storage Go SDK?
github.com/aws/aws-sdk-go-v2, specifically its service/s3 package, and there's no AIOZ-specific module.

Why does the S3 client need UsePathStyle = true?
Without it the SDK puts the bucket in the hostname, and bucket subdomains of s3.aiozstorage.network don't resolve, while path style sends endpoint/bucket/key instead.

What can I pass as PutObjectInput.Body?
Any io.Reader. Files and byte readers work as they are, and for a pipe or network stream, also set ContentLength.

Why does PutObject fail with MissingContentLength?
Your body has no known size, so the SDK sent a chunked request without a Content-Length header. Set ContentLength, or buffer the stream to a file first.

Is EndpointResolverWithOptions deprecated?
Yes, in current SDK versions, though it still works and the AIOZ docs use it. The replacement is o.BaseEndpoint on s3.Options.

Does ListObjectsV2 return everything in one call?
No, it paginates, so use s3.NewListObjectsV2Paginator and call NextPage until HasMorePages is false.

How do I read S3 error codes in Go?
Use errors.As with typed errors such as types.NoSuchKey, or with smithy.APIError to read any error code and message.

References

We only send updates when meaningful changes ship, and you can unsubscribe anytime

Related Content

blog thumbnail

How to Create a Bucket and Upload a File on AIOZ Storage

Create a bucket and upload a file on AIOZ Storage in five steps: sign up, set a passphrase, add credits, and upload. Plus what the passphrase cannot recover.

aioz-storage
5 min readOctober 20, 2026
blog thumbnail

AIOZ Storage Rate Limit: Working Within 100 Requests/s

The AIOZ Storage rate limit is 100 requests per second. See what the docs do and don't say, and how to stay under it with boto3 retries and a limiter.

aioz-storage
7 min readOctober 16, 2026
blog thumbnail

What AIOZ Storage Erasure Coding Covers and What It Doesn't

AIOZ Storage erasure coding splits each object into pieces on DePIN nodes, so it survives node loss. See what it covers, what it doesn't, and what to back up.

aioz-storage
6 min readOctober 14, 2026
blog thumbnail

How to Rename a File in an S3 Bucket on AIOZ Storage

Rename a file in an S3 bucket on AIOZ Storage from the dashboard, then move, delete and download files, and see which actions the docs say are not supported.

aioz-storage
5 min readOctober 13, 2026
blog thumbnail

AIOZ Storage llms.txt: Docs Built for Super Intelligence

AIOZ Storage publishes llms.txt, a 46-link docs index, and llms-full.txt, all docs in one 164 KB file. See what each file contains and when to use which.

aioz-storage
6 min readOctober 09, 2026
blog thumbnail

Vector Database vs Agent Memory on AIOZ Storage: A Guide

Vector database vs agent memory on AIOZ Storage: use the vector database for control over embeddings and metadata, Memory for simple Record and Recall calls.

aioz-storage
6 min readOctober 06, 2026