HomeDocsAuthentication
Security

Authentication

RoxCustody authenticates API requests using an access token. Access tokens are short-lived and are renewed using a rotating refresh token, each refresh returns a new refresh token and invalidates the previous one.

The Dual-Token Model

Your one-time API key (generated from the Vault's Configure API menu, see Getting Started) is exchanged once for two tokens:

TokenPurposeLifetimeUsage
accessTokenAuthenticate API requestsShort-lived (see expiresAt)X-ACCESS-TOKEN header
refreshTokenObtain a new access tokenRotates on each usePOST /auth/refresh-token body
Rotation rule. Each refresh returns a new refresh token and invalidates the previous one. Reusing an old refresh token will revoke the session.

Step 1, Generate Tokens

Use your one-time API key (generated from Vault → Configure API) to generate an access token and refresh token via POST /auth/generate-tokens. The API key is used only for this call, all subsequent requests use the access token.

Request body
JSON
{
"apiKey": "YOUR-ACCESS-TOKEN"
}

Language

POST /auth/generate-tokens
SHELL
1curl --request POST \
2 --url https://YOUR-SUBDOMAIN.api.roxcustody.com/api/integration/auth/generate-tokens \
3 --header 'Content-Type: application/json' \
4 --data '{ "apiKey": "YOUR-ACCESS-TOKEN" }'

Response, 201 Created

JSON
{
"message": "Vault API Keys generated successfully",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresAt": 1767258052423,
"vaultId": 1
},
"status": 201
}

Step 2, Use the Access Token

All API requests must include a valid access token in the X-ACCESS-TOKEN request header. There is no Bearer prefix, pass the raw token string.

Language

Authenticated request
SHELL
1curl --request GET \
2 --url https://YOUR-SUBDOMAIN.api.roxcustody.com/api/integration/vaults/details \
3 --header 'X-ACCESS-TOKEN: YOUR-ACCESS-TOKEN' \
4 --header 'Content-Type: application/json'

Step 3, Refresh the Access Token

When the access token expires (HTTP 498 Token has expired), refresh it using the latest refresh token via POST /auth/refresh-token. You must store the new refresh token from the response, the old one is invalidated.

Language

POST /auth/refresh-token
SHELL
1curl --request POST \
2 --url https://YOUR-SUBDOMAIN.api.roxcustody.com/api/integration/auth/refresh-token \
3 --header 'Content-Type: application/json' \
4 --data '{ "refreshToken": "YOUR-REFRESH-TOKEN" }'

Response, 201 Created

JSON
{
"message": "Vault API Keys generated successfully",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresAt": 1767258052423
},
"status": 201
}
Session revocation on refresh token reuse. Reusing an old refresh token revokes the session, the API responds with 401 Refresh token is no longer valid. Please generate a new session.You will need to generate a new API key from the Vault's Configure API menu and start a new session.

Implementation Examples

The following examples show production-ready patterns for managing token refresh automatically. Both use an interceptor/wrapper approach so you never need to manually handle 498 responses in your business logic.

Node.js, Auto-refresh interceptor
JS
const axios = require('axios')
 
const BASE_URL = 'https://YOUR-SUBDOMAIN.api.roxcustody.com/api/integration'
 
let accessToken = null
let refreshToken = null
 
async function refreshAccessToken() {
const res = await axios.post(`${BASE_URL}/auth/refresh-token`, {
refreshToken: refreshToken,
})
accessToken = res.data.data.accessToken
refreshToken = res.data.data.refreshToken // Always store the new refresh token
return accessToken
}
 
// Axios interceptor that auto-refreshes on 498 (Token has expired)
axios.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 498) {
const newToken = await refreshAccessToken()
error.config.headers['X-ACCESS-TOKEN'] = newToken
return axios(error.config)
}
return Promise.reject(error)
}
)
Python, Auto-refresh client
PYTHON
import requests
 
BASE_URL = "https://YOUR-SUBDOMAIN.api.roxcustody.com/api/integration"
 
class RoxClient:
def __init__(self, api_key: str):
self.api_key = api_key
self.access_token = None
self.refresh_token = None
 
def authenticate(self):
"""Exchange the one-time API key for the initial token pair."""
res = requests.post(f"{BASE_URL}/auth/generate-tokens", json={"apiKey": self.api_key})
res.raise_for_status()
data = res.json()["data"]
self.access_token = data["accessToken"]
self.refresh_token = data["refreshToken"]
 
def refresh(self):
"""Obtain a new access token using the rotating refresh token."""
res = requests.post(f"{BASE_URL}/auth/refresh-token", json={"refreshToken": self.refresh_token})
res.raise_for_status()
data = res.json()["data"]
self.access_token = data["accessToken"]
self.refresh_token = data["refreshToken"] # MUST update, old token is now invalid
 
def get(self, path: str):
"""Make an authenticated GET request, auto-refreshing on 498."""
headers = {"X-ACCESS-TOKEN": self.access_token}
res = requests.get(f"{BASE_URL}{path}", headers=headers)
if res.status_code == 498:
self.refresh()
headers["X-ACCESS-TOKEN"] = self.access_token
res = requests.get(f"{BASE_URL}{path}", headers=headers)
res.raise_for_status()
return res.json()

Authentication Errors

CodeHTTP StatusMeaningAction
E00401400Invalid API KeyVerify the key from Vault → Configure API; generate a new one if needed
E00402422Validation error (e.g. missing or malformed field)Check the request body matches the documented schema
E01401401Invalid access tokenCheck the token is correct and not truncated
E01402401Invalid refresh token, or refresh token no longer validGenerate a new API key and start a new session
E01403498Token has expiredCall POST /auth/refresh-token to renew

Security Best Practices

  • Store tokens securely and never expose them in client-side applications
  • Store your API key in a secrets manager (AWS Secrets Manager, HashiCorp Vault, Doppler)
  • Always persist the newest refresh token immediately, the previous one is invalidated on each refresh
  • Implement token refresh as a singleton operation, concurrent refreshes can reuse an old token and revoke the session
  • Monitor for E01402 errors, which indicate a refresh token was reused

What's next?