Token Expiry FAQ

Since there are many questions about the token expiry requirement spread across many threads I thought it would be useful to bring many of them together here in one consolidated FAQ. As more questions are asked I’ll update this post to include more answers.

Deadlines and scope

What happens on each date?

Date Consequence for non-compliant public apps
January 1, 2027 App Store delisting and blocked new installs. Existing installations can continue affected API requests.
January 15, 2027 Merchant warnings in Shopify admin.
April 1, 2027 Affected Admin API requests using non-expiring offline tokens return HTTP 403.

January 1 remains the migration deadline. April 1 is the API rejection milestone. The earlier requirement for public apps created on or after April 1, 2026 remains in effect.

Which apps and APIs are affected?

The requirement covers public apps using legacy non-expiring offline tokens with GraphQL or REST Admin APIs. Custom apps, merchant-created apps, and online access tokens are outside this migration mandate. Payments Apps API traffic is not subject to the requirement.

How does Shopify detect non-expiring token usage, and why can the warning remain after migration?

Shopify checks the token presented on an Admin API request. Detection is based on using the token, rather than simply holding an unused token. A rejected request can also count as detected usage.

Those request events are aggregated by app for Dev Dashboard. The warning considers usage in the preceding 14 days, so historical usage can remain visible after you migrate.

If you believe your app has fully migrated but are still seeing alerts, check old workers, queued jobs, and infrequent tasks that may still send a legacy token. Acquiring an expiring offline access token through any method does not itself clear earlier usage or show that every request has switched.

I received an email about non-expiring tokens but I don’t see any alerts in the Dev Dashboard

Shopify periodically sends emails to partners that have apps with active non-expiring offline access tokens. If these access tokens have not been used in the last 14 days there will not be an alert in the Dev Dashboard. However, the tokens represent a latent risk to the app because they could be used to make API requests, trigger an alert, and cause the app to be non-compliant with the expiring token policy.

Choosing a token flow

Do I have to use refresh tokens, or can I use application-only access permanently?

Application-only access is a supported long-term acquisition method using the client credentials grant with a signed client assertion. It returns a short-lived offline access token, without a refresh token. Token exchange and the authorization code grant can instead return an expiring offline access token paired with a refresh token. Choose the acquisition and renewal flow that fits the workload.

Which tokens should I use for background jobs versus interactive staff actions?

Use offline access tokens for work performed on the app’s authority, regardless of acquisition method. Use online tokens when a request must respect the signed-in staff member’s permissions. An offline token does not become staff-scoped because the request originated in your UI. See access token types for more details about the different tokens apps can use.

How long do tokens last?

The time to live (TTL) of tokens varies based on the token type and token acquisition method.

Token Acquisition method / context TTL Renewal
Offline access token Token exchange or authorization code grant, requesting expiry 1 hour Use the refresh token
Offline access token Application-only access; for public apps Up to 1 hour Repeat acquisition; no refresh token
Offline access token Client credentials grant with a client secret, or application-only access without public distribution; app and store must be in the same organization 24 hours Repeat acquisition; no refresh token
Refresh token Returned with an expiring offline access token 90 days when issued; can retire earlier Successful refresh returns a new pair
Online access token User-associated token exchange or authorization code grant 24 hours or user logout, whichever comes first Acquire again with a user session
Delegate access token Derived from a parent offline access token Parent’s remaining lifetime, or a shorter requested TTL Acquire another delegate
Legacy non-expiring offline access token Earlier offline acquisition without expiry No time-based expiry Subject to revocation and the enforcement schedule above

Use expires_in for the access token’s TTL, and refresh_token_expires_in for the refresh token’s TTL when one is returned.

Does using application-only access invalidate my existing tokens?

No. Application-only access does not retire legacy offline tokens or offline access-token/refresh-token pairs. Token exchange, authorization code acquisition, and refresh also do not retire offline access tokens acquired through application-only access. Multiple offline access tokens acquired this way can remain valid together. Continued affected legacy-token use still leaves the app incompletely migrated.

When does direct migration invalidate the old non-expiring token?

The successful direct exchange invalidates the legacy token immediately. Do not keep using it until the first refresh. Its narrowly supported reuse for migration-response recovery does not restore it as an API credential.

Can separate workers, regions, or customer connections have independent refresh-token families?

The offline flow that returns an access-token/refresh-token pair does not provide independent refreshable families per connection for one app/store. Coordinate acquisition and refresh through shared credential ownership. Application-only access supports overlapping short-lived offline access tokens and may fit independent workers, but those tokens still carry the installation’s app authority; they do not create separate merchant grants or staff identities.

Migrating existing installations

Do merchants need to reopen or reinstall the app to migrate?

No, you do not need to wait for merchants to open your app to migrate old non-expiring tokens. Follow the instructions for migrating without a user session.

Does turning on the SDK/template option migrate every stored token?

Changing acquisition configuration does not convert credentials already stored in your database. Update new token acquisitions to request expiring tokens, then migrate existing installations and update background workers. For direct OAuth/token-exchange implementations, request expiring=1; follow your framework’s configuration instructions.

Can I migrate in batches?

Yes, it’s safe to design your application rollout around individual stored installations. Deploy support for both credential states first, exchange a small cohort, persist the returned pair and expiry information together, and verify real API and background-job behavior before expanding. This is a recommended rollout approach, rather than a promise that every SDK provides batch orchestration. Ensure that you account for concurrent workers and apps that acquire tokens whenever they are opened by the merchant in admin.

What if the migration response is lost or my database write fails?

Retry the same direct migration request with the original legacy token and a valid client ID and client secret within seven days. An eligible retry returns the original pair; recovery ends sooner if that pair is refreshed or superseded by later acquisition.

Refresh, dormant stores, and recovery

Must I refresh every hour for every store, even when there is no work?

No. An unused expiring access token will expire based on its TTL. Refresh when API work resumes while its refresh token is valid. If you rely on refresh tokens, schedule maintenance before their expiry for dormant installations; alternatively implement application-only access where eligible. Receiving a webhook does not itself renew credentials.

What happens to tokens while a store is frozen, including seasonal stores frozen for months?

Apps can refresh expiring offline tokens while a store is frozen. Keep refreshing before the current refresh token expires; freezing does not give you an indefinite credential lifetime. Successful refresh issues a new refresh token with a new 90-day lifetime, without requiring the merchant to reopen the app. You do not need to refresh every hour when there is no API work.

Admin API requests to a frozen store can return HTTP 402 Payment Required. That is a store-state restriction; acquiring or refreshing a token does not unfreeze the store. Pause affected data jobs while retaining credential maintenance. For an unpaid-balance freeze, the merchant must resolve the outstanding payment in admin. See response-code reference for details of this error.

What retires the previous refresh token?

For the normal expiring offline refresh flow, the previous refresh token remains usable until the earliest of:

  • Use of its replacement refresh token.
  • The 30-day limit measured from its first refresh use.
  • Its original expiry (90 days from issuance).

New offline acquisition through token exchange or the authorization code grant immediately retires all other refresh tokens for that app and store. Using the replacement access token is not the acknowledgement that retires the preceding refresh token. See our dev docs for more details on refresh, rotation, and revocation.

What should I do if a refresh response is lost?

Retry promptly with the last stored refresh token while it remains eligible. Persist the returned access token, refresh token, and expiry dates atomically, then use the newest refresh token.

Can I restore an old database backup and use any refresh token saved there?

You can use a refresh token from a backup if it is still valid. If it has expired or been retired by later refreshes or token acquisition, you can regain access through application-only access when the app remains installed and signing-key authentication is configured. Otherwise, the merchant may need to reopen the app and complete authorization. The 30-day refresh recovery period helps with lost refresh responses; it does not restore tokens that have already been retired.

Application-only access and signing keys

What do I need to set up application-only access?

Follow our setup guide for complete details. Generate a signing key pair, register the public JWK in Dev Dashboard, and implement signed client assertions and token acquisition in your backend. Retain the private key securely. Follow the tutorial for supported algorithms, claims, and request format.

Can I request narrower scopes with application-only access?

No. This flow uses the installation’s granted scopes; the request’s scope parameter is ignored. It does not grant scopes the merchant declined or revoked. Use the returned scope information when checking what access is available.

How should I renew offline access tokens acquired through application-only access?

Cache by store, use the response expiry, and acquire again before expiry when needed. Add jitter and transient-error backoff. There is no refresh token for this flow; acquiring another offline access token through the same method does not revoke the still-valid one.

How do key rotation and revocation affect issued tokens?

Adding a replacement public key lets you move signing to the new key before revoking the old one. Revocation causes assertions and offline access tokens tied to that key to be rejected shortly afterward. Client-secret rotation does not revoke offline access tokens acquired through application-only access; public-key revocation does not revoke tokens issued with the other key or the client secret.

Short access-token lifetime limits a leaked access token. A stolen private key can keep obtaining tokens until its public key is revoked.

Is the Dashboard’s “rotation refresh token” a backup for a store’s OAuth refresh token?

No. It belongs to the client-secret rotation workflow. It is not a recovery credential for a store’s expired or retired refresh token.

You cannot actually be serious about this. The number of stores referenced in the email bears no relationship to what actually appears in the Dev Dashboard.

Are you saying if an app that has migrated all active stores and accidentally makes a single call after January 1st (because a dead or dormant store was unexpectedly reactivated) will be delisted? Just make the token stop working. An app shouldn’t be penalized for one mistake if 99% are going through fine.

What’s the chance that a competitor installs an app on his own store and then triggers an expired offline token to delist the competition automatically?

I completely agree with this point. If a store is inactive and an old non-expiring token request is detected, Shopify should ignore it for warnings and delisting scenarios. This would make things much easier for everyone. :+1:

Also, if I’m not wrong, based on what I understood from the migration email, “Complete your migration to expiring offline access tokens”, I think they are counting the “tokens to migrate” based on the unsuccessful requests to exchange non-expiring tokens for expiring tokens. Most of them are closed stores. I’ve also compared that count with the number of closed stores across my apps, but the numbers don’t match either. So this is count is only Shopify knows.

I’m also ignoring the email for now because we don’t see any warnings in the Dev Dashboard, and I’m unable to figure out the counts or understand the whole email properly.

One more thing, when will Shopify allow us to migrate frozen stores? I’ve migrated almost 5 apps to expiring access tokens, but in all these apps, I’m unable to get a new pair of tokens for frozen stores. Is this something new, especially with so many changes coming every day? :sweat_smile:

What are the chances that a competitor could install an app on their own store and then deliberately trigger an expired offline token to automatically delist competing apps?

How would that even work? Once you migrate, before making any Admin API request, you should check whether the store still has a legacy token. If it does, migrate it to an expiring offline token first, and only then proceed with the API request.

You can also run a migration script to identify stores that are closed or frozen and flag them. When those stores reopen and the merchant accesses the app, the authentication flow should automatically handle the migration and obtain the new token pair before making any Admin API requests.

Am I missing something here? I’m currently in the middle of migrating our app to expiring tokens, and so far I don’t see this being an issue.

For our other backend services, I’m planning to use application-only access tokens, while the app’s admin portal will continue using expiring offline tokens. This way, independent backend services can acquire their own tokens without interfering with the admin portal’s refresh token flow.

The only additional thing I need is a cron job to ensure refresh tokens never expire. I’ll refresh them 7 days before their expiry.

I genuinely don’t see how a competitor could exploit this to deliberately delist another app. Am I overlooking something?