Ruby SDK

Use the Ruby client for moderation, events and content delivery. Ruby 3.1 or later is required. Options use snake_case; responses retain API field names.

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 Ruby SDK is not yet available from RubyGems.

Moderate content

Keep credentials on your server. The create method queues processing; moderate waits for supported synchronous processing. Audio and video remain asynchronous.

require 'calmlens'
client = CalmLens::Client.new(api_key: ENV.fetch('CALMLENS_API_KEY'), project_id: ENV.fetch('CALMLENS_PROJECT_ID'))
result = client.assets.moderate(name: 'text', content: {plain_text: 'Hello world', type: 'text/plain'})
asset = client.assets.create(name: 'note.txt', content: {data: 'Hello', type: 'text/plain'})
client.close

Publish only an explicit approved result. An approved, flagged asset may still need review. See Reading reports. Statuses and fields added to the API later are returned unchanged, so treat any status you don't recognise as not ready.

Option names may be snake_case or camelCase at any depth, such as processing_options: {image: {ocr_enabled: true}}. Keys inside metadata are sent as you wrote them.

Uploads and delivery

Uploads accept plain text, base64, a binary String or an IO. An IO is streamed without being read into memory. Files, Tempfiles and StringIOs know their size; for a pipe or socket, pass size_in_bytes:.

File.open('clip.mp4', 'rb') do |file|
  client.assets.create(name: 'clip.mp4', content: {data: file, type: 'video/mp4'})
end

Multipart uploads save their queue mode when they start, so later parts can come from another process; pass total_size_in_bytes: at the start if you know the file size. See Multipart uploads.

Use create_signed_url to deliver private content to browsers. cdn.serve fetches content with your API key; give it a block to stream the body instead of holding it in memory:

File.open('photo.jpg', 'wb') do |file|
  client.cdn.serve(asset_identifier: asset_id) do |response|
    raise "CDN returned #{response.status}" unless response.success?
    response.each_chunk { |chunk| file.write(chunk) }
  end
end

See Delivering assets.

Pagination

assets.each, assets.each_child, events.each and events.each_page fetch pages as you iterate. Without a block they return an Enumerator, so client.assets.each(filter: 'status==approved').first(5) requests only the pages it needs.

Errors, retries and timeouts

Every error is a CalmLens::Error with code, status, request_id (from X-Request-Id), headers, retry_after, details and the original exception in cause. Subclasses let you rescue by kind: CalmLens::NotFoundError, RateLimitError, ConflictError and the other APIError classes for API responses, ConnectionError and TimeoutError when no response arrived, InvalidArgumentError (sdk/invalid-argument) and ConfigurationError (sdk/invalid-config) before anything is sent. CalmLens::ErrorCodes lists the API's error codes. See Error handling.

Uploads send an Idempotency-Key, generated unless you pass request_options: {idempotency_key: '...'}, and reuse it on retries, so a retried upload never creates a second asset. Reads and uploads are retried up to max_retries: times (default 2) after connection failures, timeouts, 429, 502, 503, 504 and 409 idempotency/request-in-progress, with jittered backoff that honours Retry-After. Other writes are not retried.

timeout: (default 120 seconds) limits each socket read or write rather than the whole transfer, so large uploads and downloads are not cut off. open_timeout: (default 10 seconds) limits connecting. Override either per call with request_options:.

Reuse one client: it pools keep-alive connections, is safe across threads and reconnects in forked worker processes. Call close when finished.

Webhooks

Verify the exact raw body, then parse it, with CalmLens.parse_webhook_event. It raises CalmLens::WebhookVerificationError for an invalid signature or stale timestamp, and rejects an already parsed Hash.

In Rails, read request.raw_post:

event = CalmLens.parse_webhook_event(
  payload: request.raw_post,
  signature: request.headers[CalmLens::WEBHOOK_SIGNATURE_HEADER],
  timestamp: request.headers[CalmLens::WEBHOOK_TIMESTAMP_HEADER],
  secret: ENV.fetch('CALMLENS_WEBHOOK_SECRET')
)

In Sinatra or another Rack app, read request.body.read and the HTTP_X_CALMLENS_SIGNATURE and HTTP_X_CALMLENS_TIMESTAMP entries of request.env.

Respond quickly and process the event afterwards, for example in a background job. Skip deliveries whose x-calmlens-delivery ID you have already handled. CalmLens.verify_webhook_signature returns true or false instead of raising. See Webhooks.

Test without API calls

See Ruby testing for the offline mock client.

Local development

Use the standalone CalmLens CLI to forward events locally, regardless of your application language.

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