Published: June 23, 2026 | Category: Zoho Expense, API & Integrations, Troubleshooting
You’ve set up your MCP server, pointed it at Zoho Expense, clicked Authorize — and hit a wall:
Invalid OAuth Scope
No stack trace. No line number. Just a cryptic rejection from Zoho's authorization server.
The good news: this error has a finite set of causes, and every one of them is fixable. This guide walks you through each one in order of likelihood, with exact scope strings pulled directly from Zoho Expense's official API documentation.
Quick orientation: If you’ve already ruled out region issues (
.comUS endpoint confirmed), skip straight to Solution 1 — scope formatting is the #1 culprit.
Zoho's OAuth 2.0 authorization server validates every scope string character-by-character before issuing a token. An Invalid OAuth Scope error means the server rejected your request before authentication even began.
The most common causes, ranked by frequency:
1. Scope string is misspelled or uses wrong resource names
2. Scopes aren’t enabled in the Zoho API Console for your Client ID
3. Wrong OAuth client type (Server-based vs. Self-Client)
4. Wrong delimiter between multiple scopes
5. Scope casing doesn’t match Zoho's exact specification
6. Requesting scopes unavailable on your current Zoho Expense plan
Work through these in order. Most developers resolve the issue at step 1 or 2.
This is the most common cause of Invalid OAuth Scope errors — and the easiest to overlook because the mistakes are subtle.
All Zoho OAuth scopes follow this exact three-part structure:
service_name.scope_name.operation_type
For Zoho Expense specifically:
ZohoExpense.{scope_name}.{OPERATION_TYPE}These are pulled directly from Zoho Expense API Authentication docs:
ZohoExpense.fullaccess.ALL
ZohoExpense.expensereport.READ
ZohoExpense.expensereport.CREATE
ZohoExpense.expensereport.UPDATE
ZohoExpense.expensereport.DELETE
ZohoExpense.approval.CREATE
ZohoExpense.reimbursement.CREATE
ZohoExpense.advance.CREATE
ZohoExpense.advance.UPDATE
ZohoExpense.advance.DELETE
ZohoExpense.users.READ
ZohoExpense.users.CREATE
ZohoExpense.users.UPDATE
ZohoExpense.users.DELETE
ZohoExpense.orgsettings.READ
ZohoExpense.orgsettings.CREATE
ZohoExpense.orgsettings.UPDATE
ZohoExpense.orgsettings.DELETE
⚠️ Critical:
settingsis not a valid scope name. The correct resource name isorgsettings. This is one of the most common copy-paste errors.
# Wrong casing on service name
zohoexpense.fullaccess.ALL ❌
ZOHOEXPENSE.fullaccess.ALL ❌
# Wrong casing on operation type
ZohoExpense.fullaccess.all ❌ (must be uppercase: ALL)
ZohoExpense.expensereport.read ❌ (must be uppercase: READ)
# Wrong resource name
ZohoExpense.reports.READ ❌ (correct: expensereport)
ZohoExpense.expense.READ ❌ (correct: expensereport)
ZohoExpense.settings.READ ❌ (correct: orgsettings)
# Wrong delimiters
ZohoExpense/fullaccess/ALL ❌
ZohoExpense:fullaccess:ALL ❌
# Missing operation type
ZohoExpense.expensereport ❌
# Wrong operation type name
ZohoExpense.fullaccess.WRITE ❌ (use CREATE or ALL)
When requesting multiple scopes, separate them with commas and no spaces:
# ✅ Correct
scope=ZohoExpense.expensereport.READ,ZohoExpense.users.READ
# ❌ Wrong — space separator
scope=ZohoExpense.expensereport.READ ZohoExpense.users.READ
# ❌ Wrong — URL-encoded space
scope=ZohoExpense.expensereport.READ%20ZohoExpense.users.READ
.env, config.json, or mcp_config.yaml)scopes fieldexpensereport (not reports), orgsettings (not settings), and ALL/READ/CREATE in uppercaseEven with a perfectly formatted scope string in your MCP config, the authorization will fail if those scopes aren’t properly configured in the Zoho Developer Console.
1. Go to: https://api-console.zoho.com/
2. Select the OAuth Client tied to your MCP Client ID
3. Review the client configuration and scope settings
4. Ensure ALL scopes your MCP server requests are valid for this client
5. If using Self-Client: regenerate the grant token with the corrected scope string
The type of OAuth client you created matters significantly for MCP server integrations:
| Client Type | Best For | Scope Behavior |
|---|---|---|
| Self-Client | Scripts, local tools, MCP servers | Scopes declared when generating grant token |
| Server-based Application | Web apps with redirect URI | Scopes set at authorization time |
| Installed Application | Desktop apps | Scopes declared in auth request |
MCP servers should use Self-Client. If you created a Server-based Application client for an MCP server, this mismatch alone can trigger scope validation errors. The Self-Client flow generates a one-time grant token with your specified scopes, which you then exchange for a long-lived refresh token — exactly the pattern MCP servers need.
If the client was misconfigured from the start, the cleanest resolution is to rebuild it correctly rather than patch an existing broken setup.
1. Visit: https://api-console.zoho.com/
2. Archive or delete the existing OAuth client
3. Create a NEW client:
→ Type: Self-Client (required for MCP/local server use)
4. In the Self-Client "Generate Code" tab:
→ Enter your scope string exactly as verified above
→ Set Time Duration (3–10 minutes is typical)
→ Copy the generated grant token immediately
5. Exchange the grant token for access + refresh tokens via POST:
→ URL: https://accounts.zoho.com/oauth/v2/token
→ Params: grant_type, client_id, client_secret, redirect_uri, code
6. Store the refresh token securely in your MCP server config
7. Update Client ID + Client Secret in your MCP configuration
8. Re-attempt authorization
Before spending more time debugging your MCP layer, test whether the issue is in your MCP server or in Zoho itself. Bypass the MCP UI entirely and test the raw OAuth URL in a browser.
https://accounts.zoho.com/oauth/v2/auth
?response_type=code
&client_id=YOUR_CLIENT_ID
&scope=ZohoExpense.fullaccess.ALL
&redirect_uri=YOUR_REDIRECT_URI
&access_type=offline
https://accounts.zoho.com/oauth/v2/auth?response_type=code&client_id=YOUR_CLIENT_ID&scope=ZohoExpense.fullaccess.ALL&redirect_uri=https://localhost&access_type=offline
Interpreting the result:
| Result | Diagnosis |
|---|---|
| Authorization screen appears ✅ | Problem is in MCP server's scope config — revisit Solution 1 |
| "Invalid OAuth Scope" error ❌ | Problem is in Zoho API Console setup — revisit Solution 2 or 3 |
This single test cuts your debugging time in half by isolating exactly which layer is broken.
Certain API operations are only available on specific Zoho Expense subscription tiers. Requesting a scope for a feature your plan doesn’t include can trigger scope rejection.
ZohoExpense.fullaccess.ALL → Verify against your active plan
ZohoExpense.reimbursement.CREATE → May require specific plan tier
ZohoExpense.orgsettings.UPDATE → Admin-level; plan-restricted in some cases
Tip: Start with
ZohoExpense.expensereport.READalone — this is available on all plans. If this single scope works, you can incrementally add others to identify which one is plan-restricted.
If your scope strings are correct and your API Console is properly configured, the issue may be in how your MCP framework passes scopes to the OAuth flow. Format matters here — some implementations fail silently when scopes are passed as an array instead of a comma-separated string.
// config.json style
{
"zoho_expense": {
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"scopes": "ZohoExpense.fullaccess.ALL",
"region": "com"
}
}
// mcp_config.yaml style
zoho_expense:
auth:
scopes: "ZohoExpense.fullaccess.ALL"
region: "com"
// .env style
ZOHO_SCOPES=ZohoExpense.expensereport.READ,ZohoExpense.users.READ
ZOHO_REGION=com
⚠️ Array vs. String: If your MCP framework accepts scopes as a JSON array (
["ZohoExpense.expensereport.READ", "ZohoExpense.users.READ"]), verify that the framework correctly joins them with commas before passing to Zoho's authorization endpoint. A space-joined array will fail every time.
Follow this sequence in order. Most cases resolve by Step 2.
Step 1 → Verify scope string format against the verified list in Solution 1
↓ Still failing?
Step 2 → Check Zoho API Console — confirm client type is Self-Client
↓ Still failing?
Step 3 → Test raw OAuth URL manually in browser (Solution 4)
↓ Raw URL also fails?
Step 4 → Recreate OAuth client from scratch as Self-Client (Solution 3)
↓ Raw URL works but MCP fails?
Step 5 → Fix MCP config scope format — array vs. string issue (Solution 6)
↓ Still failing after all above?
Step 6 → Check Zoho Expense plan for scope availability (Solution 5)
| Scope Group | Valid Scope Strings |
|---|---|
| Full Access | ZohoExpense.fullaccess.ALL |
| Expense Reports | ZohoExpense.expensereport.READ, .CREATE, .UPDATE, .DELETE |
| Approvals | ZohoExpense.approval.CREATE |
| Reimbursements | ZohoExpense.reimbursement.CREATE |
| Advances | ZohoExpense.advance.CREATE, .UPDATE, .DELETE |
| Users | ZohoExpense.users.READ, .CREATE, .UPDATE, .DELETE |
| Org Settings | ZohoExpense.orgsettings.READ, .CREATE, .UPDATE, .DELETE |
Source: Zoho Expense API Authentication Documentation
| Resource | URL |
|---|---|
| Zoho Expense OAuth Authentication | https://www.zoho.com/expense/api/v1/authentication/ |
| Zoho API Console | https://api-console.zoho.com/ |
| Zoho Expense API Docs | https://www.zoho.com/expense/api/v1/ |
| Zoho OAuth 2.0 Guide | https://www.zoho.com/accounts/protocol/oauth.html |
| Zoho Expense Pricing | https://www.zoho.com |