Stay connected
At the end of this guide your app renews its credentials without anyone signing in again, and knows the difference between a credential that expired and a connection that stopped.
Before you start
- A connection made with the offline_access permission, so you hold a refresh token.
Lifetimes
| Credential | Lasts | Then |
|---|---|---|
| Access credential | 30 minutes | Renew it with the refresh token. |
| Refresh token | 60 days from when it was issued | Each renewal issues a new one, so an app that is in use never reaches the end. A connection unused for 60 days stops. |
| Authorization code | 5 minutes, one use | Exchange it straight away. |
1. Renew
POSThttps://host.shiftly.au/oauth/token
curl -X POST https://host.shiftly.au/oauth/token \
-u "shf_your_client_id:shf_sk_your_client_secret" \
-H "Content-Type: application/json" \
-d '{ "grant_type": "refresh_token", "refresh_token": "shf_rt_..." }'The response has the same shape as the first exchange, with a new access credential and a new refresh token. Store the new refresh token and discard the old one. Renew when a request answers 401 token_expired, or shortly before the 30 minutes are up.
2. Rotation, the grace window and reuse
Every renewal rotates the refresh token. If two of your processes renew with the same token within 60 seconds, both succeed and each receives its own new refresh token. Both new tokens work, so a race on startup does not break the connection; keep whichever one you store.
Outside that window, presenting a refresh token that has already been rotated is treated as a sign the token leaked. The connection is stopped, every credential for it is revoked, and the business is told the app needs reconnecting. Your renewal answers invalid_grant with reason reused.
3. Tell a stopped connection from an expired credential
| What you see | What happened | What to do |
|---|---|---|
| 401 token_expired on a v1 request | The access credential has expired. | Renew and retry. |
| 401 invalid_token on a v1 request | The access credential is missing or malformed. | Send the credential as a bearer token. |
| 403 connection_disconnected, connection_suspended or app_suspended on a v1 request | The connection has stopped. The same three states as the renewal reasons below. | Stop using the connection and follow the matching row below. |
| invalid_grant, reason disconnected | The business disconnected your app in Shiftly. | Stop using the connection. Ask the business to connect again from your product if they want to. |
| invalid_grant, reason suspended | The connection stopped: the person who connected it left the business or closed their account, it went unused for 60 days, or a refresh token was reused. | Ask someone at the business to connect your app again. |
| invalid_grant, reason app_suspended | Shiftly has paused your app for every business. | Contact Shiftly. Nothing to do per business. |
| 403 permission_denied with a permission named | The person who connected the app lost that permission, or never had it. | Work without that permission, or have an owner connect the app again. |
Shiftly does not send notifications to partner apps yet, so your app learns that a connection stopped from these answers, or by reading Connection.
4. Revoke
POSThttps://host.shiftly.au/oauth/revoke
When a business removes your product, revoke the connection so Shiftly shows it as disconnected. Authenticate your app as for the token endpoint and send the refresh token or an access credential as token. Revoking is idempotent and always answers 200. It ends one connection: to disconnect several businesses, revoke each connection's refresh token.
What can go wrong
| You see | Why | What to do |
|---|---|---|
| invalid_grant with no reason | The refresh token is unknown, expired after 60 days, or belongs to another app. | The connection is gone. Ask the business to connect again. |
| 429 on the token endpoint | More than 60 token requests in a minute for your app. | Renew once per connection and share the credential across your processes. |
