Exchange Refresh Token for New Token Pair

A refresh token lets your app get a new access token without asking the user to log in again. Access tokens expire after 1 hour (expires_in: 3600 seconds). Use this endpoint whenever yours has expired.

If you don't have a refresh token yet, start with Get an Authorization Code.

⚠️

Important

Each time you use a refresh token, it is invalidated and replaced with a new one. Always store the new refresh_token from the response immediately; the old one will no longer work.


Request

Send a POST request to /apiv2/oauth/authorize/token.

Auth type: Basic Auth — Authorization: Basic base64(CLIENT_ID:CLIENT_SECRET)

The Authorization header value must be the Base64 encoding of CLIENT_ID:CLIENT_SECRET (joined by a literal colon). curl's -u flag handles this automatically. If you are constructing the header manually in code or a tool like Postman, encode it first:

echo -n "CLIENT_ID:CLIENT_SECRET" | base64
# → e.g. ZTFiMmMzZDQ6c2VjcmV0

Headers:

  • Content-Type: application/x-www-form-urlencoded

Body parameters (form-encoded):

ParameterTypeRequiredDescription
grant_typestringYesMust be the literal string refresh_token.
refresh_tokenstringYesThe refresh token you received from a previous token exchange.
redirect_uristringYesMust exactly match the URI configured on the App credentials page.

Using curl (recommended):

curl -X POST https://go.zelt.app/apiv2/oauth/authorize/token \
  -u "CLIENT_ID:CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "redirect_uri=https://yourapp.example.com/callback" \
  -d "refresh_token=YOUR_SAVED_REFRESH_TOKEN"

Constructing the header manually (Postman, code, etc.):

ENCODED=$(echo -n "CLIENT_ID:CLIENT_SECRET" | base64)

curl -X POST https://go.zelt.app/apiv2/oauth/authorize/token \
  -H "Authorization: Basic $ENCODED" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "redirect_uri=https://yourapp.example.com/callback" \
  -d "refresh_token=YOUR_SAVED_REFRESH_TOKEN"

Success response

On success, you receive a 200 OK response with this structure:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Response fields:

FieldTypeDescription
access_tokenstringThe new access token. Use it in the Authorization: Bearer header for all API calls.
refresh_tokenstringA new refresh token. Save this immediately; the one you just used is now invalid.
token_typestringAlways Bearer.
expires_inintegerLifetime of the access token in seconds (3600 = 1 hour).

After a successful exchange:

  • Store the new refresh_token immediately. The token you just used is now invalid. Refresh tokens expire after 90 days of non-use.
  • Use the new access_token in the Authorization: Bearer header for all subsequent API calls.
  • Repeat this flow whenever your access token expires.

Common errors

ErrorCauseFix
401 UnauthorizedRefresh token expired (after 90 days), already used, or malformedRestart the full authorization code flow to get a new token pair
401 UnauthorizedWrong Client ID or Client secretCopy them again from the App credentials page
401 UnauthorizedAuthorization header value is not Base64-encoded (e.g. sent as Basic CLIENT_ID:CLIENT_SECRET literally)The value after Basic must be Base64-encoded. Use curl -u "CLIENT_ID:CLIENT_SECRET" (encodes automatically), or encode manually:
echo -n "CLIENT_ID:CLIENT_SECRET" | base64
401 Unauthorizedredirect_uri missing or doesn't match what's configured in the appInclude redirect_uri in the request body and ensure it exactly matches the URI on your App credentials page
400 Bad Requestgrant_type missing or incorrectEnsure grant_type=refresh_token is included in the form body

⚠️

Important

If your refresh token is lost or expired, you must restart the full authorization code flow on the Get an authorization code page.