ddcli is a Datadog CLI designed for scripts and LLM agents. It keeps the
interface narrow and discoverable while returning stable JSON on stdout.
Most commands are read-only; write commands are explicit and idempotent.
Use a scoped API key and application key:
export DD_SITE=datadoghq.com
export DD_API_KEY=...
export DD_APP_KEY=...DD_APPLICATION_KEY is also accepted. Flags can override env vars:
ddcli --site us3.datadoghq.com --api-key "$DD_API_KEY" --app-key "$DD_APP_KEY" dashboards listFor temporary OAuth-style credentials, DD_ACCESS_TOKEN or --access-token is accepted and sent as bearer auth through the Datadog Go client. A full auth login flow is not implemented yet because it needs OAuth client registration and secure refresh-token storage.
Every command writes JSON to stdout. Diagnostics and errors go to stderr.
Default output is wrapped:
{
"query": {
"command": "logs search",
"filter": "service:web error"
},
"meta": {
"site": "datadoghq.com",
"from": "now-15m",
"to": "now",
"limit": 50
},
"data": {}
}Use --pretty for indented JSON and --raw to print the Datadog response without the query and meta wrapper.
ddcli logs search --query 'service:web error' --from now-15m --to now --limit 50
ddcli logs indexes order
ddcli logs indexes list --query production
ddcli logs indexes get bandzoogle-production
ddcli logs indexes patch-exclusions patch.json --dry-run
ddcli logs indexes patch-exclusions patch.json
ddcli logs pipelines order
ddcli logs pipelines list --query openresty
ddcli logs pipelines get PIPELINE_ID
ddcli logs pipelines apply pipeline.json --dry-run
ddcli logs pipelines apply pipeline.json
ddcli synthetics list --query checkout --limit 25
ddcli synthetics get abc-def-ghi
ddcli synthetics validate test.json
ddcli synthetics apply test.json --dry-run
ddcli synthetics apply test.json
ddcli metrics list --query system.cpu
ddcli metrics metadata system.cpu.user
ddcli metrics query --query 'avg:system.cpu.user{*}' --from now-1h --to now
ddcli hosts list --filter 'env:prod' --limit 25
ddcli hosts totals
ddcli dashboards list --query app --limit 100
ddcli dashboards get abc-def-ghi
ddcli dashboards apply dashboard.json --dry-run
ddcli dashboards apply dashboard.json
ddcli monitors list --name 'Kamal edge'
ddcli monitors get 12345678
ddcli monitors validate monitor.json
ddcli monitors apply monitor.json --dry-run --require-non-notifying
ddcli apm spans --query 'service:api @http.status_code:500' --from now-15m --to now --limit 25
ddcli appsec blocked-rules summary --from now-7d --limit 200 --pretty
ddcli appsec custom-rules list
ddcli appsec exclusion-filters list
ddcli errors search --query 'service:api' --track trace --from now-1h --to now
ddcli errors get ISSUE_ID
ddcli security signals search --query 'team:bandzoogle' --from now-1h --to now
ddcli security signals get SIGNAL_ID
ddcli cost analyze --group-by service --from now-30d --to now-2d --limit 25
ddcli cost analyze --metric aws.cost.amortized --filter 'env:prod' --group-by region
ddcli cost accounts list --provider all
ddcli cost budgets list
ddcli cost allocation-rules list
ddcli cost tag-pipelines list
ddcli scopes
ddcli scopes --command costTime flags accept now, relative values like now-15m, RFC3339 timestamps, Unix seconds, or Unix milliseconds. Logs and spans pass time strings through to Datadog; metrics and Error Tracking convert them to the epoch formats required by their APIs.
logs indexes list|get|order and logs pipelines list|get|order are
read-only. Index responses include routing filters, daily quotas,
Standard/Flex retention, and ordered exclusion filters with sample rates.
Pipeline responses preserve the API's processor order and include filters,
parser rules, remappers, and nested processors. Write access exists for both
— logs indexes patch-exclusions and logs pipelines apply — and is covered
in its own section below.
Use the explicit order commands when routing order matters:
ddcli logs indexes order --pretty
ddcli logs indexes get bandzoogle-production --pretty
ddcli logs pipelines order --pretty
ddcli logs pipelines list --query openresty --prettyThese commands require logs_read_config. Datadog also requires an
administrator-owned application key for pipeline configuration reads.
logs pipelines apply validates canonical pipeline JSON and creates or
updates a custom pipeline, the same way dashboards apply and monitors apply do. If the definition contains an id, that pipeline is updated.
Otherwise, the command matches by exact name: no match creates a pipeline,
one match updates it, and multiple matches fail without writing.
Apply refuses to write to a pipeline that is read-only on either side of the
match: a submitted definition with "is_read_only": true, or a live pipeline
Datadog already marks read-only, such as its bundled Varnish or Rails
integration pipelines. Those pipelines can never be created or edited through
this API, so if you need to change what they emit, add your own pipeline
after them instead — see insert_after_pipeline_id below.
ddcli logs pipelines apply pipeline.json --dry-run
ddcli logs pipelines apply pipeline.jsonAn optional top-level insert_after_pipeline_id places the pipeline
immediately after a named pipeline in the evaluation order, and is stripped
before the body is sent to Datadog. This is how you make a pipeline run after
a read-only integration pipeline to re-map something it produced — for
example, re-deriving a saner log-level status after Datadog's own Varnish
pipeline maps every HTTP 4xx to warning:
{
"name": "Varnish severity override",
"filter": { "query": "source:varnish" },
"insert_after_pipeline_id": "4DOu6dYaR46nARsjyjShbg",
"processors": [
{
"type": "category-processor",
"target": "http.varnish_severity",
"categories": [
{ "filter": { "query": "@http.status_code:[500 TO 599]" }, "name": "error" },
{ "filter": { "query": "@http.status_code:[200 TO 499]" }, "name": "info" }
]
},
{
"type": "status-remapper",
"sources": ["http.varnish_severity"]
}
]
}Order placement is idempotent: apply fetches the live pipeline order first
and only issues an order update when the pipeline is not already positioned
immediately after insert_after_pipeline_id; a rerun with no drift reports
order_changed: false and makes no order write. --dry-run performs the same
lookups and reports the intended action and order diff without writing
anything, and needs no write permission — only logs_read_config.
The application key or access token needs logs_write_pipelines (Datadog UI:
Logs Write Pipelines) for the write itself; name-based matching and the
read-only check also need logs_read_config. Pipeline configuration
endpoints require an administrator-owned application key. The exact
logs_write_pipelines permission name has not been confirmed against a live
403 from this tool — if apply is rejected on authorization, the error detail
will name whatever permission Datadog actually requires.
logs indexes patch-exclusions is one of two log-configuration write
commands (the other is logs pipelines apply above). It fetches the named
index, requires every named exclusion to occur exactly once
with the exact expected query, replaces only those query strings, and sends one
index update request containing all current updateable properties. Missing,
duplicate, stale, unknown, empty, and no-op patch entries fail without writing.
Patch files are non-secret JSON:
{
"index_name": "example-index",
"replacements": [
{
"name": "Example exclusion",
"expected_query": "service:example status:info",
"replacement_query": "service:example status:(info OR ok)"
}
]
}Always run --dry-run first. It performs the read and all precondition checks,
then prints the exact before/after queries and preserved invariants without an
update. The guarded read requires the logs_read_config RBAC permission.
Applying through the V1 UpdateLogsIndex endpoint requires
logs_modify_indexes, shown as Logs Modify Indexes in the Datadog UI.
These are role or scoped application-key permissions; Datadog does not offer
them as OAuth client scopes.
dashboards apply validates a canonical Datadog dashboard JSON file and creates
or updates it. If the definition contains an id, that dashboard is updated.
Otherwise, the command matches by exact title: no match creates a dashboard,
one match updates it, and multiple matches fail without writing.
Use --dry-run to validate the JSON locally without credentials or a write:
ddcli dashboards apply dashboards/review-edge-overview.json --dry-run --pretty
ddcli dashboards apply dashboards/review-edge-overview.json --prettyThe application key or access token needs dashboards_write; title-based
matching also needs dashboards_read.
monitors apply validates canonical monitor JSON and creates or updates it. If
the definition contains an id, that monitor is updated. Otherwise, the
command matches by exact name: no match creates a monitor, one match updates
it, and multiple matches fail without writing.
Use --dry-run before writes and monitors validate to ask Datadog to validate
the query and options without creating the monitor. For unattended
preparation, pass --require-non-notifying; the command then accepts only
draft monitors or monitors globally silenced indefinitely with no notification
mentions in their message. Draft monitors require Datadog's draft-monitor
preview to be enabled for the organization.
The application key or access token needs monitors_write for validation and
apply; name-based matching and inventory also need monitors_read.
Use ddcli scopes to print the Datadog RBAC permissions and, where available,
separate OAuth scopes needed by each command group. Commands marked with no
OAuth scope require application-key authentication. This command does not
require Datadog credentials.
Datadog API keys identify the organization. Access is controlled by the application key owner's role permissions, scoped application key permissions, or OAuth access token scopes.
Unlike most commands, ddcli scopes prints a compact human-readable permissions list by default because it is primarily a setup reference. Use --raw if you need the underlying JSON data object.
ddcli synthetics apply manages a Synthetic API test from a canonical JSON
definition, the same way dashboards apply and monitors apply do, so a test
can live in version control next to the monitors it backs up.
ddcli synthetics validate systems/production/datadog/synthetics/asset-delivery.json
ddcli synthetics apply systems/production/datadog/synthetics/asset-delivery.json --dry-run
ddcli synthetics apply systems/production/datadog/synthetics/asset-delivery.jsonMatching works like the other apply commands:
- A
public_idin the JSON names the test to update. - Otherwise apply matches an existing test by exact
name. - Two tests with the same name is a refusal, not a guess, so an ambiguous match can never overwrite the wrong test. A name belonging to a browser or mobile test is refused for the same reason.
--dry-runresolves the match read-only and prints what would be written.
public_id and monitor_id are assigned by Datadog. Apply strips both from the
request body and takes the public ID from the match instead, so a file produced
by ddcli synthetics get can be edited and applied back.
Both single-request API tests ("subtype": "http") and multistep API tests
("subtype": "multi") are supported. Browser and mobile tests are not: they use
different endpoints and schemas.
Validation is strict about unrecognized keys, at any depth. The Datadog client
keeps unknown JSON keys instead of rejecting them, so {"tick_evry": 300} would
otherwise round-trip to Datadog and be ignored, leaving a test that does not do
what the file says. Apply and validate both report the dotted path instead:
Synthetic test JSON contains unsupported fields: config.steps[0].extracedValues, options.tick_evry
synthetics validate runs entirely locally and needs no credentials — Datadog
publishes no Synthetics validate endpoint, unlike monitors. Use
apply --dry-run when you also want to see which test would be written.
A multistep test that checks end-to-end asset delivery — fetch a page, extract a digest-stamped asset URL from the body, then assert the asset itself is served — looks like this:
{
"name": "Production asset delivery end-to-end",
"type": "api",
"subtype": "multi",
"message": "Asset delivery failed while pages still returned 200.",
"tags": ["env:production", "service:openresty"],
"locations": ["aws:us-east-1", "aws:eu-west-1"],
"config": {
"steps": [
{
"name": "Fetch a member page",
"subtype": "http",
"request": { "method": "GET", "url": "https://www.example.com/", "timeout": 30 },
"assertions": [{ "type": "statusCode", "operator": "is", "target": 200 }],
"extractedValues": [
{
"name": "ASSET_URL",
"type": "http_body",
"parser": { "type": "regex", "value": "https://[^\"]+/assets/application-[0-9a-f]+\\.css" }
}
]
},
{
"name": "Fetch the digest asset",
"subtype": "http",
"request": { "method": "GET", "url": "{{ ASSET_URL }}", "timeout": 30 },
"assertions": [
{ "type": "statusCode", "operator": "is", "target": 200 },
{ "type": "header", "property": "content-type", "operator": "contains", "target": "text/css" }
]
}
]
},
"options": { "tick_every": 300, "min_location_failed": 1, "retry": { "count": 1, "interval": 300 } },
"status": "live"
}Apply needs synthetics_write in addition to synthetics_read; see
ddcli scopes --command synthetics.
ddcli cost analyze queries Datadog's Cloud Cost data source through the metrics API. Its default query is sum:all.cost{*} by {service} over the last 30 days, ending at now-2d to avoid incomplete recent cost data.
For cost-cutting work, start broad and then pivot:
ddcli cost analyze --group-by service
ddcli cost analyze --group-by team
ddcli cost analyze --group-by subaccountname
ddcli cost analyze --group-by region
ddcli cost analyze --metric aws.cost.amortized --group-by instance_typeThe Cloud Cost Management inventory commands expose setup and governance context, such as connected accounts, budgets, custom allocation rules, tag pipelines, and custom cost files.
This CLI is meant to complement MCP rather than replace it everywhere. MCP is useful for integrated auth and rich tool discovery, but it also adds server and tool context to conversations. A CLI gives agents a smaller contract: --help, explicit commands, JSON stdout, stderr diagnostics, and replayable invocations outside Cursor.
Pushes to main that pass CI and change Go sources or go.mod / go.sum trigger a patch semver GitHub release (for example v0.1.1). Builds are published for Linux and macOS on amd64 and arm64.
Stable download URLs (always point at the latest release’s asset names):
- Linux x86_64:
https://github.com/bandzoogle/datadog-cli/releases/latest/download/ddcli_linux_amd64.tar.gz - Linux arm64:
https://github.com/bandzoogle/datadog-cli/releases/latest/download/ddcli_linux_arm64.tar.gz - macOS x86_64:
https://github.com/bandzoogle/datadog-cli/releases/latest/download/ddcli_darwin_amd64.tar.gz - macOS arm64:
https://github.com/bandzoogle/datadog-cli/releases/latest/download/ddcli_darwin_arm64.tar.gz
Each archive contains a single ddcli binary. Run ddcli --version to see the release tag baked into the binary. Versioned archives (ddcli_<tag>_linux_amd64.tar.gz, etc.) are attached for pinning.
bin/buildbin/build runs unit tests, builds dist/ddcli, and smoke-tests the help output
for the main command groups. Live Datadog smoke tests require credentials and
are intentionally manual for now.