Go SDK for image and video uploads

dreep-go is the official Go SDK for Dreep. Install it with go get github.com/IndigoSoftwares21/dreep-go on Go 1.22 or later. It uses only the standard library, streams multipart uploads instead of buffering them, presigns direct uploads for browsers, builds transform and signed URLs locally, and retries safe requests on 429 and 5xx responses. Every request method takes a context.Context first.

Install and run your first upload

Create an API key on the API Keys page of the dashboard and keep it in an environment variable on your server. dreep.New returns an error if the key is empty. The cURL tab shows the request Upload sends.

client, err := dreep.New(os.Getenv("DREEP_API_KEY"))
if err != nil {
    log.Fatal(err)
}

f, err := os.Open("hero.jpg")
if err != nil {
    log.Fatal(err)
}
defer f.Close()
fi, _ := f.Stat()

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()

asset, err := client.Upload(ctx, dreep.UploadOptions{
    File:        f,
    Filename:    fi.Name(),
    KnownSize:   fi.Size(), // exact Content-Length, no chunked encoding
    Destination: dreep.Destination{Folder: "marketing/2026"},
    Transform:   &dreep.Transform{Width: 1200, Format: dreep.FormatWebP, Quality: 80},
})
if err != nil {
    log.Fatal(err)
}

fmt.Println(asset.URL)
fmt.Println(client.URL(asset.ID, &dreep.Transform{Width: 400})) // no network call

What the package gives you

A small client that behaves like the rest of your Go code: contexts for deadlines, plain structs for options, one error type you can inspect with errors.As.

Standard library only

net/http, mime/multipart and crypto/hmac. Nothing is added to go.sum beyond the module itself, which keeps audits and upgrades simple.

Streaming uploads

Upload writes the multipart body through an io.Pipe, so a 400 MB video is never held in memory. Set KnownSize for an exact Content-Length.

Context first, safe to share

Every request method takes a context.Context, so deadlines and cancellation work as usual. One *dreep.Client is safe for concurrent use across goroutines.

Retries that can't duplicate

Requests with replayable bodies are retried on 429 and 5xx. Streaming uploads are never retried, so a failed upload can't store the same file twice.

Two ways to upload

Upload sends the file through the Dreep API in one request. UploadPresigned presigns, PUTs the bytes straight to Dreep storage and confirms, in one call. Both accept files up to 500 MB.

BehaviourUploadUploadPresigned
RequestsOne multipart POSTPresign, PUT, confirm
Where the bytes goThrough the Dreep APIStraight to Dreep storage
TransformBefore storageAt confirm
Duplicate detectionNoContentHash skips the PUT when Dreep already has the bytes
Automatic retriesNeverConfirm retried on 409 while bytes settle

Video uploads and trimmed clips

Video inputs are MP4, MOV and WebM. Upload the original once, then request trimmed, resized or re-encoded variants by URL. Light jobs return straight away; heavy ones such as a re-encode return 202 while processing, so request the same URL again until it returns 200 from cache.

fi, err := f.Stat()
if err != nil {
    return err
}

asset, err := client.UploadPresigned(ctx, dreep.DirectUploadOptions{
    PresignOptions: dreep.PresignOptions{
        ContentType: "video/mp4",
        SizeBytes:   fi.Size(),
        Filename:    "launch.mp4",
        Destination: dreep.Destination{Folder: "videos/2026"},
        ContentHash: sha256Hex, // optional: skip the PUT for known bytes
    },
    File: f,
})
if err != nil {
    return err
}

clip := client.URL(asset.ID, &dreep.Transform{
    Width:     720,
    Format:    dreep.FormatMP4,
    TrimStart: dreep.Ptr(2.0),
    TrimEnd:   dreep.Ptr(12.0),
})

Video transformations have their own monthly allowance: 20 on Free, 1,000 on Starter and 4,000 on Pro. There are no webhooks; polling the URL is the completion signal.

Browser uploads from an http.Handler

Browsers never hold your API key. Your handler checks who is uploading and returns a single-use upload URL, valid for 10 minutes. The browser PUTs the file there with a matching Content-Type and Content-Length, then calls your confirm route.

  1. Browser asks

    name, type and size

  2. Handler presigns

    PresignUpload

  3. Browser PUTs

    bytes to uploadUrl

  4. Handler confirms

    ConfirmUpload

  5. Asset ready

    CDN URL returned

The asset stays pending, and out of listings, until ConfirmUpload runs.

The handler and the browser side

Two routes on a Go 1.22 ServeMux. Put your own authentication middleware in front of both, and check the confirm id belongs to an upload that user started.

type presignRequest struct {
    Filename    string `json:"filename"`
    ContentType string `json:"contentType"`
    SizeBytes   int64  `json:"sizeBytes"`
}

func presignHandler(client *dreep.Client) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Authorise the user and enforce your own quotas first.
        var in presignRequest
        if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
            http.Error(w, "bad request", http.StatusBadRequest)
            return
        }
        upload, err := client.PresignUpload(r.Context(), dreep.PresignOptions{
            Filename:    in.Filename,
            ContentType: in.ContentType,
            SizeBytes:   in.SizeBytes,
            Destination: dreep.Destination{Folder: "user-uploads"},
        })
        if err != nil {
            http.Error(w, "could not start upload", http.StatusBadGateway)
            return
        }
        json.NewEncoder(w).Encode(map[string]string{
            "id":        upload.ID,
            "uploadUrl": upload.UploadURL,
        })
    })
}

func confirmHandler(client *dreep.Client) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        asset, err := client.ConfirmUpload(r.Context(), r.PathValue("id"), nil)
        if err != nil {
            http.Error(w, "could not confirm upload", http.StatusBadGateway)
            return
        }
        json.NewEncoder(w).Encode(asset)
    })
}

mux := http.NewServeMux()
mux.Handle("POST /uploads", presignHandler(client))
mux.Handle("POST /uploads/{id}/confirm", confirmHandler(client))

Transform URLs and signed links

URL builds a delivery URL from an asset ID and a *dreep.Transform. It returns a string with no error and makes no request. When Format is set, the extension is added to the path as well as the query. SignedURL adds exp and sig for assets in signed folders, computed locally with your signing secret.

client, err := dreep.New(
    os.Getenv("DREEP_API_KEY"),
    dreep.WithSigningSecret(os.Getenv("DREEP_SIGNING_SECRET")),
)

card := client.URL(asset.ID, &dreep.Transform{
    Width: 1200, Height: 630, Fit: dreep.FitCover,
    Gravity: dreep.GravityAttention, Format: dreep.FormatAVIF, Quality: 70,
})
// .../fetch/<id>.avif?fit=cover&format=avif&gravity=attention&height=630&quality=70&width=1200

thumb := client.URL(asset.ID, &dreep.Transform{Preset: "thumbnail"}) // ...?p=thumbnail

signed, err := client.SignedURL(asset.ID, 15*time.Minute, nil)
// ...?exp=1774118400&sig=a3f9e2b1...

SignedURL returns dreep.ErrNoSigningSecret if the client was built without WithSigningSecret. Pass nil as the transform: the Go SDK signs only the asset ID and expiry, and Dreep rejects signed links that carry transform parameters signed that way. Use the JavaScript SDK if you need signed, transformed links. The signature is an HMAC-SHA256, so anyone holding the link can fetch it until then: keep TTLs short and issue links from a handler that checks your own permissions.

Retries and errors

Every non-2xx response is a *dreep.Error with StatusCode, Code and Message. Helpers cover the cases worth branching on. Network failures and context cancellation wrap the underlying error, so errors.Is(err, context.DeadlineExceeded) works.

HelperStatusTrue when
IsUnauthorized401Bad API key, or a signed asset without a valid exp and sig
IsPaymentRequired402A plan limit was hit: storage, transformations or credits
IsNotFound404Unknown asset, folder, preset or presigned upload
IsConflict409ConfirmUpload ran before the bytes landed
IsInvalidRequest400The API returned the invalid_request code
FeatureLimit402Returns the feature key when the limit error carries detail

Tuning retries and handling limits

By default, replayable requests get two retries on 429 and 5xx, waiting 500ms and then doubling. A Retry-After header always wins. The default http.Client has no global timeout, so bound calls with a context.

client, err := dreep.New(os.Getenv("DREEP_API_KEY"),
    dreep.WithMaxRetries(4), // 0 disables retries
    dreep.WithRetryBaseDelay(200*time.Millisecond),
)

asset, err := client.Upload(ctx, opts)
switch {
case err == nil:
    return asset.URL, nil
case dreep.IsPaymentRequired(err):
    if feature, ok := dreep.FeatureLimit(err); ok {
        var e *dreep.Error
        errors.As(err, &e)
        log.Printf("limit hit on %s: used %d of %d", feature, e.Used.Int64(), e.Limit.Int64())
    }
    return "", err // upgrade or buy an add-on; there is no automatic overage
case errors.Is(err, context.DeadlineExceeded):
    return "", fmt.Errorf("upload took too long: %w", err)
default:
    return "", err
}

Who this is for

  • Go APIs and workers that accept user uploads or generate media, such as thumbnails or exports.
  • Services that want no third-party modules in go.sum for their media client.
  • Large video uploads from a server, where streaming or presigned PUTs keep memory flat.
  • Backends issuing presigned URLs to web or mobile clients from an http.Handler.

When Dreep is not the right fit

  • You are on a Go version older than 1.22.
  • You want to embed the API key in a CLI or desktop app you ship to users. Keep it on a server.
  • You need a video player, live streaming or DRM. Dreep delivers files and HLS manifests; the player is yours.

Frequently asked questions

How do I install the Dreep Go SDK?

Run go get github.com/IndigoSoftwares21/dreep-go and import github.com/IndigoSoftwares21/dreep-go. The package name is dreep. It needs Go 1.22 or later and has no dependencies outside the standard library.

Does Upload load the whole file into memory?

No. Upload streams the multipart body through an io.Pipe, so memory stays flat whatever the file size. Pass KnownSize to send an exact Content-Length instead of chunked transfer encoding.

When should I use UploadPresigned instead of Upload?

Use UploadPresigned for large files or when you want duplicate detection with ContentHash: the bytes go straight to Dreep storage and the confirm step is retried on 409. Use Upload for a simple one-request upload with a transform applied before storage.

Which requests are retried?

Requests with replayable bodies, such as PresignUpload, ListMedia, DeleteMedia and the folder, preset and usage calls, are retried on 429 and 5xx with exponential backoff. Streaming uploads, ConfirmUpload and ExtractText are sent once. Connection failures are returned immediately.

Can I sign URLs without a network call?

Yes. Build the client with dreep.WithSigningSecret, then call client.SignedURL(assetID, ttl, nil). It computes an HMAC-SHA256 signature locally and appends exp and sig to the delivery URL.

Is dreep-go open source?

Yes. It is MIT licensed on GitHub at IndigoSoftwares21/dreep-go, with the API reference on pkg.go.dev and a runnable upload example in the examples folder.

Related

go get dreep-go and upload your first file

Create a free project for 1 GB of storage, 100 image and 20 video transformations a month, copy an API key, and run the upload example.