Updating records in Zoho requires a valid, active OAuth connection with the correct scopes — once those are in place, the update flow handles token refresh automatically and writes changes back to CRM or Desk.
Why this matters
Whether you're syncing contact details, closing tickets, or modifying lead data, programmatic record updates depend on a live OAuth token and the right permission scopes. If your token has expired or your scopes are too narrow, update calls will silently fail or return 401 errors. Understanding how the connection layer works helps you diagnose and fix these issues quickly.
> Note: Beam Help is independent expert support for Zoho — we are not official Zoho support.
---
Step-by-step
Step 1. Confirm your OAuth connection exists and holds a valid token.
Before any record update can succeed, the system must find a stored connection for your user. The connection is looked up from a local zohoconnections table using your userid. If no row is found, the API client cannot be initialised and all subsequent calls will fail.[3]
Step 2. Allow the token refresh cycle to run.
Tokens are refreshed proactively when the current time is within 120 seconds of the stored tokenexpiresat value. A successful refresh writes the new accesstoken and updated tokenexpiresat back to the zohoconnections row, so the next call always uses a fresh credential.[3] If the refresh itself fails (for example, the refresh_token is missing or revoked), the function returns None and no update will proceed.[1]
Step 3. Verify the correct OAuth scopes are granted.
For Zoho CRM record updates, your OAuth grant must include ZohoCRM.modules.ALL or the equivalent module-level UPDATE scope. For Zoho Desk, the relevant scopes include Desk.tickets.UPDATE, Desk.contacts.UPDATE, Desk.tasks.UPDATE, and similar per-object UPDATE permissions.[5] If these scopes were not requested during the original OAuth authorisation, you will need to re-authorise and explicitly include them.
Step 4. Initialise the correct API client for your product.
Pass apptype="crm" to target Zoho CRM, or apptype="desk" to target Zoho Desk. For Desk, an orgid is also required — if it is not already stored, the system will call getallorganizations to discover and persist it automatically.[1] For CRM, a ZohoCrmClient is constructed using the stored apidomain and access_token, with the token refresher callback wired in.[4]
Step 5. Execute the update tool with the target record ID and changed fields.
Once the API client is ready, call execute_tool with the appropriate update tool name and a params object that includes the record id and the fields you want to change. The execution layer will attempt the call, and if a clarifying question or error is returned in the result, that message is surfaced directly so you can correct the payload and retry.[8]
Step 6. Confirm the connection metadata is current after an auth callback.
If you recently re-authorised, check that the auth callback correctly wrote the latest accesstoken, refreshtoken, apidomain, and tokenexpiresat into zohoconnections. The callback performs an upsert — updating the existing row if one is found, or inserting a new one otherwise — and also stores crmorgid for CRM tenancy.[2]
---
Common pitfalls
- Missing
orgidfor Desk calls. Ifdeskorg_idis blank or whitespace, the client is still constructed but the first API call triggers an auto-discovery lookup. If that lookup also fails (e.g., due to a scope gap onDesk.basic.READ), all subsequent Desk updates will error.[1] - Token refresh returning no
accesstoken. IfZohoOAuth.refreshtokensresponds without anaccess_tokenkey, the refresher returnsNoneand the stale token remains in use, causing 401s on update calls.[1][3] - Scope gaps from the original grant. Scopes are set at authorisation time. Adding
UPDATEscopes to your config file does not retroactively update an existing grant — the user must complete a fresh OAuth flow.[5] - SQLite locking during concurrent updates. Backfill or migration operations that update
zoho_connectionsmust use a single connection/transaction to avoid locking conflicts.[6]
---
What to check
- Confirm the
zohoconnectionsrow for youruseridhas a non-nullrefreshtokenand thattokenexpires_atis a valid Unix timestamp.[3] - Verify your OAuth grant includes the relevant
UPDATEscope for the module you are targeting (e.g.,ZohoCRM.modules.ALLfor CRM,Desk.tickets.UPDATEfor Desk tickets).[5] - After any re-authorisation, check that the
apidomainstored inzohoconnectionscontainszohoapis.so the data centre is correctly inferred for subsequent API calls.[2]