Obtaining and refreshing an access token
You call the ReceiptRoller API with the access token from the OAuth authorization code flow in an Authorization: Bearer … header. Access tokens last 8 hours, so you renew them with the refresh token. This article covers lifetimes, the requests for obtaining and refreshing tokens, how the refresh token is replaced on every refresh, and the cases in which tokens stop working. For the authorize URL and the consent screen, see the API Authentication Guide.
Lifetimes
| Token | Lifetime | Used for |
|---|---|---|
| Access token | 8 hours (expires_in: 28800) | Calling the API |
| Refresh token | 30 days from issue | Refreshing the access token (works once) |
| Authorization code | 10 minutes (works once) | Getting the first tokens |
Obtain tokens (authorization code to tokens)
POST https://receiptroller.io/api/v1/auth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
If you used PKCE, send code_verifier as well. Public clients send no client_secret and must send code_verifier.
Response
{
"access_token": "…",
"refresh_token": "…",
"token_type": "Bearer",
"expires_in": 28800,
"scope": "store.products.read store.orders.read",
"accessToken": "…",
"refreshToken": "…",
"tokenType": "Bearer",
"expiresIn": 28800
}
The camelCase keys repeat the same values for older clients. New apps should read the snake_case keys.
Refresh (refresh token to new tokens)
POST https://receiptroller.io/api/v1/auth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=REFRESH_TOKEN
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
The response has the same shape as above. Public clients send no client_secret.
- Every refresh returns a new access token and a new refresh token. Store both every time.
- The refresh token you used, and the old access token paired with it, stop working at that moment, even if the old access token had time left.
- The new refresh token is valid for 30 days from the refresh. As long as you refresh at least once every 30 days, you keep working without re-authorization.
- Scopes and the bound business account stay the same as before the refresh.
When to refresh
- Before expiry: work out the expiry from
expires_inand refresh a little before it. - On a 401: when the API returns 401, refresh once and retry; if you still get 401, send the user through authorization again.
Refreshing twice with the same refresh token makes the second attempt fail with invalid_grant. If several processes share a token, refresh from one place, or use a lock so that two refreshes never run at once.
When tokens stop working
| Case | What happens |
|---|---|
| The refresh token's 30 days have passed | Refresh returns 401 invalid_grant. Re-authorization is needed. |
| You refreshed with a refresh token that was already used | 401 invalid_grant. Use the most recent refresh token you received. |
| The business account's owner disconnected the app in "Connected apps" | Every token the app holds for that business account is revoked: API calls return 401 and refresh returns invalid_grant. |
Your app revoked it with /api/v1/auth/revoke | That access token and refresh token pair stops working. |
| The client secret was regenerated | Issued access tokens keep working until they expire, but refreshing with the old secret returns 401 invalid_client. Switch to the new secret. |
| The app was deleted | All of the app's tokens are deleted with it. Refresh returns 401 invalid_client. |
| The business account's plan dropped below Starter | Refresh returns 401 invalid_grant, with an error_description saying the plan is needed. The tokens are not revoked, so after the plan is restored the same refresh token works again. |
When re-authorization is needed, send the user to the authorize URL again.
Check a token
POST https://receiptroller.io/api/v1/auth/verify
Content-Type: application/json
{ "accessToken": "ACCESS_TOKEN" }
A valid token returns 200 with active, clientId, scope, expiresAt and organizationId; an invalid one returns 401. organizationId is the business account the token is bound to, or null for a token that is not bound to one.
Storage
- Keep tokens on your server, not in browser local storage. In mobile apps, use the device's secure storage (such as the iOS Keychain).
- Encrypt tokens you store in a database.
- Store them per user and per business account so they cannot be mixed up.
- Do not write them to logs.