A Zorlya access token lasts 1 hour and a refresh token 30 days, and every refresh replaces both. Scopes only narrow, and a person, a client or an account change can end a connection at any time. Read this before you store tokens.
The lifetimes
| Item | Lasts | Notes |
|---|---|---|
| Authorization code | 60 seconds | Works once |
| Connect request | 10 minutes | The time a person has to finish the Connect page and the sign-in after you send them. It works once |
| Access token | 1 hour | Sent on every call |
| Refresh token | 30 days | Rotates every time it is used |
Every token is for one audience, the resource it was issued for: https://mcp.zorlya.com. The MCP server refuses a token for any other audience, and a refresh cannot move a grant to another one. Tokens also carry the account type (job seeker, hiring, support or staff), the role and, for hiring, the company.
Zorlya stores only a SHA-256 digest of each code and token. A database dump cannot be used to present any of them, and the server cannot show you a token it issued earlier. Lose a token and you sign in again.
Refresh tokens rotate
Trade a refresh token for a new pair:
curl -s -X POST https://api.zorlya.com/oauth/token \
-d grant_type=refresh_token \
-d "refresh_token=$REFRESH_TOKEN" \
-d "client_id=$CLIENT_ID"
Send this to api.zorlya.com, the authorization server. The MCP host does not serve /oauth/*.
The answer has a new access_token and a new refresh_token. Three things follow, and each one was checked against a local server:
- The old refresh token is dead at once. Using it again answers
invalid_grantwith “refresh token does not exist”. - The old access token is dead too. A call with it answers
401straight after the refresh, even though its hour had not run out. - Store the new pair before you use it. If your process stops between the answer and the write, you hold two dead tokens and the person must sign in again. Refreshing from two processes at the same time has the same effect, so refresh from one place.
A refresh also resets the 30 day clock, so a client that refreshes at least once a month stays signed in.
Scopes only narrow
Scope names are area:action, such as applications:write. The action is read, write or delete. zorlya:read, zorlya:write and zorlya:delete are umbrellas: each means every area of that action that the account may use. What the Zorlya MCP server offers lists the areas.
Seven controls decide what a connection gets, and it gets what all of them allow:
- An administrator’s switch for each action (read, write, delete) in each app. Delete starts off.
- An administrator’s switch for each area.
- The ceiling set by your AI app’s entry on Zorlya’s list.
- The person’s choice on the Connect page: Read only, Read and change, or Read, change and delete once a delete tool exists. The person can only lower what you asked for.
- The areas the person switched off for this one connection, on the Connect page (Advanced: choose areas) or later with Lower access on
mcp.zorlya.com/connections. A tool in a switched-off area is not listed, and a call to it is refused as not available. This control only removes tools, and an area comes back only when the person connects again. - The account’s own rights, checked on every call. Leaving a company or losing a staff role ends access at once.
- Per-call safeguards: separate rate limits for read, write and delete, and a request log.
A grant carries the scopes that come out of that. On refresh you may send scope to ask for fewer:
-d "scope=zorlya:read"
You cannot ask for more than the grant holds. A refresh that names a scope the grant lacks answers invalid_scope. To widen access, send the person through the Connect page again and ask for the larger scope. A refresh is also held to the list entry as it is now: if the entry was switched off, lost the account’s app or got a lower ceiling, the refresh answers invalid_grant and the person connects again.
What a connection can do follows from its scopes:
tools/listshows only the tools the grant can call.- A write tool called on a read grant answers
403withWWW-Authenticate: Bearer error="insufficient_scope", scope="…"and a body naming the scope needed. The client keeps its session and asks the person again with the wider scope. - A tool that is switched off, or that the account may never use, answers “Tool not available”.
zorlya:readis also what a client gets when it asks for no scope.
Ending a connection
There are four ways, and they end different things.
The client revokes. POST /oauth/revoke with the token and your client_id:
curl -s -X POST https://api.zorlya.com/oauth/revoke \
-d "token=$REFRESH_TOKEN" -d "client_id=$CLIENT_ID"
It answers 200 and an empty body, whether or not the token was valid, so a caller cannot probe for live tokens. Revoking a refresh token ends the whole grant: that token and its access token. Revoking an access token ends only that access token. Prefer revoking the refresh token on sign-out. Use the hint token_type_hint=refresh_token to say which you are sending.
The person revokes. A job seeker sees and ends their connections in the Zorlya app under Settings, Integrations, AI clients. The email sent when a connection is made also links to mcp.zorlya.com/connections, which lists a person’s connections for every kind of account and lets them lower one or end it. Your next call answers 401, and a refresh answers invalid_grant.
A used code is replayed. Present an authorization code a second time and the server refuses it with “This authorization code has already been used.” It also ends the tokens the first exchange issued. A call with the first access token answers 401 afterwards.
The account changes. The server checks on every call that the account may still connect and still holds the role the token was issued for. A suspended account, a member removed from the company or a staff role taken away gets 401, and a password reset ends every session, AI clients included. An administrator can also switch MCP off for everyone, and every route then answers 404.
What to do on each answer
| You get | Do |
|---|---|
401 on the MCP server |
Refresh once and retry. If the refresh fails, sign in again |
403 with insufficient_scope |
Ask the person again with the scope named in the answer |
invalid_grant on refresh |
The grant is over. Discard your tokens and start the Connect page again |
invalid_scope on refresh |
Ask for no more than you hold, or start the Connect page again with the wider scope |
invalid_client on /oauth/token |
The client_id is unknown. Register again |
invalid_target |
The resource is not the one the grant was issued for |
429 on /oauth/register, /oauth/authorize, /oauth/token or /oauth/revoke |
Wait. Registration is limited to 10 a minute per address, /oauth/authorize to 30, /oauth/token to 60 and /oauth/revoke to 20; on the MCP server a 429 means the 120 reads a minute are used |
What we checked
Rotation, the dead access token, scope narrowing, revocation by refresh token and code replay were run against a local copy of the server on 2026-10-17. The lifetimes come from the server’s source. On 2026-10-18 the audience, the controls, the step-up 403 and the connections page were added from the source and the design, and not run. The request log and the connections page on mcp.zorlya.com are merged. On 2026-10-25 the text was checked against the repository again. None of it was run against the production server.