Token Refresh
Access tokens expire after 15 minutes for security reasons. The Token Refresh endpoint allows you to obtain a new access token using a valid refresh token without requiring credentials again.
Endpoint
POST /auth/refresh
Authentication
Required: No (public endpoint)
Request
Request Body
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
Field Descriptions
| Field | Type | Required | Description |
|---|---|---|---|
refresh | string | Yes | Valid refresh token from login or app token endpoint |
Response
Success Response (200 OK)
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
Response Fields
| Field | Type | Description |
|---|---|---|
access | string | New JWT access token (valid for 15 minutes) |
refresh | string | New JWT refresh token (valid for 7 days) |
Token Lifecycle
┌─────────────────────────────────────────────────────────┐
│ Token Lifecycle │
├─────────────────────────────────────────────────────────┤
│ │
│ Login/Auth Refresh Before Expiry │
│ ↓ ↓ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Access: 15m │ │ Access: 15m │ │
│ │ Refresh: 7d │──────────│ Refresh: 7d │────── │
│ └─────────────┘ └──────────────┘ │ │
│ ▲ ▲ │ │
│ │ │ │ │
│ └──────────────────────────┘ │ │
│ │ │
│ Refresh Expired (7d) │
│ ↓ │
│ Must Re-login │
│ │
└─────────────────────────────────────────────────────────┘
Status Codes
| Code | Status | Description |
|---|---|---|
| 200 | Success | Token refreshed successfully |
| 400 | Bad Request | Missing refresh token |
| 401 | Unauthorized | Invalid or expired refresh token |
| 403 | Forbidden | Token revoked or user deactivated |
| 500 | Server Error | Internal server error |
Error Responses
400 Bad Request - Missing Refresh Token
{
"detail": "Refresh token is required"
}
401 Unauthorized - Invalid Token
{
"detail": "Invalid refresh token"
}
401 Unauthorized - Expired Refresh Token
{
"detail": "Refresh token has expired. Please login again."
}
403 Forbidden - User Deactivated
{
"detail": "User account is no longer active"
}
Examples
Using cURL
curl -X POST https://sandbox.api.jpay.africa/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}'
Using Python
import requests
import json
from datetime import datetime, timedelta
import jwt
class JPayTokenManager:
"""Manages token refresh for JPay API"""
def __init__(self, access_token, refresh_token):
self.access_token = access_token
self.refresh_token = refresh_token
self.token_expiry = None
self._decode_token_expiry()
def _decode_token_expiry(self):
"""Decode JWT to get expiry time"""
try:
decoded = jwt.decode(
self.access_token,
options={"verify_signature": False}
)
exp_timestamp = decoded.get('exp')
if exp_timestamp:
self.token_expiry = datetime.fromtimestamp(exp_timestamp)
except:
# If decoding fails, assume expired
self.token_expiry = datetime.now()
def is_token_expired(self):
"""Check if token is expired or about to expire"""
if self.token_expiry is None:
return True
# Refresh if less than 2 minutes remaining
return datetime.now() >= (self.token_expiry - timedelta(minutes=2))
def refresh_access_token(self):
"""Get new access token using refresh token"""
try:
response = requests.post(
'https://sandbox.api.jpay.africa/api/v1/auth/refresh',
json={'refresh': self.refresh_token},
timeout=10
)
if response.status_code == 200:
data = response.json()
self.access_token = data['access']
self.refresh_token = data['refresh']
self._decode_token_expiry()
print("Token refreshed successfully")
return True
elif response.status_code == 401:
print("Refresh token expired. Please login again.")
return False
else:
print(f"Token refresh failed: {response.status_code}")
print(response.json())
return False
except requests.RequestException as e:
print(f"Token refresh error: {e}")
return False
def get_valid_access_token(self):
"""Get valid access token, refreshing if necessary"""
if self.is_token_expired():
if not self.refresh_access_token():
raise RuntimeError("Failed to refresh token")
return self.access_token
def get_auth_headers(self):
"""Get authorization headers with valid token"""
token = self.get_valid_access_token()
return {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
# Usage Example
def api_request_with_auto_refresh(token_manager, endpoint, method='GET', data=None):
"""Make API request with automatic token refresh"""
headers = token_manager.get_auth_headers()
if method == 'GET':
response = requests.get(endpoint, headers=headers)
elif method == 'POST':
response = requests.post(endpoint, headers=headers, json=data)
else:
raise ValueError(f"Unsupported method: {method}")
return response
# Initialize token manager
token_manager = JPayTokenManager(
access_token='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...',
refresh_token='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...'
)
# Make request - will auto-refresh if token is expired
response = api_request_with_auto_refresh(
token_manager,
'https://sandbox.api.jpay.africa/api/v1/payments/collections/list',
method='GET'
)
print(response.json())
Using JavaScript
class JPayTokenManager {
constructor(accessToken, refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.tokenExpiry = this.decodeTokenExpiry(accessToken);
}
decodeTokenExpiry(token) {
try {
const parts = token.split('.');
const decoded = JSON.parse(atob(parts[1]));
return new Date(decoded.exp * 1000);
} catch (e) {
return new Date(0); // If decode fails, assume expired
}
}
isTokenExpired() {
// Refresh if less than 2 minutes remaining
const bufferTime = 2 * 60 * 1000; // 2 minutes
return new Date() >= new Date(this.tokenExpiry.getTime() - bufferTime);
}
async refreshAccessToken() {
try {
const response = await fetch(
'https://sandbox.api.jpay.africa/api/v1/auth/refresh',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
refresh: this.refreshToken
})
}
);
if (!response.ok) {
if (response.status === 401) {
console.error('Refresh token expired. Please login again.');
return false;
}
console.error(`Token refresh failed: ${response.status}`);
return false;
}
const data = await response.json();
this.accessToken = data.access;
this.refreshToken = data.refresh;
this.tokenExpiry = this.decodeTokenExpiry(data.access);
console.log('Token refreshed successfully');
return true;
} catch (error) {
console.error('Token refresh error:', error);
return false;
}
}
async getValidAccessToken() {
if (this.isTokenExpired()) {
const success = await this.refreshAccessToken();
if (!success) {
throw new Error('Failed to refresh token');
}
}
return this.accessToken;
}
async getAuthHeaders() {
const token = await this.getValidAccessToken();
return {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
};
}
async makeRequest(endpoint, options = {}) {
const headers = await this.getAuthHeaders();
const response = await fetch(endpoint, {
...options,
headers: {
...headers,
...options.headers
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'API request failed');
}
return response.json();
}
}
// Usage Example
const tokenManager = new JPayTokenManager(
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...',
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...'
);
// Make request - will auto-refresh if token is expired
try {
const collections = await tokenManager.makeRequest(
'https://sandbox.api.jpay.africa/api/v1/payments/collections/list'
);
console.log('Collections:', collections);
} catch (error) {
console.error('Failed to fetch collections:', error);
}
Token Refresh Best Practices
1. Proactive Refresh
Refresh tokens before they expire rather than waiting for a 401 error:
# Good: Refresh when 2 minutes remaining
if token_manager.is_token_expired():
token_manager.refresh_access_token()
# Bad: Wait for request to fail
try:
response = api_request() # 401 Unauthorized
except UnauthorizedError:
refresh_token() # Too late
2. Centralized Token Management
Use a token manager class to handle all token operations:
# Good: Use token manager for all requests
token_manager = JPayTokenManager(access, refresh)
headers = token_manager.get_auth_headers()
response = requests.get(endpoint, headers=headers)
# Bad: Manual token management scattered throughout code
if token_expired():
refresh()
headers = {'Authorization': f'Bearer {token}'}
3. Handle Refresh Token Expiry
Implement logic to redirect to login when refresh token expires:
def ensure_valid_token(token_manager):
if token_manager.is_token_expired():
success = token_manager.refresh_access_token()
if not success:
# Refresh token also expired
redirect_to_login()
4. Secure Token Storage
Store tokens securely based on your environment:
Browser (Web Application):
- Use httpOnly cookies (prevents XSS attacks)
- Set Secure flag (HTTPS only)
- Set SameSite=Strict (CSRF protection)
Server (Backend Application):
- Use environment variables
- Use secrets manager (AWS Secrets Manager, Vault, etc.)
- Never commit tokens to version control
5. Token Refresh on Network Errors
Implement retry logic with token refresh:
def api_request_with_retry(token_manager, endpoint, max_retries=3):
for attempt in range(max_retries):
try:
headers = token_manager.get_auth_headers()
response = requests.get(endpoint, headers=headers, timeout=10)
if response.status_code == 401:
# Unauthorized, try refresh
if attempt < max_retries - 1:
token_manager.refresh_access_token()
continue
return response
except requests.RequestException as e:
if attempt < max_retries - 1:
wait_time = 2 ** attempt
time.sleep(wait_time)
continue
raise
Related Endpoints
- App Token Authentication - Get initial app tokens
- Authentication Overview - Authentication methods overview
Next Steps
After refreshing your token:
- Update your stored token with the new access token
- Continue making API requests with the new token
- Monitor token expiry and refresh proactively
- Implement automatic refresh in your token manager