modapi is the official command-line interface for Moderation API. Use it to moderate content, manage authors, review queues, actions, wordlists, webhooks, projects and channels from your terminal, in scripts and CI, and from AI coding agents.
- Get an API key in the dashboard.
- Read the documentation and the API reference.
Moderation API: Moderate text, images, video and audio in real time, manage authors, review queues and actions, and receive webhooks.
More information about the API can be found at https://docs.moderationapi.com
brew install moderation-api/tap/modapicurl -fsSL https://raw.githubusercontent.com/moderation-api/cli/main/scripts/install.sh | bashiwr -useb https://raw.githubusercontent.com/moderation-api/cli/main/scripts/install.ps1 | iexAlternatively, install directly via Go:
go install github.com/moderation-api/cli/cmd/modapi@latestDownload pre-built binaries for your platform from the releases page.
Shell completions are available for Bash, Zsh, Fish, and PowerShell.
# Add to ~/.bashrc:
source <(modapi completion bash)
# Or install permanently:
modapi completion bash > /etc/bash_completion.d/modapi# Add to ~/.zshrc:
source <(modapi completion zsh)
# Or install permanently:
modapi completion zsh > "${fpath[1]}/_modapi"modapi completion fish | source
# Or install permanently:
modapi completion fish > ~/.config/fish/completions/modapi.fishmodapi completion powershell | Out-String | Invoke-Expressionmodapi content submit --content '{"type":"text","text":"Hey, check out my profile for a deal!"}' --content-id message-48213 --author-id user-1092 --conversation-id room-general --metadata '{"platform":"ios"}'
Run init in your project's directory. It links one of your Moderation API projects or creates a new one, and writes the project's keys to your env file:
modapi login # once per machine; opens the browser
modapi init # in your project
modapi doctor # check the setup at any timeinit detects your language, framework and package manager, writes MODAPI_SECRET_KEY and MODAPI_WEBHOOK_SECRET to .env.local (Next.js) or .env, and adds that file to .gitignore. Describe your platform so the project can suggest moderation policies:
modapi init --domain acme.com \
--context "Marketplace where buyers and sellers chat before a booking" \
--brief "Stop contact details shared outside the platform, scams and harassment"To connect a directory to a project you already have, run modapi link (or modapi link "<project name>"). It makes a new key for the project, so you can revoke it on its own. Agents and scripts pass init --project <id or name> or init --new instead of answering the question.
init does not change your code. To add the moderation calls, let your coding agent do it with the setup skill:
npx skills add moderation-api/skillsThen ask your agent to "set up Moderation API". The skill finds where users create content, adds the moderation call, handles the result and sets up webhooks, asking you before each change.
Without a browser (for example over SSH), use modapi login --device. In CI, skip login and set MODAPI_ORGANIZATION_KEY to an organization key from the dashboard.
Set your API key once, then submit content:
export MODAPI_SECRET_KEY="your-project-key"
modapi content submit --content '{"type":"text","text":"Your text here"}' --author-id user-123Images, video and audio take a URL:
modapi content submit --content '{"type":"image","url":"https://example.com/image.jpg"}'Use --jq to read one field of the result, for example the recommended action (allow, review or reject):
modapi content submit --content '{"type":"text","text":"Your text here"}' --jq '.recommendation.action'Add --dry-run to any command to see the request it would send, without sending it.
This CLI is built to be driven by AI coding agents as well as people: everything an agent needs is discoverable from the binary itself, and every command can be validated without credentials. Work down this ladder:
| Run | You get |
|---|---|
modapi --help, modapi actions list --help |
Commands by category, runnable examples, flags |
modapi --usage, modapi actions list --usage |
The command surface as machine-readable KDL: commands, aliases, flags, defaults, env vars, config keys |
modapi actions create --schema |
The exact JSON Schema of the command's request body (all $refs bundled) — build a valid --body from it |
modapi actions list --dry-run |
The exact HTTP request (method, URL, headers, body), with no credentials or network call |
modapi actions list --output-format json (or --jq) |
Machine-readable output |
# Every command, flag, default, env var and config key, as KDL
modapi --usage
# One command's subtree only
modapi actions list --usage--schema is available on every command that accepts a request body (--body, stdin, or a whole-body flag where the command has one), including intent commands. It prints the JSON Schema the request is validated against and exits without calling the API.
# JSON Schema (draft 2020-12) of the request body, with every $ref bundled under $defs
modapi actions create --schemaStart quota-spending commands with --dry-run. It validates inputs, resolves the request, redacts secrets and binary payloads, makes no network call, and exits 0. It never reads the OS keychain; credentials supplied by flag, environment, or config file are included only as [REDACTED].
# Human preview: the [DRY-RUN] block is on stderr and stdout is empty
modapi actions list --dry-run
# Machine preview: compact JSON on stdout and silent stderr
modapi actions list --dry-run --output-format jsonThe machine form writes one object per would-be request, one per line (NDJSON for multi-request commands), with exactly this shape:
{"dry_run":true,"request":{"method":"POST","url":"https://…","headers":{"Accept":["application/json"],…},"body":<JSON value | string | null>}}body is a parsed JSON value when the body is JSON, a string for text, "<bytes:N>" for binary data, and null when absent. An explicit caller --jq also selects this JSON preview protocol, but the filter is not applied to preview objects. Command-declared jq presets do not select or filter the preview.
Local mutation commands make no request under --dry-run: instead of a preview they emit one {"dry_run":true,"local":true,"command":"…","message":"…"} object. select(.request) keeps only would-be requests; select(.local) keeps the local no-ops.
# JSON on stdout
modapi actions list --output-format json
# Filter or reshape with a jq expression (always emits JSON, overrides --output-format)
modapi actions list --jq '.'
# Print jq string results as plain text instead of JSON strings (like jq -r)
modapi actions list --jq '.' --raw-output--output-format toon emits TOON, a compact line-oriented format that uses fewer tokens than JSON; it is the default in agent mode.
Required-input prompts and guided configure / auth login forms are enabled by default. Required-input prompts require an interactive terminal; off-TTY forms read line input from stdin. Use --no-interactive to force flag-only execution.
# Prompt for missing command inputs
modapi actions list --interactive
# Open the guided configuration form
modapi configure --interactive
# Explicitly launch the terminal command explorer
modapi exploreAgent mode turns on automatically when a known agent environment is detected (CLAUDECODE, CURSOR_AGENT, CODEX, AIDER, CLINE, WINDSURF_AGENT, GITHUB_COPILOT, AMAZON_Q, GEMINI_CODE_ASSIST, SRC_CODY) or with --agent-mode (--agent-mode=false disables detection).
In agent mode interactive prompts never launch, output defaults to TOON, and every failure — API errors and CLI usage errors alike — is one JSON envelope on stderr:
--output-format json and --jq use the same error envelope without requiring agent mode.
{
"error": "...",
"error_type": "validation_error",
"error_reason": "CLI_VALIDATION",
"exit_code": 2,
"message": "human-readable message",
"hints": ["what to try next"]
}error_type is one of authentication_error, authorization_error, not_found, validation_error, rate_limit_error, server_error, api_error, connection_error, protocol_error, runtime_error, unsupported_error, async_failed, async_timeout, async_unknown_state. Classification derives from the HTTP status and transport evidence; error_reason is absent for API errors. Status-less local failures may use CLI_VALIDATION, CLI_CONNECTION, CLI_PROTOCOL, CLI_RUNTIME, CLI_UNAVAILABLE, CLI_AUTHENTICATION, or the async polling reasons CLI_ASYNC_FAILED, CLI_ASYNC_TIMEOUT, and CLI_ASYNC_UNKNOWN_STATE. hints preserves server guidance first, adds the most specific local taxonomy guidance, then typed CLI and command-specific guidance, removing exact duplicates. exit_code is always the code for the final error_type shown in the envelope: 1 runtime, 2 usage, or 3 authentication/authorization.
List commands accept --all to fetch every page and stream results as they arrive (one JSON value per line with --output-format json; --max-pages N bounds the walk).
Structured output and agent mode never write pagination hints to stderr; if a later page fails or the server repeats a cursor, the command exits non-zero after the pages already written.
modapi actions list --all --output-format jsonAuthentication credentials can be configured in four ways (in order of priority):
Pass credentials directly as flags to any command:
modapi --secret-key "$MODAPI_SECRET_KEY" actions listSet credentials via environment variables:
| Variable | Description |
|---|---|
MODAPI_SECRET_KEY |
HTTP Bearer |
Credentials are stored securely in your operating system's keychain when you run:
modapi configureSecret credentials (tokens, API keys, passwords) are automatically stored in:
- macOS: Keychain
- Linux: GNOME Keyring / KWallet (via D-Bus Secret Service)
- Windows: Windows Credential Locker
If no keychain is available (e.g., in CI environments), credentials fall back to the config file.
Where the keychain cannot be unlocked (e.g., headless Linux or SSH sessions with a locked GNOME Keyring), skip it entirely with --no-keyring, MODAPI_NO_KEYRING=true, or no_keyring: true in the config file. The keychain is then never read or written, and secrets are stored in the config file instead; flags and environment variables still take precedence.
Run the interactive configure command to store non-secret settings:
modapi configureConfiguration is stored in ~/.config/modapi/config.yaml.
Moderation API has two kinds of API keys:
- A project key works with one project: content, authors, review queues, actions, wordlists, webhooks and the account.
- An organization key works with the management API:
modapi projectsandmodapi channels. It can also use any project's endpoints: name the project with--project-id.
A project key can't run the management commands: the API returns 403 with the code organization_key_required. To run a management command while MODAPI_SECRET_KEY holds your project key, pass the organization key for that command:
modapi projects list --secret-key "$MODAPI_ORGANIZATION_KEY"Available commands
analytics- Operations for analyticsauthors- Operations for authorsfeedback- Operations for feedbackcreate- Send feedback
queue- Operations for queueactions- Operations for actionswebhooks- Operations for webhookswebhook-secret- Operations for webhook-secretretrieve- Get the webhook signing secret
account- Operations for accountretrieve- Get account details
wordlist- Operations for wordlistcontent- Operations for contentsubmit- Submit content
projects- Operations for projectschannels- Operations for channels
Commands that accept a request body take it three ways, with a clear priority chain. The examples use modapi actions create; every body-bearing command works the same way and prints its exact request schema with --schema.
Provide the entire request body as a JSON string:
modapi actions create --body '{"name":"Suspend for 24 hours","key":"suspend-24h","type":"AUTHOR_BLOCK_TEMP","queueBehaviour":"REMOVE"}'Pipe JSON into any command that accepts a request body:
echo '{"name":"Suspend for 24 hours","key":"suspend-24h","type":"AUTHOR_BLOCK_TEMP","queueBehaviour":"REMOVE"}' | modapi actions createThis is useful for chaining commands, reading from files, or scripting:
# Read body from a file
modapi actions create < request.json
# Pipe from another command
curl -s https://example.com/request.json | modapi actions createWhen multiple input methods are used, the priority is:
| Priority | Source | Description |
|---|---|---|
| 1 (highest) | Individual flags | A field flag always wins |
| 2 | --body flag |
Whole-body JSON via flag |
| 3 (lowest) | Stdin | Piped JSON input |
Use --server-url to override the server URL entirely, bypassing any named or indexed server selection:
modapi --server-url https://custom-api.example.com actions listPrecedence: --server-url > --server > default
Every command supports a --output-format flag that controls how the response is rendered to stdout.
| Format | Flag | Description |
|---|---|---|
| Pretty | --output-format pretty (default) |
Aligned key-value pairs with color, nested indentation. Human-readable at a glance. |
| JSON | --output-format json |
JSON output. Passthrough when the response is already JSON (preserves original field order and numeric precision). Falls back to typed marshaling otherwise. |
| YAML | --output-format yaml |
YAML output via standard marshaling. |
| Table | --output-format table |
Tabular output for array responses. |
| TOON | --output-format toon |
Token-Oriented Object Notation — a compact, line-oriented format that typically uses 30–60% fewer tokens than JSON. Well-suited for piping responses into LLM prompts. |
# Default pretty output
modapi actions list
# Machine-readable JSON
modapi actions list --output-format json
# TOON for LLM-friendly compact output
modapi actions list --output-format toon
# Pipe JSON to jq without using --output-format
modapi actions list --output-format json | jq '.'Use --jq to filter or transform the response inline using a jq expression. This always outputs JSON and overrides --output-format:
# Extract a single field
modapi actions list --jq '.'
# Reshape with any jq program; --raw-output prints string results as plain text (like jq -r)
modapi actions list --jq '.' --raw-outputUse --color to control terminal colors:
| Value | Behavior |
|---|---|
auto (default) |
Color when stdout is a TTY, plain text otherwise |
always |
Always colorize |
never |
Never colorize |
The NO_COLOR and FORCE_COLOR environment variables are also respected.
When using --all (pagination) or streaming operations, output is written incrementally as items arrive:
| Format | Streaming behavior |
|---|---|
json |
One compact JSON object per line (NDJSON) |
yaml |
YAML documents separated by --- |
toon |
One TOON-encoded object per block, separated by blank lines |
pretty (default) |
Pretty-printed items separated by blank lines |
Some operations in this CLI support automatic pagination. These operations accept --all to automatically fetch all pages and stream results incrementally.
# Fetch a single page (default behavior)
modapi actions list
# Automatically fetch all pages
modapi actions list --allUse --max-pages with --all to cap the number of pages fetched. A negative value is invalid; 0 means unlimited. Passing --max-pages without --all is an error.
# Fetch at most 5 pages
modapi actions list --all --max-pages 5When using --all, output is streamed as each page is fetched. Operations whose pagination declaration names an outputs.results array emit one item at a time. Other operations emit one complete page object at a time, preserving the single-page response shape and any continuation cursor.
| Format | Behavior |
|---|---|
--output-format json |
One JSON object per line (NDJSON) |
--output-format yaml |
YAML documents separated by --- |
--output-format toon |
One TOON-encoded block per item, separated by blank lines |
| Default (pretty) | Pretty-printed items separated by blank lines |
# Stream all results as NDJSON
modapi actions list --all --output-format json
# Pipe to jq for further processing
modapi actions list --all --output-format json | jq '.'
# Use the built-in --jq flag
modapi actions list --all --jq '.'Under the hood, --all calls the operation once, then follows the underlying Next() pagination closure to fetch subsequent pages. Results are written to stdout as they arrive rather than buffered in memory, so this works well even with large result sets.
Without --all, paginated operations behave like any other command — pass cursor, page, offset, or limit flags manually and get a single page of results. In pretty or table output, a cursor response that proves another page exists prints a hint on stderr. JSON, YAML, TOON, --jq, and agent mode keep stderr silent on success. Offset/limit responses do not guess from a full result page. Cursor operations that declare both a results array and a mutable limit also suppress the hint because the client cannot safely reproduce the SDK's runtime limit check.
Pagination can fail after earlier pages have already been written. A later-page API failure or a repeated/cyclic continuation cursor stops with a non-zero exit status; callers should treat stdout as partial whenever the command exits non-zero. --all tracks cursor values and stops before issuing another request when the server repeats one; the same applies to next URLs when the target generator supports them.
The CLI uses standard exit codes to indicate success or failure:
| Exit Code | Meaning |
|---|---|
0 |
Success |
1 |
Runtime/API failure |
2 |
Usage or input failure |
3 |
Authentication or authorization failure |
On success, the response data is printed to stdout as JSON. On failure, error details are printed to stderr.
# Capture output and handle errors
modapi actions list --output-format json > output.json 2> error.log
if [ $? -ne 0 ]; then
echo "Error occurred, see error.log"
fiIn pretty mode, each error is printed once as Error (<type>): <message>, followed by its reason/HTTP status, actionable Fix: bullets, and only non-duplicative residual details.
In agent mode, or with explicit --output-format json, --output-format toon, or --jq machine output, stderr is one classified JSON envelope with exit_code, error_type, optional error_reason, message, hints, and optional status_code — see For AI agents.
The CLI includes two diagnostic flags available on all commands:
Preview what would be sent without making any network calls:
modapi actions list --dry-runIn human output modes, stdout is empty and the [DRY-RUN] block goes to stderr. It includes:
- HTTP method and URL
- Request headers (sensitive values redacted)
- Request body preview (sensitive fields redacted)
With --output-format json, or with a caller-explicit --jq, stderr is silent and stdout is NDJSON: one compact preview object per would-be request. The jq filter is not applied, and command-declared jq presets do not select the JSON protocol.
{"dry_run":true,"request":{"method":"POST","url":"https://…","headers":{"Accept":["application/json"],…},"body":<JSON value | string | null>}}JSON bodies remain structured; text bodies are strings; binary bodies are "<bytes:N>"; absent bodies are null. Headers retain all values as arrays, with credentials replaced by [REDACTED]. Dry-run never reads the OS keychain, but credentials supplied by flag, environment, or config file still appear redacted. The command exits successfully without contacting the API.
Local mutation commands emit one {"dry_run":true,"local":true,"command":"…","message":"…"} object in place of a preview; filter with select(.request) or select(.local).
Log request and response diagnostics while running normally:
modapi actions list --debugDebug output goes to stderr and includes:
- Request method, URL, headers, and body preview
- Response status, headers, and body preview
- Transport errors (if any)
The command still executes normally and produces its regular output on stdout.
If both --dry-run and --debug are set, --dry-run takes precedence and no network calls are made.
Sensitive information is automatically redacted in diagnostic output:
- Headers:
Authorization,Cookie,Set-Cookie,X-API-Key, and other security headers show[REDACTED] - Body: JSON fields named
password,secret,token,api_key,client_secret, etc. show[REDACTED] - Binary data: binary media and canonical base64 strings are replaced with
<bytes:N> - URL query: credential-like query parameters are replaced with
[REDACTED]
Diagnostic output should still be treated as potentially sensitive operational data.
Found a bug? Open an issue in this repository. This package is generated from the Moderation API OpenAPI spec, so pull requests to the generated code can't be merged as they are, but we use them to fix the generator.
This CLI is in beta, and there may be breaking changes between versions without a major version update. Therefore, we recommend pinning usage to a specific package version. This way, you can install the same version each time without breaking changes unless you are intentionally looking for the latest version.
This CLI is generated programmatically. Edits to generated files are overwritten on regeneration. To customize it:
- Configuration and behavior: Use OpenAPI overlays in the Speakeasy workflow with
x-speakeasy-*extensions (for example,x-speakeasy-cli-commands) to define commands, flags, help text, examples, authentication, and grouping. - Persistent code changes: Store unified diffs as patch files at
.speakeasy/patches/<path-of-generated-file>.patch; they are re-applied on every generation.