Every service command is generated from Google’s Discovery Service at build time — 308 methods, 839 typed flags, zero hand-written per-service commands. That makes the tree predictable instead of memorized. For the flat, id-based view of the same index, see Discovery API.
308 methods · 10 services · generated, not curated
the naming rule
Predict any command from its method id.
There is nothing to memorize: a Discovery method id, with dots turned into spaces, is the command. The same rule generates every flag.
method id → command
# A method id, with dots turned into spaces, IS the command.
gmail.users.messages.list grr gmail users messages list
calendar.events.list grr calendar events list
drive.files.list grr drive files list
tasks.tasklists.list grr tasks tasklists list
sheets.spreadsheets.values.get grr sheets spreadsheets values get
# Resources nest as subcommands; the method is the leaf. Each leaf also
# carries its bare name as a visible alias, shown in --help.
grr gmail --help # "users" and its children, alphabetically
grr gmail users --help # "messages", "drafts", "labels", ...
# camelCase method names keep their casing as aliases:
grr gmail users getProfile --user-id me
Top level: grr auth, grr api, grr schema, grr transport, plus one subcommand per service. The tree is emitted into src/commands/generated.rs by a generator that reads the committed Discovery index, and a daily workflow opens a PR when Google adds a method — so new surface arrives without a grr release.
Flag names come from the same ids, camelCase collapsed to kebab-case, typed by what Discovery declares:
Integers are parsed as numbers: --max-results 10.
Booleans are presence flags: --include-spam-trash, not --include-spam-trash true.
Repeated parameters repeat: --label-ids INBOX --label-ids UNREAD. (Single-string parameters that take comma lists inside the value, like Drive’s --order-by, are passed through as-is.)
Enum parameters validate their values; a bad one is caught before anything is sent.
Required parameters are enforced by clap — you cannot build a request missing --user-id.
Bodies are untyped, deliberately. Discovery’s request schemas are not part of the distilled index, so POST/PATCH/PUT bodies pass through --params (a JSON object) or --body-file (a file, or - for stdin) verbatim. Typed flags win over --params on conflict.
typed flags · collision rule · bodies
# Flags are generated from Discovery parameter names: camelCase -> kebab-case.
grr gmail users messages list --user-id me \
--q "is:unread" \
--max-results 5 \
--label-ids INBOX --label-ids UNREAD \
--include-spam-trash
# --max-results is parsed as an integer, --include-spam-trash is a presence
# flag, --label-ids repeats. Enum parameters validate; required ones are
# enforced before anything is sent.
# Collision rule: a parameter literally named "format" or "query" becomes
# --param-format / --param-query (the -f output flag keeps --format).
grr gmail users messages get --user-id me --id <id> --param-format metadata
# Bodies are untyped by design -- Discovery request schemas are not in the
# index -- so they pass through --params or --body-file verbatim.
grr calendar events insert --calendar-id primary --body-file ./event.json
Every leaf also carries the same escape hatches grr api call has: --params, --body-file, repeatable --query KEY=VALUE for raw query pairs, --dry-run, and -f/--format. Dispatch resolves the leaf’s id against the embedded index and funnels into one shared call path — the two routes below produce byte-identical output:
piping · dry runs · parity
# stdout is data, stderr is logs -- so this is always valid JSON.
$ grr gmail users messages list --user-id me --q "from:github.com" --max-results 10 | jq -r '.[0].id'
19f8ab2c
# --dry-run prints the resolved request as JSON and sends nothing.
$ grr gmail users messages list --user-id me --q "is:unread" --dry-run | jq -r '.url'
https://gmail.googleapis.com/gmail/v1/users/me/messages?q=is%3Aunread
# The same method, reached through the flat escape hatch, byte-identical:
$ grr api call gmail.users.messages.list --param userId=me --param 'q=is:unread' --dry-run
the services
Every service, a few real commands.
Verified against the binary and the committed index. Run grr <service> --help for the authoritative version of any branch — these are samples, not the census.
Gmail
79 methods. Messages, threads, drafts, labels, history, attachments, filters, forwarding, POP/IMAP, send-as, CSE, delegates, and watches — the deepest tree.
grr gmail
grr gmail users messages list --user-id me --q "in:inbox" --max-results 5
grr gmail users messages get --user-id me --id <message-id> --param-format metadata
grr gmail users messages attachments get --user-id me --message-id <message-id> --id <attachment-id>
grr gmail users threads list --user-id me --max-results 10
grr gmail users labels list --user-id me
grr gmail users drafts create --user-id me --body-file ./draft.json
grr gmail users settings filters list --user-id me
Calendar
38 methods. Calendars, events, recurring instances, ACL, free/busy, colors, and settings. The calendar id is a parameter like any other — `primary` where one is accepted.
grr calendar
grr calendar events list --calendar-id primary --max-results 10
grr calendar events get --calendar-id primary --event-id <event-id>
grr calendar events insert --calendar-id primary --body-file ./event.json
grr calendar calendarList list
grr calendar freebusy query --body-file ./freebusy.json
grr calendar colors get
Drive
64 methods. Files, permissions, comments, replies, revisions, changes, drives, and approvals — including the long tail no CLI ever wraps by hand.
grr drive
grr drive files list --q "name contains 'report'" --page-size 10
grr drive files get --file-id <file-id>
grr drive files export --file-id <document-id> --mime-type application/pdf
grr drive permissions list --file-id <file-id>
grr drive comments list --file-id <file-id>
grr drive changes list --page-token <token>
grr drive files emptyTrash
People
24 methods. The People API verbatim: resource names look like people/c12345, and the API nests one `people` level, so the command says it twice.
grr people
grr people people connections list --resource-name people/me
grr people people get --resource-name people/c12345
grr people contactGroups list
grr people otherContacts list
Chat
54 methods. Spaces, members, messages, reactions, media, custom emoji, and read state. Space-scoped methods take --parent spaces/… .
grr chat
grr chat spaces list --page-size 10
grr chat spaces messages create --parent spaces/AAAA --body-file ./message.json
grr chat spaces messages list --parent spaces/AAAA --page-size 25
grr chat spaces members list --parent spaces/AAAA
Forms
10 methods. Form bodies, responses, publish settings, and push-notification watches. The API nests one `forms` level, like People does with `people`.
grr forms
grr forms forms get --form-id <form-id>
grr forms forms responses list --form-id <form-id>
grr forms forms watches list --form-id <form-id>
Tasks
14 methods. Task lists and the tasks inside them. Note the scope caveat: this build does not consent to the Tasks scope, so live calls can 403.
grr tasks
grr tasks tasklists list
grr tasks tasks list --tasklist @default
grr tasks tasks insert --tasklist @default --body-file ./task.json
Sheets
17 methods. Spreadsheets, values (read, write, append, batch), and developer metadata. Colon-verb paths like values/{range}:append are handled for you.
Every command takes -f/--format, including the generated leaves. There is no global --format flag: it would collide by id with per-method parameters named format.
json (default), jsonl, table, pretty. JSON is the default because the common consumer is a pipe, not a person.
jsonl emits one JSON document per line for array results, which is what you want when streaming into jq without loading everything. table renders arrays as one row per element with the union of keys as columns, a single object as a Field/Value table, and nested values as compact JSON inside a cell.
output formats
# Every command accepts -f/--format. JSON is the default.
$ grr gmail users messages list --user-id me --max-results 5 -f table
+----------+--------------------+------------------+
| ID | THREAD_ID | SNIPPET |
+----------+--------------------+------------------+
| 19f8ab2c | 19f8ab2c | Build notes ... |
+----------+--------------------+------------------+
$ grr gmail users messages list --user-id me -f jsonl
{"id":"19f8ab2c","threadId":"19f8ab2c"}
{"id":"19f8ab31","threadId":"19f8ab31"}
$ grr gmail users messages list --user-id me -f pretty
account level
The four commands that are not a service.
These sit at the top level because they are not about one API. grr schema is the one to build against.
grr auth
login runs the PKCE browser flow, or the device flow with --device. status makes a live profile call through the shared path and reports the account plus the history_id cursor. setup creates and stores your own OAuth client.
grr transport
Prints the negotiated protocol, whether HTTP/3 was requested and effective, whether it fell back, the core count, and io_uring support. Plain text, not JSON — it is a diagnostic, not data.
grr schema
Dumps the entire command tree as JSON: every subcommand, every argument, whether it is required, whether it takes a value, its default, and its possible values. Because the tree itself is generated from the index, schema is a faithful census of all 308 methods.
grr api
list, describe, call, and refresh against the embedded Discovery index — the flat escape hatch that reaches any method by id. Same index, same call path as the tree.
grr schema is the canonical reference. It is honest about its own size: roughly 290 KB of valid JSON, emitted in well under 100 ms, with zero configuration and before any OAuth exists. Generate a wrapper or an agent tool definition from it — because it is derived from the same tree the binary dispatches on, it cannot drift from the commands that actually exist.
Results on stdout, logs on stderr. The tracing subscriber is wired to stderr explicitly, so grr … | jq never receives a log line. The one stdout exception is grr auth login --device, which prints its human-readable URL and code before the final JSON.
Paging is yours to drive. The tree does not follow page tokens: --max-results is the API’s own page size, and --page-token takes the nextPageToken from the previous response. That keeps a list command from turning into an unbounded crawl.
Booleans are presence flags (--include-spam-trash), a deliberate change from value-taking booleans: a flag is either passed or absent, so a script never has to guess.
Errors are actionable. A 403 names the missing scope, an unknown method id returns ranked suggestions, a missing required parameter names itself. Non-zero exit on failure, always.
Two commands print plain text, not JSON:grr transport and the device-login line above. --dry-run output is JSON, so it pipes.
Timestamps are RFC 3339 (--time-min 2026-10-01T00:00:00Z), or a bare YYYY-MM-DD where the API accepts a date.
Where the tree comes from
Every leaf above is generated from the committed Discovery index at build time and refreshed daily by a workflow PR. The same index drives the flat grr api surface, so the census never splits in two.