Skip to main content

Authentication

The SavvyMoney API uses OAuth 2.0 for authentication. This guide covers how to obtain and use access tokens.

Overview​

There are two types of tokens:

Token TypePurposeLifetime
Access TokenServer-to-server API calls1 hour
User TokenUser-specific operations (iframe, mobile)1 hour
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Your Server │────▢│ SavvyMoney │────▢│ Credit Data β”‚
β”‚ β”‚ β”‚ Auth Server β”‚ β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β”‚ Access Token β”‚
│◀──────────────────────│
β”‚ β”‚
β”‚ User Token β”‚
│◀──────────────────────│

Obtaining Access Tokens​

Client Credentials Flow​

Use the client credentials grant to obtain an access token for server-to-server API calls.

Request
curl -X POST "https://api.sandbox.savvymoney.com/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
Response
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write"
}

Code Examples​

Node.js
const getAccessToken = async () => {
const response = await fetch('https://api.sandbox.savvymoney.com/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.SAVVY_CLIENT_ID,
client_secret: process.env.SAVVY_CLIENT_SECRET
})
});

if (!response.ok) {
throw new Error(`Authentication failed: ${response.status}`);
}

const data = await response.json();
return data.access_token;
};
Java
@Service
public class SavvyMoneyAuthService {

private final RestTemplate restTemplate;

@Value("${savvymoney.client-id}")
private String clientId;

@Value("${savvymoney.client-secret}")
private String clientSecret;

@Value("${savvymoney.auth-url}")
private String authUrl;

public String getAccessToken() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "client_credentials");
body.add("client_id", clientId);
body.add("client_secret", clientSecret);

HttpEntity<MultiValueMap<String, String>> request =
new HttpEntity<>(body, headers);

ResponseEntity<TokenResponse> response = restTemplate.postForEntity(
authUrl + "/oauth/token",
request,
TokenResponse.class
);

return response.getBody().getAccessToken();
}
}
Python
import requests
import os

def get_access_token():
response = requests.post(
'https://api.sandbox.savvymoney.com/oauth/token',
data={
'grant_type': 'client_credentials',
'client_id': os.environ['SAVVY_CLIENT_ID'],
'client_secret': os.environ['SAVVY_CLIENT_SECRET']
}
)

response.raise_for_status()
return response.json()['access_token']

Obtaining User Tokens​

User tokens are required for user-specific operations like displaying credit scores in iframes or mobile apps.

Request
curl -X POST "https://api.sandbox.savvymoney.com/v1/auth/user-token" \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{
"userId": "user-12345",
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe"
}'
Response
{
"userToken": "ut_eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 3600,
"expiresAt": "2024-01-15T11:30:00Z"
}

Required Fields​

FieldTypeDescription
userIdstringYour internal user identifier
emailstringUser's email address
firstNamestringUser's first name
lastNamestringUser's last name

Optional Fields​

FieldTypeDescription
phonestringUser's phone number
externalIdstringAdditional external identifier

Using Tokens​

Bearer Authentication​

Include the access token in the Authorization header:

curl -X GET "https://api.savvymoney.com/v1/users/user-12345/credit-score" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

Token Management​

Caching Tokens​

Tokens are valid for 1 hour. Implement caching to avoid unnecessary token requests:

Token Manager
class TokenManager {
constructor() {
this.token = null;
this.expiresAt = null;
}

async getToken() {
// Check if current token is still valid (with 5-minute buffer)
const now = Date.now();
const bufferMs = 5 * 60 * 1000; // 5 minutes

if (this.token && this.expiresAt && (this.expiresAt - now > bufferMs)) {
return this.token;
}

// Fetch new token
const response = await fetch('https://api.savvymoney.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.SAVVY_CLIENT_ID,
client_secret: process.env.SAVVY_CLIENT_SECRET
})
});

const data = await response.json();

this.token = data.access_token;
this.expiresAt = now + (data.expires_in * 1000);

return this.token;
}

invalidate() {
this.token = null;
this.expiresAt = null;
}
}

// Singleton instance
export const tokenManager = new TokenManager();

Handling Token Expiration​

If a request fails with 401 Unauthorized, refresh the token and retry:

Retry with Token Refresh
const makeAuthenticatedRequest = async (url, options = {}) => {
const token = await tokenManager.getToken();

const response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`
}
});

// If unauthorized, refresh token and retry once
if (response.status === 401) {
tokenManager.invalidate();
const newToken = await tokenManager.getToken();

return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${newToken}`
}
});
}

return response;
};

Security Best Practices​

Credential Storage​

Never Expose Credentials
  • Never include credentials in client-side code
  • Never commit credentials to version control
  • Use environment variables or secure vaults
Environment Variables
# .env file (never commit this!)
SAVVY_CLIENT_ID=your_client_id
SAVVY_CLIENT_SECRET=your_client_secret
Accessing Environment Variables
// Node.js
const clientId = process.env.SAVVY_CLIENT_ID;
const clientSecret = process.env.SAVVY_CLIENT_SECRET;

Token Security​

  1. Store tokens securely - Use secure, encrypted storage
  2. Never log tokens - Mask tokens in logs
  3. Use HTTPS - All API calls must use HTTPS
  4. Rotate credentials - Periodically rotate client credentials

IP Allowlisting​

For production environments, we recommend IP allowlisting. Contact support to configure allowed IP addresses for your account.

Scopes​

Access tokens can be limited to specific scopes:

ScopeDescription
readRead-only access to user data
writeCreate and update operations
adminAdministrative operations
offersAccess to offer data
webhooksManage webhook subscriptions

Request specific scopes during authentication:

curl -X POST "https://api.savvymoney.com/oauth/token" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "scope=read offers"

Troubleshooting​

Common Errors​

ErrorCauseSolution
invalid_clientWrong client ID or secretVerify credentials
invalid_grantExpired or invalid grantCheck grant type
unauthorized_clientClient not authorized for grant typeContact support
invalid_scopeRequested scope not allowedUse authorized scopes

Debug Authentication​

Test your credentials:

curl -v -X POST "https://api.sandbox.savvymoney.com/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"

Next Steps​