AI has stopped being a buzzword. It is now quietly embedded in the way many of us work every single day. But most AI assistants are locked out of the actual data that lives in your daily tools β your Gmail inbox, your Google Calendar, your contact list. A CLI that speaks directly to Google Workspace APIs gives both humans and AI agents a consistent, scriptable interface. This article shows how to search Google Contacts by name using the gws CLI.
Why This Matters: AI Is Now Part of Our Daily Workflow
AI has stopped being a buzzword. It is now quietly embedded in the way many of us work every single day β drafting emails, summarising meetings, suggesting calendar slots, finding files we half-remember, answering questions about people we are about to meet. Tools like ChatGPT, Claude, Gemini, and GitHub Copilot are no longer novelties we demo at conferences; they are open in a browser tab while we work.
But there is a gap. Most AI assistants are great at reasoning β synthesising information, writing, planning β yet they are locked out of the actual data that lives in your daily tools. Your Gmail inbox, your Google Calendar, your Drive files, your contact list: all of that sits behind APIs that AI agents cannot reach unless you explicitly connect them.
That gap is closing fast. A new generation of agentic AI workflows β where an AI model can not just answer questions but take actions β depends entirely on being able to call those APIs programmatically. An AI assistant that can look up a contact, check a calendar, read a document, and send a message based on a single natural-language request is dramatically more useful than one that can only generate text.
This is where the command line becomes a superpower.
A CLI that speaks directly to Google Workspace APIs gives both humans and AI agents a consistent, scriptable interface to these services. You can embed it in automation pipelines, call it from agent tools, chain it with jq and shell scripts, or simply use it to understand exactly what the underlying APIs do β knowledge that transfers directly to building AI-powered integrations.
This article focuses on one concrete example: searching Google Contacts by name. It is a small, practical workflow β but the same principles (authentication, scope management, troubleshooting) apply to every Google Workspace service you might want to wire into an AI agent or automation.
What is GWS?
gws is an open-source command-line interface for Google Workspace, maintained by Google. It lets you interact with Gmail, Drive, Calendar, Sheets, Docs, Contacts, Chat, and a dozen other Google services β all from your terminal, without writing a single line of code.
Under the hood, gws is built in Rust and reads the Google Discovery Service at runtime, which means it always reflects the latest Google API surface without requiring you to update the tool. Each command maps directly to a Google REST API endpoint, and you pass parameters as plain JSON.
Why use GWS?
- No SDK boilerplate β skip OAuth plumbing, token management, and API client setup
- Scriptable β all output is structured JSON, pipe it into
jqor any other tool - Cross-service workflows β combine Gmail, Drive, Calendar and Contacts in a single shell script
- Dry-run mode β preview the exact HTTP request before it hits Google's servers
- Works everywhere β Windows (PowerShell / CMD), macOS, and Linux
Installation
npm install -g @googleworkspace/cli
Verify the install:
gws --version
One-time setup
gws uses OAuth 2.0. You need a Google Cloud project with an OAuth 2.0 Desktop client to authenticate. The built-in setup wizard walks you through it:
gws auth setup
gws auth login
After login, credentials are stored locally at ~/.config/gws/ (or %USERPROFILE%\.config\gws\ on Windows). Nothing is sent to any third-party server.
Prerequisites
Authentication with the correct scope
Contact search requires the https://www.googleapis.com/auth/contacts scope. By default, gws auth login (with no flags) only grants scopes for Drive, Gmail, Calendar, Sheets, Docs, Slides, and Tasks β Contacts is not included.
Understanding -s vs --scopes
gws auth login has two flags that sound similar but behave very differently:
| Flag | What it does |
|---|---|
-s / --services |
A filter for the interactive TUI scope picker. Opens a terminal UI where you select scopes. Whatever you select (or don't select) in that UI is what gets saved. |
--scopes |
Bypasses the TUI entirely. Directly requests the exact full scope URLs you provide. |
This distinction matters: running gws auth login -s people launches the interactive picker filtered to People-related scopes β but if you just press Enter without carefully selecting everything, you may end up with fewer scopes than intended (or only the default cloud-platform scopes).
The correct command to get all scopes including Contacts
Use --scopes with explicit full URLs so there is no ambiguity:
# PowerShell / bash / zsh β run this once for full access to all common services
gws auth logout
gws auth login --scopes "https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/gmail.modify,https://www.googleapis.com/auth/calendar,https://www.googleapis.com/auth/spreadsheets,https://www.googleapis.com/auth/documents,https://www.googleapis.com/auth/presentations,https://www.googleapis.com/auth/tasks,https://www.googleapis.com/auth/contacts"
Then clear the token cache so the new scopes take effect immediately:
# PowerShell
Remove-Item "$env:USERPROFILE\.config\gws\token_cache.json" -ErrorAction SilentlyContinue
# bash / zsh / macOS
rm -f ~/.config/gws/token_cache.json
Verify the saved scopes include contacts:
gws auth status
Look for https://www.googleapis.com/auth/contacts in the scope list.
Searching Contacts by Name
Command
gws people people searchContacts --params '<JSON>'
Required --params fields
| Field | Type | Description |
|---|---|---|
query |
string | Name, email, or phone fragment to search for |
readMask |
string | Comma-separated list of fields to include in the response |
Shell-specific syntax
The JSON value must be quoted correctly for your shell.
PowerShell β single quotes outside, backslash-escaped inner quotes:
gws people people searchContacts --params '{\"query\": \"Angelina\", \"readMask\": \"names,emailAddresses,phoneNumbers\"}'
CMD β double quotes outside, backslash-escaped inner quotes:
gws people people searchContacts --params "{\"query\": \"Angelina\", \"readMask\": \"names,emailAddresses,phoneNumbers\"}"
bash / zsh β single quotes, standard JSON inside:
gws people people searchContacts --params '{"query": "Angelina", "readMask": "names,emailAddresses,phoneNumbers"}'
Dry-run first
Use --dry-run to confirm the request URL and params without making an API call:
gws people people searchContacts --params '{\"query\": \"Angelina\", \"readMask\": \"names,emailAddresses,phoneNumbers\"}' --dry-run
A successful dry-run prints the HTTP method, URL, and resolved parameters β confirming your quoting is correct before any real network call is made.
Available readMask fields
addresses, ageRanges, biographies, birthdays, calendarUrls, clientData, coverPhotos, emailAddresses, events, externalIds, genders, imClients, interests, locales, locations, memberships, metadata, miscKeywords, names, nicknames, occupations, organizations, phoneNumbers, photos, relations, sipAddresses, skills, urls, userDefined
Other search options
| Goal | Command |
|---|---|
| Search workspace directory (not personal contacts) | gws people people searchDirectoryPeople --params '{"query": "Angelina", "readMask": "names,emailAddresses", "sources": ["DIRECTORY_SOURCE_TYPE_DOMAIN_CONTACT"]}' |
| Search "Other contacts" (people you have emailed) | gws people otherContacts search --params '{"query": "Angelina", "readMask": "names,emailAddresses"}' |
| List all personal contacts | gws people people connections list --params '{"resourceName": "people/me", "personFields": "names,emailAddresses", "pageSize": 20}' |
Troubleshooting
Problem: JSON parse error / "key must be a string"
Cause: Shell interpreted the inner double-quote characters before passing them to gws.
Fix: Use the correct quoting for your shell β see the Shell-specific syntax section above. Test with --dry-run to validate quoting without making an API call.
Problem: 403 insufficient authentication scopes
This error has two distinct root causes. Work through them in order.
Root cause A β contacts scope was never saved
This happens when gws auth login -s people was used but the interactive TUI did not actually select the contacts scope (e.g. you pressed Enter too quickly, or selected only cloud-platform). Check gws auth status β if https://www.googleapis.com/auth/contacts is not in the scope list, you need to re-authenticate with the correct flag.
Fix: Use --scopes (not -s) so the exact URLs are saved without any TUI interaction:
gws auth logout
gws auth login --scopes "https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/gmail.modify,https://www.googleapis.com/auth/calendar,https://www.googleapis.com/auth/spreadsheets,https://www.googleapis.com/auth/documents,https://www.googleapis.com/auth/presentations,https://www.googleapis.com/auth/tasks,https://www.googleapis.com/auth/contacts"
Root cause B β stale token cache
This happens when the contacts scope is present in the credentials file (visible in gws auth status) but the API still returns 403. The cached access token was minted before the new scope was added and is still being served.
Symptoms:
gws auth statusshowstoken_valid: trueandtoken_cache_exists: truecontactsscope is listed in the credentials- Retry still returns
403 insufficient authentication scopes
Fix (PowerShell):
# Delete the stale cached token β gws will mint a fresh one on next call
Remove-Item "$env:USERPROFILE\.config\gws\token_cache.json" -ErrorAction SilentlyContinue
gws people people searchContacts --params '{\"query\": \"Angelina\", \"readMask\": \"names,emailAddresses,phoneNumbers\"}'
Fix (bash / zsh / macOS):
rm -f ~/.config/gws/token_cache.json
gws people people searchContacts --params '{"query": "Angelina", "readMask": "names,emailAddresses,phoneNumbers"}'
If neither fix works, do a complete re-auth cycle using --scopes as shown in Root cause A above.
Problem: 403 accessNotConfigured
Cause: The People API is not enabled in the GCP project that owns the OAuth client.
Fix: Open the GCP Console β APIs & Services β Library β search "People API" β Enable. The error JSON from gws also contains an enable_url you can open directly.
Problem: redirect_uri_mismatch during login
Cause: The OAuth client type is not "Desktop app".
Fix: GCP Console β APIs & Services β Credentials β delete the existing client β create a new OAuth 2.0 Client ID of type Desktop app β re-run gws auth setup.
Problem: "Access blocked" / 403 on the browser consent screen
Cause: Your account is not listed as a test user on the OAuth consent screen.
Fix: GCP Console β APIs & Services β OAuth consent screen β Test users β + Add users β enter your Google account email.
Scope Reference
| Service | Login flag | Key scope granted |
|---|---|---|
| People / Contacts | -s people |
https://www.googleapis.com/auth/contacts |
| Gmail | -s gmail |
https://www.googleapis.com/auth/gmail.modify |
| Drive | -s drive |
https://www.googleapis.com/auth/drive |
| Calendar | -s calendar |
https://www.googleapis.com/auth/calendar |
| Sheets | -s sheets |
https://www.googleapis.com/auth/spreadsheets |
| Chat | -s chat |
https://www.googleapis.com/auth/chat.messages |
Multiple scopes can be combined in one login:
gws auth login -s people,gmail,drive
π¬ Comments & Reactions