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
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
| Method | Options 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 cleanSee 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.