Refreshing tokens

Access tokens are short-lived. How you renew depends on how you signed in. There are two cases: the device refresh token, and machine-to-machine (which does not refresh at all).

Refresh-token rotation is opportunistic: there is no fixed rotation or expiry policy. The device path persists a rotated refresh token whenever the server returns one. Always store the newest refresh token you receive.

Case A: device refresh token

If you signed in with the device flow or PKCE and asked for offline_access, you have a refresh token. Exchange it at the token endpoint with grant_type=refresh_token. The native client is public, so there is no client secret in this call.

curl
curl -sS -X POST https://auth.cybersentriq.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=refresh_token" \
  --data-urlencode "client_id=YM8rjZukoH5rG2hnhMK82lSMAZYP57WR" \
  --data-urlencode "refresh_token=<REFRESH_TOKEN>"
TypeScript / Node
const body = new URLSearchParams({
  grant_type: "refresh_token",
  client_id: "YM8rjZukoH5rG2hnhMK82lSMAZYP57WR",
  refresh_token: storedRefreshToken,
});

const res = await fetch("https://auth.cybersentriq.com/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body,
});
const data = await res.json();
// Persist the rotated refresh token if one came back.
if (data.refresh_token) storedRefreshToken = data.refresh_token;
Python
import requests

res = requests.post(
    "https://auth.cybersentriq.com/oauth/token",
    data={
        "grant_type": "refresh_token",
        "client_id": "YM8rjZukoH5rG2hnhMK82lSMAZYP57WR",
        "refresh_token": stored_refresh_token,
    },
)
data = res.json()
# Persist the rotated refresh token if one came back.
if data.get("refresh_token"):
    stored_refresh_token = data["refresh_token"]
C#
using System.Text.Json;

var form = new FormUrlEncodedContent(new Dictionary<string, string>
{
    ["grant_type"] = "refresh_token",
    ["client_id"] = "YM8rjZukoH5rG2hnhMK82lSMAZYP57WR",
    ["refresh_token"] = storedRefreshToken,
});

var res = await http.PostAsync("https://auth.cybersentriq.com/oauth/token", form);
using var doc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
var root = doc.RootElement;
// Persist the rotated refresh token if one came back.
if (root.TryGetProperty("refresh_token", out var rt))
    storedRefreshToken = rt.GetString();
Go
form := url.Values{
    "grant_type":    {"refresh_token"},
    "client_id":     {"YM8rjZukoH5rG2hnhMK82lSMAZYP57WR"},
    "refresh_token": {storedRefreshToken},
}

res, err := http.PostForm("https://auth.cybersentriq.com/oauth/token", form)
if err != nil {
    log.Fatal(err)
}
defer res.Body.Close()

var data struct {
    AccessToken  string `json:"access_token"`
    RefreshToken string `json:"refresh_token"`
    ExpiresIn    int    `json:"expires_in"`
}
json.NewDecoder(res.Body).Decode(&data)
// Persist the rotated refresh token if one came back.
if data.RefreshToken != "" {
    storedRefreshToken = data.RefreshToken
}

The response is a fresh access token, and often a rotated refresh token. Always store the new refresh token if one is returned, and use it next time. If the exchange fails with invalid_grant, the refresh token is spent or revoked; sign in again.

Case B: machine to machine (no refresh token)

The client-credentials grant does not issue a refresh token, and does not need one. When your cached token nears expiry, request a new one exactly as in Service account and M2M authentication. Caching the token and re-requesting on expiry is the whole story.

Next