# Gmail API OAuth scopes reference

Source: https://developer.nylas.com/docs/cookbook/use-cases/build/google-oauth-scopes/

Pick the wrong Gmail scope and Google makes you pay for it before launch. Ask for `gmail.modify` when you only read mail and you're routed into a CASA Tier 2 security assessment that can take weeks and cost real money. Ask for too little and the first send returns `403 insufficient authentication scopes`. This page lists the exact Gmail API OAuth scope strings Nylas requests for each email, calendar, and contacts feature, so you grant the narrowest set that still works.

For the Google and Microsoft scope strings side by side, see [OAuth scopes for email and calendar](/docs/cookbook/use-cases/build/oauth-scopes-email-calendar/). This page is the Google-only deep reference, including the verification tiers that decide your launch timeline.

## What OAuth scopes do I need for reading and sending email through the Gmail API?

Reading mail needs the `https://www.googleapis.com/auth/gmail.readonly` scope, and sending needs `https://www.googleapis.com/auth/gmail.send`. Google classifies `gmail.readonly` as restricted and `gmail.send` as sensitive, so a read-and-send app crosses both of Google's two review tiers at once. Nylas requests these exact strings when you call `GET /v3/grants/<NYLAS_GRANT_ID>/messages` and `POST /v3/grants/<NYLAS_GRANT_ID>/messages/send`.

Google scopes are full URIs prefixed with `https://www.googleapis.com/auth/`, and there's no shorter form. The table below maps each common task to the scope Nylas requests, verified against the Google tab in the [granular scopes reference](/docs/dev-guide/scopes/). The `Wider scope` column shows the broader scope Google also accepts when a feature needs write access, which the modify and folder operations require.

| Feature | Required Gmail scope | Wider scope that also works |
| --- | --- | --- |
| Read messages and attachments | `/gmail.readonly` | `/gmail.modify` |
| Update or delete messages | `/gmail.modify` | `https://mail.google.com/` (hard-delete) |
| Send email | `/gmail.send` | `/gmail.compose`, `/gmail.modify` |
| Read and write drafts | `/gmail.compose` | `/gmail.modify` |
| Manage folders (labels) | `/gmail.labels` | `/gmail.modify` |
| Read calendars and Free/Busy | `/calendar.readonly` | `/calendar` |
| Create or update events | `/calendar.events` | `/calendar` |
| Read contacts | `/contacts.readonly` | None |
| Read and write contacts | `/contacts` | None |

One classification catch: `gmail.compose` and `gmail.insert` are restricted scopes that trigger the CASA assessment, just like `gmail.readonly` and `gmail.modify`, so neither is a lighter way to dodge the restricted-scope review. `gmail.send` is sensitive and `gmail.labels` is non-sensitive. Mail, calendar, and contacts scopes are independent in Google, so a calendar-only app never requests a `gmail.*` scope. Request `gmail.send` even for scheduled delivery: the [scheduled send](/docs/v3/email/scheduled-send/) feature queues the message but still sends through your Gmail send scope.

## What are common OAuth errors when integrating Gmail API in a Node.js app?

The four errors that trip up most Node.js Gmail integrations are `access_denied`, `redirect_uri_mismatch`, `invalid_scope`, and `403 insufficient authentication scopes`. The first three surface during the consent redirect, before any grant exists, and the `403` surfaces at the first API call when the granted scopes don't cover the operation you attempted.

`access_denied` almost always means your OAuth app is still in testing mode or the requested restricted scopes aren't verified yet, which blocks any account outside your test-user list. `redirect_uri_mismatch` means the callback URL in your auth request doesn't byte-match the one registered in your Google Cloud project, down to the trailing slash. `invalid_scope` means a malformed or non-existent scope string, often a missing `https://` prefix. The `403` means you asked for `gmail.readonly` but tried to modify a label. Resolve each through [Fix Google access_denied OAuth errors](/docs/cookbook/use-cases/build/fix-google-access-denied/) and the broader [troubleshoot OAuth errors](/docs/cookbook/use-cases/build/troubleshoot-oauth-errors/) guide, which covers all four against both providers.

## What are the best practices for OAuth scope management in an email API integration?

The core practice is least privilege: request the smallest Gmail scope that covers your features, then widen only when a feature needs it. Restricted scopes like `gmail.readonly` and `gmail.modify` each trigger Google's annual security assessment, so dropping one unused restricted scope can save weeks of review and remove an audit cost from your launch plan.

Three habits keep a Gmail integration clean. First, separate read from write: use `gmail.readonly` over `gmail.modify` wherever your app never edits or deletes mail, since the read scope alone often satisfies the assessment with a lighter review. Second, use incremental authorization, requesting send access the first time a user clicks "send" rather than on day one, which raises consent conversion. Third, set scopes on the Nylas connector once rather than per call, so the hosted flow at `GET /v3/connect/auth` always sends the same vetted set. The table in the next section maps these habits to concrete app shapes.

## Choose the least-privilege Gmail scope set

Request the smallest scope that covers your features, then add scopes only when a feature needs them. This keeps Google off the restricted-scope assessment track where possible, since fewer restricted scopes mean a shorter review. The table maps 5 common app types to the minimal Gmail scopes each needs, from a single restricted scope for a read-only inbox up to four scopes for a mail-plus-calendar app.

| App type | Gmail and Google scopes |
| --- | --- |
| Read-only inbox | `/gmail.readonly` |
| Send-only | `/gmail.send` |
| Full email client | `/gmail.modify`, `/gmail.send` |
| Calendar scheduler | `/calendar.events` |
| Mail plus calendar | `/gmail.modify`, `/gmail.send`, `/calendar.events` |

Each row maps to a real product shape. A read-only inbox that classifies or indexes mail needs only `gmail.readonly`, because it never writes or sends, though that one scope still triggers a restricted-scope review. A full email client that marks messages read, moves them between labels, and sends replies needs `gmail.modify` for the modify and label operations plus `gmail.send` for outbound mail. A calendar scheduler that creates and updates events needs `calendar.events` and nothing from the `gmail.*` family. Drop scopes you don't call, because every extra restricted scope lengthens the assessment.

## How do I set Gmail scopes on the auth request?

You set Gmail scopes on the Nylas connector or pass them to the hosted OAuth URL, not on each API call. The hosted flow at `GET /v3/connect/auth` sends the connector's scopes to Google, the user consents once, and the code exchanges for a grant at `POST /v3/connect/token`. Scope strings are space-separated in the `scope` query parameter.

The request below starts the hosted Google OAuth flow for a read-and-send app. It lists three Gmail and calendar scopes plus the offline access Google needs to return a refresh token, so the grant survives past the first access-token expiry. Always include the refresh-token request, or the grant stops working after about 60 minutes.

```bash
GET https://api.us.nylas.com/v3/connect/auth?
  client_id=<NYLAS_CLIENT_ID>
  &redirect_uri=https://myapp.com/callback
  &response_type=code
  &provider=google
  &access_type=offline
  &scope=https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/gmail.send https://www.googleapis.com/auth/calendar.events
```

After the user consents, Google redirects to your callback with a `code`. Exchange it for a grant, then read mail through the unified endpoint. The call below lists messages from the connected Google account using the `grant_id` the token exchange returned, with a `limit` of 50 messages per page.

```bash
curl --request GET \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages?limit=50' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

## When should you call the Gmail API directly?

Call the Gmail API directly when your app is Google-only and depends on Gmail-specific features the unified schema doesn't surface, such as raw RFC 822 history syncs through `users.history.list` or fine-grained label-color management. Google's own client handles those edge cases natively, and a single-provider app pays no penalty for skipping the abstraction layer.

The calculation flips the moment you add a second provider. Supporting Outlook alongside Gmail through native APIs means a second, unrelated integration against Microsoft Graph, with its own consent model, ID formats, and throttling rules. One unified schema collapses both into a single code path, and the same grant reaches 6 providers, so adding iCloud or Yahoo later costs no new integration. The unified read call `GET /v3/grants/<NYLAS_GRANT_ID>/messages` returns the same JSON shape for every provider, which is the difference from mapping each provider's response by hand.

| Task | Gmail API directly | Unified API with Nylas |
| --- | --- | --- |
| **Auth setup** | GCP project, OAuth consent screen, verification | 1 connector, OAuth handled for you |
| **Read mail** | `users.messages.list` with `gmail.readonly` | `GET /v3/grants/<NYLAS_GRANT_ID>/messages` |
| **Send mail** | `users.messages.send` with `gmail.send` | `POST /v3/grants/<NYLAS_GRANT_ID>/messages/send` |
| **Restricted-scope review** | You manage CASA assessment yourself | Same scopes, one consent flow |
| **Other providers** | Google only | Microsoft, iCloud, Yahoo, IMAP, and more |

## Things to know about Gmail scopes

Beyond the read-versus-write split and the verification tiers covered above, a few Google-specific details change when these scopes take effect. Each of the four items below maps to a real failure mode developers hit during Gmail integration, and getting them wrong is a common reason a working integration starts returning `403`.

- **Adding a scope forces re-authentication.** Google only grants new scopes at consent time. Add `gmail.send` after a user already connected with `gmail.readonly` and their existing grant doesn't gain it. The user re-authenticates through `GET /v3/connect/auth` to pick up the wider access.
- **Restricted scopes require an annual assessment.** `gmail.readonly`, `gmail.modify`, `gmail.compose`, `gmail.insert`, and `https://mail.google.com/` sit in Google's restricted tier, which adds a yearly CASA security assessment on top of OAuth verification. `gmail.send` is a sensitive scope that needs OAuth verification but not the restricted-scope assessment, and `gmail.labels` is non-sensitive. Plan several weeks of lead time before production launch, not days.
- **Contacts sources change the scope.** The `contacts.readonly` scope covers the personal address book, but the `inbox` and `domain` contact sources need `contacts.other.readonly` and `directory.readonly`. Request those only when you read recent-correspondent or directory contacts.
- **Hard-delete needs the widest scope.** A `DELETE` on a message under `gmail.modify` moves it to Trash. Permanently deleting requires the full `https://mail.google.com/` scope, which Google reviews most strictly of all.

The canonical list lives in the [Google OAuth 2.0 scopes reference](https://developers.google.com/identity/protocols/oauth2/scopes). Cross-check any scope against it and the [Gmail API scopes page](https://developers.google.com/workspace/gmail/api/auth/scopes) before you add it, because Google renames and re-tiers scopes over time.

## What's next

- [OAuth scopes for email and calendar](/docs/cookbook/use-cases/build/oauth-scopes-email-calendar/) for the Google and Microsoft scope strings side by side
- [Fix Google access_denied OAuth errors](/docs/cookbook/use-cases/build/fix-google-access-denied/) when users hit the app-not-verified wall
- [Troubleshoot OAuth errors](/docs/cookbook/use-cases/build/troubleshoot-oauth-errors/) for redirect_uri_mismatch, invalid_scope, and 403 fixes
- [Granular scopes reference](/docs/dev-guide/scopes/) for required and optional scopes per Nylas API endpoint