Skip to content

apitally/cli

Repository files navigation

Apitally CLI

Tests Codecov Release npm

A command-line interface for Apitally, built for humans and agents.

Apitally is a simple API monitoring and analytics tool that makes it easy to understand API usage, monitor performance, and troubleshoot issues.

Learn more about Apitally on our 🌎 website or check out the 📚 documentation.

Highlights

  • Stream API request logs from Apitally to a local DuckDB database
  • Run arbitrary SQL queries against that database to analyze API request data
  • Bundled DuckDB, no runtime dependencies, written in Rust (it's fast)

Installation

The CLI can be used with npx, no installation required:

npx @apitally/cli <command>

If you wish to install the binary directly, use the standalone installer script:

# On macOS and Linux
curl -fsSL https://apitally.io/cli/install.sh | sh
# On Windows
powershell -ExecutionPolicy Bypass -c "irm https://apitally.io/cli/install.ps1 | iex"

You can also download the binary for your platform from the latest release on GitHub.

Authentication

To use the CLI, you need an API key. You can create one in the Apitally dashboard under Settings → API keys.

Then run the auth command to configure your API key interactively:

apitally auth

Or provide the key directly:

apitally auth --api-key "your-api-key"

The API key is saved to ~/.apitally/auth.json.

You can also set the API key via the APITALLY_API_KEY environment variable or pass the --api-key flag to any command.

Commands

Run apitally --help to see all commands and options.

whoami

apitally whoami

Check authentication and show the authenticated team.

Example command:

apitally whoami

Example output:

{"team":{"id":1,"name":"My Team"}}

apps

apitally apps [--db [<path>]]

List all apps in your team. Use this to get app IDs for other commands.

Outputs newline-delimited JSON (one object per line). With --db, data is written to the apps table in a DuckDB database instead. Defaults to ~/.apitally/data.duckdb if no path is given. Existing records will be updated. If the database file doesn't exist, it will be created.

Example command:

apitally apps

Example output (without --db flag):

{"id":1,"name":"Example API 1","framework":"FastAPI","client_id":"76bf09e2-8996-4dd0-bdb5-ccdc3a48f64c","envs":[{"id":1,"name":"prod","created_at":"2026-01-01T00:00:00.000000Z","last_sync_at":"2026-01-01T01:00:00.000000Z"}],"created_at":"2026-01-01T00:00:00.000000Z"}
{"id":2,"name":"Example API 2","framework":"FastAPI","client_id":"339c08bb-5e88-4cba-a24d-be9d80fbd096","envs":[{"id":2,"name":"prod","created_at":"2026-01-02T00:00:00.000000Z","last_sync_at":"2026-01-02T01:00:00.000000Z"}],"created_at":"2026-01-02T00:00:00.000000Z"}

consumers

apitally consumers <app-id> [--requests-since <datetime>] [--db [<path>]]

List all consumers for an app. Use this to get consumer details to combine with request log data, which only includes consumer IDs.

Use the --requests-since flag to only return consumers that have made requests since a specific date/time (ISO 8601 format).

Outputs newline-delimited JSON (one object per line). With --db, data is written to the consumers table in a DuckDB database instead. Defaults to ~/.apitally/data.duckdb if no path is given. Existing records will be updated. If the database file doesn't exist, it will be created.

Example command:

apitally consumers 1 --requests-since "2026-01-01T00:00:00Z"

Example output (without --db flag):

{"id":1,"identifier":"[email protected]","name":"Bob","group":{"id":1,"name":"Admins"},"created_at":"2026-01-01T00:00:00Z","last_request_at":"2026-01-01T01:00:00Z"}
{"id":2,"identifier":"[email protected]","name":"Alice","group":null,"created_at":"2026-01-02T00:00:00Z","last_request_at":"2026-01-02T02:00:00Z"}

request-logs

apitally request-logs <app-id> \
  --since <datetime> [--until <datetime>] \
  [--fields <json>] [--filters <json>] [--limit <n>] \
  [--db [<path>]]

Retrieve request log data for an app.

The time range is --since inclusive and --until exclusive. If --until is not provided, it defaults to now. If a timestamp does not include a timezone, UTC is assumed.

Outputs newline-delimited JSON (one object per line). With --db, data is written to the request_logs table in a DuckDB database instead. Defaults to ~/.apitally/data.duckdb if no path is given. Existing records will be updated. If the database file doesn't exist, it will be created.

Results are ordered by timestamp ascending and capped at 1,000,000 records. Requests to endpoints marked as excluded in the Apitally dashboard are not returned.

Use the --fields flag to pass a JSON array of fields to include. If omitted, default fields are returned.

Field Type Default
timestamp string (datetime)
request_uuid string (ID)
app_env string
method string
path string
url string
consumer_id int (ID)
request_headers array of tuples
request_size int
request_body_json string (JSON)
status_code int
response_time_ms int
response_headers array of tuples
response_size int
response_body_json string (JSON)
client_ip string
client_country_iso_code string
exception_type string
exception_message string
exception_stacktrace string
sentry_event_id string (ID)
trace_id string (ID)

Use the --filters flag to pass a JSON array of filter objects with field, op, and value keys. Multiple filters are combined with a logical AND. Supported operators are:

  • String fields: eq, neq, in, not_in, like, not_like, ilike, not_ilike
  • Numeric fields: eq, neq, gt, gte, lt, lte, in, not_in
  • Header fields: eq, neq, in, not_in, like, not_like, ilike, not_ilike, exists, not_exists
  • ID fields: eq, neq, in, not_in

For in and not_in, value must be a JSON array. For header fields, also provide key for the header name. For exists and not_exists, omit value.

Example command:

apitally request-logs 1 \
  --since "2026-01-01T00:00:00Z" \
  --filters '[{"field":"status_code","op":"gte","value":400}]' \
  --limit 2

Example output (without --db flag):

{"timestamp":"2026-01-01T00:15:00.000Z","request_uuid":"2fbc1df6-3124-4ed1-a376-7d2c64e4d5cf","app_env":"prod","method":"GET","path":"/test/1","url":"https://api.example.com/test/1","consumer_id":1,"request_size":0,"status_code":404,"response_time_ms":122,"response_size":66,"client_ip":"203.0.113.10","client_country_iso_code":"DE"}
{"timestamp":"2026-01-01T00:16:00.000Z","request_uuid":"c6d32f8a-0bc1-43c1-b6c5-7d04363dc97c","app_env":"prod","method":"GET","path":"/test/2","url":"https://api.example.com/test/2","consumer_id":1,"request_size":0,"status_code":500,"response_time_ms":68,"response_size":66,"client_ip":"198.51.100.22","client_country_iso_code":"US"}

sql

apitally sql [<query>] [--db <path>]

Run a SQL query against a local DuckDB database and output the result as newline-delimited JSON (one object per line). If the query argument is omitted, the query is read from stdin. Defaults to ~/.apitally/data.duckdb if --db is not provided.

Available tables are apps, app_envs, consumers, and request_logs.

DuckDB's SQL dialect closely matches PostgreSQL's semantics.

Example commands:

apitally sql "SELECT timestamp, method, path, status_code FROM request_logs WHERE status_code >= 400"
echo "SELECT COUNT(*) FROM request_logs" | apitally sql

Example output:

{"timestamp":"2026-01-01T00:16:00.000Z","method":"POST","path":"/users","status_code":500}
{"timestamp":"2026-01-01T00:15:00.000Z","method":"GET","path":"/users/{userId}","status_code":404}

Exit codes

Code Meaning
0 Success
1 General / unknown error
2 Usage error (invalid arguments, missing required flags)
3 Authentication error (missing or invalid API key)
4 Input error (invalid argument values)
5 API / network error

Getting help

If you need help please create a new discussion on GitHub or email us at [email protected]. We'll get back to you as soon as possible.