DEV Community

Drew-Opexcell
Drew-Opexcell

Posted on • Originally published at opexcell.com

Why your Clio token stopped working

If your Clio integration started returning 401 and you are trying to work out why, the first question is not about your code. It is which of Clio's two OAuth systems you are on, because they have different rules and most advice on the internet does not say which one it is describing.

There are two, and they are not interchangeable

Clio Manage is the older one. OAuth at app.clio.com/oauth, API at app.clio.com/api/v4.

Clio Platform is the newer one, covering Grow and the lead inbox among others. OAuth at auth.api.clio.com/oauth, API at api.clio.com.

They differ on essentially every point that matters when a token dies.

Manage Platform
Access token lifetime 2,592,000s, 30 days 86,400s, 24 hours
Refresh token expiry Documented as none No time expiry, but rotates
Refresh token rotation Not documented, and the refresh sample returns no new refresh token Documented: rotates on every use, previous one revoked
Revocation endpoint POST app.clio.com/oauth/deauthorize, Bearer auth POST auth.api.clio.com/oauth/revoke, Basic auth

Treat the lifetime row as the documented default rather than a constant. Honour the expires_in you get back on each response instead of hardcoding 30 days, because there are field reports of accounts issuing much shorter access tokens, and a hardcoded assumption fails in a way that looks exactly like revocation.

That rotation row is the one that decides your debugging. On Platform, every refresh gives you a new refresh token and kills the old one, so failing to persist the new value out of each response leaves you holding a dead token the next time you try. Clio's docs say it directly: store the new refresh token returned in each response.

On Manage, the documented behaviour is a long-lived refresh token that does not expire and is not replaced. Their refresh response sample does not even include a refresh_token field. So on Manage, a token that suddenly stops working usually points somewhere else: revocation, or the wrong region.

The region trap

Clio runs four separate instances, and this is the one that turns a five minute fix into an afternoon because nothing about the error says "region."

Region Manage API Platform OAuth
US app.clio.com/api/v4 auth.api.clio.com
EU eu.app.clio.com/api/v4 eu.auth.api.clio.com
Canada ca.app.clio.com/api/v4 ca.auth.api.clio.com
Australia au.app.clio.com/api/v4 au.auth.api.clio.com

Clio's documentation states that a token issued in one region is not valid against another region's API. So if you built against app.clio.com because that is what every tutorial uses, and you have just onboarded a firm on the Australian instance, nothing is wrong with the token. You are asking the wrong server.

Do not stop at swapping the hostname, because it will fail again. Clio requires a separate developer application in each region, which means a separate client_id and client_secret per region as well. Supporting a second region is a registration exercise, not a config change, and finding that out one failure at a time is the slow way.

Revocation is asymmetric, and it matters

This is the detail most likely to explain a chain that died without anyone touching the code.

Clio's Manage documentation draws a distinction that is easy to skim past: when the user revokes access to your application from inside Clio, both access tokens and refresh tokens for that user are deauthorized. When your application requests the deauthorization, only the supplied access token is deauthorized.

So a firm administrator clicking "disconnect" in their Clio settings takes out your refresh token too, permanently, and your integrations will fail on their next call with nothing in your logs explaining why. Your own deauthorize call does something much narrower.

Clio also offers a deauthorization callback URL you can set on the app, which POSTs client_id, user_id and access_token when access is revoked, and sends the literal string all for the access token when the user initiated it. If you run anything in production against Clio, set that callback.

There is no API path back

Once a grant is revoked, the documented recovery is that the user re-authorizes through the browser. There is a deauthorize endpoint for ending access and nothing for restoring it.

Recovery requires a specific human with access to the Clio account, in a browser, doing an OAuth flow. If that person is in a hearing, your integrations stay down until they are out. Two things worth having ready before you need them: a written re-authorization procedure that somebody other than the original developer can follow, including the exact scopes to request, because re-authorizing with fewer scopes brings half your integrations back in a subtler kind of broken; and alerting that fires on the token failure itself rather than on the downstream symptoms.

What we run, and what it is for

Several scheduled jobs sharing one stored Clio connection is a fragile shape whichever system you are on, because a rotating system punishes any two jobs that refresh at once, and every system punishes a stale cached token.

Two measures, and they work together. Refresh proactively on a schedule so nothing is ever near expiry and on-demand refreshes almost never fire. And before any worker calls the token endpoint, have it re-read the stored token and stand down if a sibling already refreshed, using the persisted refresh token rather than whatever it cached at startup. That second detail matters more than it looks: a long-running process holding a token it read hours ago is exactly the thing that submits a stale one.

If you want a hard guarantee rather than a small probability, a database advisory lock around the refresh gives you a single writer. We have not needed it on top of the two measures above.

One diagnostic worth keeping: if every integration fails at the same timestamp, that is a shared-credential problem, not five coincidences. It rules out a lot quickly.


Lifetimes, rotation behaviour, region hosts and the revocation asymmetry above are from Clio's developer documentation for Clio Manage and Clio Platform, checked July 2026. Clio does not publish the error body its token endpoint returns for an invalid refresh token, so this page does not guess at one. The refresh pattern in the last section is what we run in production. If Clio changes any of this, believe their docs over this page.

Top comments (0)