Go SDK

The Go SDK supports Go 1.22+ and uses only the standard library. It includes typed responses, context cancellation, streaming uploads and downloads, safe automatic retries, pagination iterators, webhook verification and an in-memory testing package.

Choose a moderation flow

Use the SDK's moderate method to wait for a decision in the request, or its create method to queue moderation and let your app continue. Text and images support both flows. Audio and video always run asynchronously, even when using moderate.

For queued content, save the returned asset ID and set up a webhook for asset.classified (decision ready) and asset.failed (processing failed). Keep content unpublished until approved. You can retrieve the asset periodically instead of receiving events. The quick start demonstrates both flows.

Install

The Go SDK is not yet available from the Go module proxy.

Moderate text

Keep API credentials on your server. Each network method accepts a context.Context and returns an error.

package main

import (
    "context"
    "fmt"
    "log"
    "os"

    calmlens "github.com/Censkh/CalmLens/packages/go-sdk"
)

func main() {
    client, err := calmlens.NewClient(calmlens.Options{
        APIKey: os.Getenv("CALMLENS_API_KEY"),
        ProjectID: os.Getenv("CALMLENS_PROJECT_ID"),
    })
    if err != nil {
    log.Fatal(err)
}
    asset, err := client.Assets.Moderate(context.Background(), calmlens.SubmitAssetOptions{
        Name: "text",
        Content: &calmlens.Content{
            Type: "text/plain",
            PlainText: calmlens.Ptr("Great photo!"),
        },
        ExternalID: calmlens.Ptr("comment-42"),
    })
    if err != nil {
    log.Fatal(err)
}
    if asset.Status == "approved" {
        fmt.Println("Approved", asset.Report)
    }
}

Only publish after an explicit approval. An approved, flagged result can still need review. A category with no score is unknown, rather than safely zero. See Report format.

Upload and deliver images

Inside your application, using the client above:

ctx := context.Background()
file, err := os.Open("photo.jpg")
if err != nil {
    log.Fatal(err)
}
defer file.Close()
queued, err := client.Assets.Create(ctx, calmlens.SubmitAssetOptions{
    Name: "photo.jpg",
    Content: &calmlens.Content{Type: "image/jpeg", Data: file},
    Visibility: "private",
    ProcessingOptions: calmlens.JSON{"keepContentAfterProcessing": true},
})
if err != nil {
    log.Fatal(err)
}

// Retrieve later, or respond to an asset.classified webhook.
current, err := client.Assets.Retrieve(ctx, calmlens.AssetIDOptions{AssetID: queued.ID})
if err != nil {
    log.Fatal(err)
}
if current.Status == "approved" {
    signed, err := client.Assets.CreateSignedURL(ctx, calmlens.SignedURLOptions{
        AssetID: current.ID, Resource: "content", ExpiresInSeconds: 60,
    })
    if err != nil {
    log.Fatal(err)
}
    fmt.Println(signed.URL)
}

Assets.Create queues processing. Assets.Moderate waits for classification for supported content; audio and video remain asynchronous. Data readers are streamed without buffering, and timeouts never cut off an upload or a CDN.Serve download: bound those with a context deadline. Multipart readers need an exact SizeInBytes; send parts sequentially. CalmLens saves the queue mode chosen when a multipart upload starts, so any process can finish it with the asset ID; UploadAssetPartOptions.Queue overrides it, and SubmitAssetOptions.TotalSizeInBytes declares the whole file size up front. Remote downloads are limited to 10 MB for images and 20 MB for other files. See Assets API for upload limits.

Operations

MethodOptions type
Assets.Create/Moderate(ctx, options)SubmitAssetOptions
Assets.Retrieve/Retry/DeleteContent/Delete(ctx, options)AssetIDOptions
Assets.Update(ctx, options)UpdateAssetOptions
Assets.List(ctx, options)ListAssetsOptions
Assets.ListChildren(ctx, options)ListAssetChildrenOptions
Assets.UploadPart(ctx, options)UploadAssetPartOptions
Assets.Move(ctx, options)MoveAssetOptions
Assets.CreateSignedURL(ctx, options)SignedURLOptions
Folders.List(ctx)No options.
Folders.Create/Retrieve/Update/Move/Delete(ctx, options)Corresponding folder options.
Events.List(ctx, options)ListEventsOptions
Assets.ListAll / Assets.ListChildrenAll / Events.ListAll(options)Iterators over every page; see below.
EnsureLens/EnsureLenses(ctx, options)EnsureLensOptions / EnsureLensesOptions
CDN.GetAssetURL(options) / CDN.Serve(ctx, options)CDNAssetURLOptions / CDNServeOptions

Use calmlens.Ptr(value) for optional scalars. FolderID: nil moves an asset to the root. Updates use UpdateAssetOptions.Fields, a JSON patch map; omitted keys stay unchanged and nil clears a nullable field.

Pagination

Assets.ListAll, Assets.ListChildrenAll and Events.ListAll return iterators that fetch pages as you go:

it := client.Assets.ListAll(calmlens.ListAssetsOptions{PageSize: 100})
for it.Next(ctx) {
    fmt.Println(it.Asset().ID)
}
if err := it.Err(); err != nil {
    log.Fatal(err)
}

On Go 1.23 or later, for asset, err := range it.All(ctx) works too. For events, start from a saved cursor and store it.Cursor() afterwards.

Retries

Reads (GET) and uploads (Create, Moderate, Text.Moderate, UploadPart) are retried up to Options.MaxRetries times (default 2) with jittered backoff after a connection failure or timeout, 429, 502, 503, 504 or idempotency/request-in-progress, honouring Retry-After. Uploads always send an Idempotency-Key, generated per call and reused for the SDK's retries, so a retry never creates a second asset. Set IdempotencyKey yourself to retry safely across restarts. Other writes are never retried automatically. Set MaxRetries: calmlens.Ptr(0) to turn retries off. See Retrying requests safely.

Errors

Every failure is a *calmlens.Error, including invalid arguments and encoding failures. It carries Code, Status, RequestID (the X-Request-Id header), Header, RetryAfter, Details and Cause, and Error() includes the code, status and request ID. Compare codes with the generated constants, such as calmlens.CodeAssetNotFound, or use calmlens.IsNotFound(err), calmlens.IsRetryable(err) and calmlens.ErrorCode(err). Connection, TLS, protocol, decompression and truncated-body failures have code sdk/transport-error, timeouts sdk/timeout and cancellation sdk/cancelled; the original error stays in Cause, so errors.Is and errors.As still work. Arguments rejected before a request use sdk/invalid-argument, and invalid client options sdk/invalid-config.

Options.Timeout (default 120 seconds) bounds each attempt of a JSON API call. The default transport also limits connecting (ConnectTimeout, 10 seconds) and waiting for response headers (ResponseHeaderTimeout, 120 seconds). Requests send User-Agent: calmlens-go/<version>.

Webhooks

Verify the exact raw webhook bytes and the signature and timestamp headers before parsing JSON. Timestamps more than 5 minutes from now are rejected. With net/http, chi or any router that passes the *http.Request, VerifyWebhookRequest reads the body (up to 1 MiB), verifies it and returns the event:

func handleWebhook(w http.ResponseWriter, r *http.Request) {
	event, err := calmlens.VerifyWebhookRequest(r, os.Getenv("CALMLENS_WEBHOOK_SECRET"))
	if err != nil {
		http.Error(w, "Invalid webhook", http.StatusUnauthorized)
		return
	}
	// Deduplicate by event.ID, queue the work, then respond quickly.
	w.WriteHeader(http.StatusNoContent)
}

Call it before any middleware reads or decodes the body. With gin, pass c.Request before binding. If you already hold the raw bytes, calmlens.ParseWebhookEvent(rawBody, signature, timestamp, secret) verifies and decodes them, and calmlens.VerifyWebhookSignature returns a boolean. calmlens.WithTolerance, calmlens.WithNow and calmlens.WithMaxBodyBytes change the defaults.

calmlens.WebhookDeliveryHeader (x-calmlens-delivery) identifies each delivery for deduplication. calmlens.SignWebhookPayload(rawBody, timestamp, secret) takes the same argument order and signs test deliveries.

Install the standalone CalmLens CLI to forward events locally:

calmlens listen --forward-to http://localhost:3000/webhook --env-file .env.local
calmlens trigger clean

See Webhooks & Connectors for event handling and signatures. When using CDN.Serve, check the response status and close response.Body; the body is streamed. Use short-lived signed URLs for private content.

Test without API calls

See Go testing for offline mock setup, request inspection and simulated failures.

Signing in

Signing out

Cookies on CalmLens

We use essential cookies to keep you signed in and the site secure. With your permission, we'll also use functional cookies to remember preferences such as your layout and documentation language, and to offer Google One Tap sign-in. We don't use analytics or advertising cookies. Manage cookies