# Get started with the CalmLens API

CalmLens tells your app whether user content is safe to show. In this guide you moderate a comment, then an image, and learn how to read the decision. New to the concepts? Read [How CalmLens works](/docs/how-it-works) first.

## Choose when to get the decision

You can moderate in place as part of your request, or queue moderation and receive an event when it finishes. This choice is separate from whether CalmLens stores and serves your content.

| Flow | Use | Get the result |
| --- | --- | --- |
| **Wait in the request** | `client.assets.moderate()` (`queue=false` over HTTP) | The response contains the moderation decision. Useful when your app needs a decision before accepting a submission. |
| **Queue and receive an event** | `client.assets.create()` (`queue=true` over HTTP) | The response gives you an asset ID while moderation runs in the background. A webhook sends [`asset.classified`](/docs/api/events#event-asset-classified) when the decision is ready. |

Text and images support either flow. Audio and video always run asynchronously, even with `queue=false`. For queued content, keep it unpublished until approved, and handle [`asset.failed`](/docs/api/events#event-asset-failed) if processing fails. See [Webhooks](/docs/guides/webhooks) for setup and payloads.

## 1. Get an API key and your project ID

1. In the portal, go to [API keys](/portal/api-keys) and create a key. Copy it now: it's only shown once.
2. Copy the **Project ID** shown next to your new key and at the top of the API Keys page.

Store both on your server, for example as `CALMLENS_API_KEY` and `CALMLENS_PROJECT_ID`. Never put the key in browser or mobile app code.

Choose your language in the examples below. SDK guides cover [JavaScript / TypeScript](/docs/sdk/javascript), [Python](/docs/sdk/python), [Go](/docs/sdk/go), [Rust](/docs/sdk/rust), [Ruby](/docs/sdk/ruby) and [Java](/docs/sdk/java). Language selection stays in sync across examples.

**JavaScript**

```sh
npm install @calmlens/sdk
```

**python**

> The Python SDK is not yet available from PyPI.

**go**

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

**ruby**

> The Ruby SDK is not yet available from RubyGems.

**rust**

> The Rust SDK is not yet available from crates.io.

**java**

> The Java SDK is not yet available from Maven Central.

**python**

```python
import os
from calmlens import CalmLensClient

client = CalmLensClient(
    api_key=os.environ["CALMLENS_API_KEY"],
    project_id=os.environ["CALMLENS_PROJECT_ID"],
)

print("SDK client ready")
```

**go**

```go
package main

import (
    "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) }
    fmt.Println("SDK client ready", client != nil)
}

```

**JavaScript**

```javascript
import { CalmLensClient } from "@calmlens/sdk";

const client = new CalmLensClient({
  apiKey: process.env.CALMLENS_API_KEY!,
  projectId: process.env.CALMLENS_PROJECT_ID!,
});
```

**ruby**

```ruby
require 'calmlens'
require 'json'

client = CalmLens::Client.new(
  api_key: ENV.fetch('CALMLENS_API_KEY'),
  project_id: ENV.fetch('CALMLENS_PROJECT_ID')
)

puts "SDK client ready"

```

**rust**

```rust
use calmlens::Client;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new(std::env::var("CALMLENS_API_KEY")?, std::env::var("CALMLENS_PROJECT_ID")?)?;
    println!("SDK client ready");

    Ok(())
}

```

**java**

```java
import com.calmlens.CalmLensClient;

public class Example {
  public static void main(String[] args) throws Exception {
    var client = new CalmLensClient(System.getenv("CALMLENS_API_KEY"), System.getenv("CALMLENS_PROJECT_ID"));
    System.out.println("SDK client ready");

  }
}

```

## 2. Moderate a comment

This example moderates in place: `client.text.moderate()` waits for the decision before your app continues. You can also queue text with `client.assets.create()` and receive the result by webhook.

**python**

```python
import os
from calmlens import CalmLensClient

client = CalmLensClient(
    api_key=os.environ["CALMLENS_API_KEY"],
    project_id=os.environ["CALMLENS_PROJECT_ID"],
)

comment = {"id": "42", "body": "Great photo!"}
result = client.text.moderate(text=comment["body"], external_id=f"comment-{comment['id']}")

if result["status"] == "approved":
    # Publish the comment in your application.
    print("Publish", comment["id"])
else:
    print("Your comment was not posted because it may break our community guidelines.")
```

**go**

```go
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) }
    ctx := context.Background()
    commentID, text := "42", "Great photo!"
    externalID := "comment-" + commentID
    result, err := client.Text.Moderate(ctx, calmlens.ModerateTextOptions{Text: text, ExternalID: &externalID})
    if err != nil { log.Fatal(err) }
    if result.Status == "approved" {
        // Publish the comment in your application.
        fmt.Println("Publish", commentID)
    } else {
        fmt.Println("Your comment was not posted because it may break our community guidelines.")
    }
}

```

**JavaScript**

```javascript
const result = await client.text.moderate({ text: comment.body, externalId: `comment-${comment.id}` });

if (result.status === "approved") {
  await publishComment(comment);
} else {
  showError("Your comment wasn't posted because it may break our community guidelines.");
}
```

**ruby**

```ruby
require 'calmlens'

client = CalmLens::Client.new(api_key: ENV.fetch('CALMLENS_API_KEY'), project_id: ENV.fetch('CALMLENS_PROJECT_ID'))
result = client.text.moderate(text: "Great photo!", external_id: "comment-42")
puts result
client.close
```

**rust**

```rust
use calmlens::{models::*, Client};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new(std::env::var("CALMLENS_API_KEY")?, std::env::var("CALMLENS_PROJECT_ID")?)?;
    let result = client.text().moderate(ModerateText { text: "Great photo!".into(), external_id: Some("comment-42".into()), ..Default::default() }).await?;
    println!("{result:?}");
    Ok(())
}
```

**java**

```java
import com.calmlens.CalmLensClient;
import com.calmlens.Models.*;

public class Example {
  public static void main(String[] args) {
    var client = new CalmLensClient(System.getenv("CALMLENS_API_KEY"), System.getenv("CALMLENS_PROJECT_ID"));
    var result = client.text.moderate(new ModerateText().text("Great photo!").externalId("comment-42"));
    System.out.println(result);
  }
}
```

**curl**

```sh
curl -X POST "https://api.calmlens.com/v1/projects/$CALMLENS_PROJECT_ID/assets?queue=false" \
  -H "X-Api-Key: $CALMLENS_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
  "name": "comment-42",
  "externalId": "comment-42",
  "content": {
    "plainText": "Great photo!",
    "type": "text/plain"
  }
}'

# Publish the comment only when the returned status is approved.
```

Use the SDK for your server language. It handles authentication, uploads and API errors. See the [Assets API](/docs/api/assets) for the HTTP reference.

## Try it without code

Paste a comment and see what CalmLens decides with your project's policy, and why. Each check is a normal upload, so it counts towards your usage and free uploads. The example above makes the same check from your server.

The web version of this page has a moderation playground: signed-in users paste text, click **Check**, and see the decision their project's policy makes, the moderation action and the six category scores. Each check is a normal text upload (synchronous, content not retained after processing), so it counts towards usage and free uploads. Signed-out readers see a sign-in prompt instead.

The same check from your server:

```ts
const result = await client.text.moderate({ text: "Love this photo!" });
// result.status: "approved" | "rejected"; result.report?.flagged; result.report?.categories
```

## 3. Read the decision

Your project's [moderation policy](/docs/guides/moderation-policies) turns each report into an action. Your app only needs two fields:

| `status` | `report.flagged` | What your app should do |
| --- | --- | --- |
| `approved` | `false` | Publish. |
| `approved` | `true` | Publish, and add it to a review queue. The policy action was `alert`. |
| `rejected` | `true` | Don't publish. Tell the author it wasn't posted. The action was `hide` or `purge`. |
| `pending` | N/A | Still being classified. Keep it unpublished and wait for a webhook. |
| `error` | N/A | Classification failed. Don't publish; retry or review manually. |

New projects use the **General community** policy, which suits most social apps and marketplaces. To see why something was flagged, read the six category scores in `report.categories`. A category with status `not-evaluated` is unknown, not safe. See [Report format](/docs/guides/moderation-reports).

## 4. Moderate an image

This example uses the queued flow: `client.assets.create()` returns an asset ID while moderation runs. Keep the post hidden from other users until it's approved. Images also support waiting in the request with `client.assets.moderate()`.

**python**

```python
import os
from calmlens import CalmLensClient

client = CalmLensClient(
    api_key=os.environ["CALMLENS_API_KEY"],
    project_id=os.environ["CALMLENS_PROJECT_ID"],
)

with open("photo.jpg", "rb") as content:
    asset = client.assets.create({
        "name": "photo.jpg",
        "content": {"data": content, "type": "image/jpeg"},
        "externalId": "post-42",
    })
# Save asset["id"] with the post; keep it hidden until approved.
print(asset["id"], asset["status"])
```

**go**

```go
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) }
    ctx := context.Background()
    content, err := os.Open("photo.jpg")
    if err != nil { log.Fatal(err) }
    defer content.Close()
    externalID := "post-42"
    asset, err := client.Assets.Create(ctx, calmlens.SubmitAssetOptions{Name: "photo.jpg", ExternalID: &externalID, Content: &calmlens.Content{Data: content, Type: "image/jpeg"}})
    if err != nil { log.Fatal(err) }
    // Save asset.ID with the post; keep it hidden until approved.
    fmt.Println(asset.ID, asset.Status)
}

```

**JavaScript**

```javascript
const asset = await client.assets.create({
  name: file.name,
  content: { data: file, type: file.type }, // a File or Blob; base64 and buffers also work
  externalId: `post-${post.id}`,
});
// asset.status is "pending"; save asset.id with the post
```

**ruby**

```ruby
require 'calmlens'
client = CalmLens::Client.new(api_key: ENV.fetch('CALMLENS_API_KEY'), project_id: ENV.fetch('CALMLENS_PROJECT_ID'))
asset = client.assets.create(name: 'photo.jpg', content: {data: File.binread('photo.jpg'), type: 'image/jpeg'})
puts asset['id']
client.close
```

**rust**

```rust
use calmlens::{models::*, Client};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new(std::env::var("CALMLENS_API_KEY")?, std::env::var("CALMLENS_PROJECT_ID")?)?;
    let bytes = std::fs::read("photo.jpg")?;
    let asset = client.assets().create(SubmitAsset::new("photo.jpg", Content::bytes(bytes, "image/jpeg"))).await?;
    println!("{}", asset.id);
    Ok(())
}
```

**java**

```java
import com.calmlens.CalmLensClient;
import com.calmlens.Models.*;

public class Example {
  public static void main(String[] args) throws Exception {
    var client = new CalmLensClient(System.getenv("CALMLENS_API_KEY"), System.getenv("CALMLENS_PROJECT_ID"));
    byte[] bytes = java.nio.file.Files.readAllBytes(java.nio.file.Path.of("photo.jpg"));
    Asset asset = client.assets.create(new SubmitAsset("photo.jpg", Content.bytes(bytes, "image/jpeg")).externalId("post-42"));
    System.out.println(asset.id());
  }
}
```

**curl**

```sh
curl -X POST "https://api.calmlens.com/v1/projects/$CALMLENS_PROJECT_ID/assets/upload?name=photo.jpg&contentType=image%2Fjpeg&queue=true&visibility=private&externalId=post-42" \
  -H "X-Api-Key: $CALMLENS_API_KEY" \
  -H "X-CalmLens-Upload: content" \
  -H "Content-Type: image/jpeg" \
  --data-binary @photo.jpg
```

For this queued upload, receive the result by webhook.

### Receive the result by webhook

In [Webhooks & connectors](/portal/connectors), subscribe your HTTPS endpoint to [`asset.classified`](/docs/api/events#event-asset-classified). The event includes the decision in `data.asset.status`. Also subscribe to [`asset.failed`](/docs/api/events#event-asset-failed) so your app can handle processing failures. Save the returned asset ID to match the event to your submission.

[Set up a webhook](/docs/guides/webhooks).

The SDK chooses the appropriate upload mode. See [upload modes](/docs/guides/uploading-assets#upload-modes) for file limits and multipart uploads.

## 5. Try a rejection safely

Use [test inputs](/docs/guides/testing) to check your rejection flow without uploading real harmful content. For example, add `<!calmlens-test XXXXXXXX harassment>` to a comment, replacing `XXXXXXXX` with the last 8 characters of your project ID.

## 6. Serve approved images (optional)

To host approved files with CalmLens, follow [Delivering assets](/docs/guides/delivering-assets). It covers retention, public and private access, signed URLs and image transforms.

## AI assistants

Use [the documentation index](/llms.txt) to discover published Markdown guides and the OpenAPI contract. Append `.md` to a documentation page URL to read its Markdown export.

## Next steps

- [How CalmLens works](/docs/how-it-works): the concepts in one page.

- [Policies and actions](/docs/guides/moderation-policies): choose what gets hidden.

- [JavaScript SDK](/docs/sdk/javascript): every method, plus a mock client for unit tests.

- [Python SDK](/docs/sdk/python): synchronous calls, streamed uploads and local testing.

- [Go SDK](/docs/sdk/go): typed calls, context cancellation and local testing.

- [Rust SDK](/docs/sdk/rust), [Ruby SDK](/docs/sdk/ruby) and [Java SDK](/docs/sdk/java): installation, uploads and testing.
