Skip to main content

API Integration

The API integration gives you complete control over the user experience. Build custom UI components while leveraging SavvyMoney's credit data and offer engine.

Overview

With the API integration, you can:

  • ✅ Build completely custom UI
  • ✅ Control data flow and caching
  • ✅ Integrate with your existing design system
  • ✅ Access granular credit data
  • ✅ Implement custom offer presentation

Time to implement: 4-8 weeks

Prerequisites

  • API credentials (Client ID & Secret)
  • Backend infrastructure for API calls
  • Understanding of OAuth 2.0 authentication
  • SSL-enabled endpoints
Server-Side Only

All API calls must be made from your backend servers. Never expose API credentials in client-side code.

Authentication Flow

Step 1: Obtain Access Token

Get Access Token
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
})
});

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

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

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

private final RestTemplate restTemplate;

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(
"https://api.sandbox.savvymoney.com/oauth/token",
request,
TokenResponse.class
);

return response.getBody().getAccessToken();
}
}

Step 2: Token Management

Implement token caching and refresh:

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

async getToken() {
// Return cached token if valid (with 5 min buffer)
if (this.token && this.expiresAt > Date.now() + 300000) {
return this.token;
}

// Fetch new token
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
})
});

const data = await response.json();
this.token = data.access_token;
this.expiresAt = Date.now() + (data.expires_in * 1000);

return this.token;
}
}

const tokenManager = new TokenManager();

Core API Operations

Enroll a User

Before accessing credit data, users must be enrolled:

User Enrollment
const enrollUser = async (userData) => {
const token = await tokenManager.getToken();

const response = await fetch('https://api.sandbox.savvymoney.com/v1/users/enroll', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
externalUserId: userData.id,
email: userData.email,
firstName: userData.firstName,
lastName: userData.lastName,
ssn: userData.ssn, // Last 4 digits
dateOfBirth: userData.dob, // YYYY-MM-DD format
address: {
street: userData.street,
city: userData.city,
state: userData.state,
zipCode: userData.zipCode
}
})
});

if (!response.ok) {
const error = await response.json();
throw new Error(`Enrollment failed: ${error.message}`);
}

return response.json();
};

Get Credit Score

Retrieve Credit Score
const getCreditScore = async (userId) => {
const token = await tokenManager.getToken();

const response = await fetch(
`https://api.sandbox.savvymoney.com/v1/users/${userId}/credit-score`,
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);

const data = await response.json();

return {
score: data.score,
scoreDate: data.scoreDate,
scoreRange: data.scoreRange,
factors: data.factors,
trend: data.trend
};
};

Response Structure

Credit Score Response
{
"score": 742,
"scoreDate": "2024-01-15",
"scoreRange": {
"min": 300,
"max": 850
},
"rating": "Good",
"factors": [
{
"code": "PAYMENT_HISTORY",
"name": "Payment History",
"impact": "high",
"description": "Your payment history is excellent",
"score": 95
},
{
"code": "CREDIT_UTILIZATION",
"name": "Credit Utilization",
"impact": "medium",
"description": "Your credit utilization is 28%",
"score": 72
}
],
"trend": {
"direction": "up",
"change": 12,
"period": "3months"
}
}

Get Personalized Offers

Get Offers
const getOffers = async (userId) => {
const token = await tokenManager.getToken();

const response = await fetch(
`https://api.sandbox.savvymoney.com/v1/users/${userId}/offers`,
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);

return response.json();
};

Track Offer Click

When a user clicks an offer, track the interaction:

Track Offer Click
const trackOfferClick = async (userId, offerId) => {
const token = await tokenManager.getToken();

await fetch(
`https://api.sandbox.savvymoney.com/v1/users/${userId}/offers/${offerId}/click`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
timestamp: new Date().toISOString(),
source: 'web'
})
}
);
};

Building Your UI

React Example

CreditScoreCard.tsx
import React, { useState, useEffect } from 'react';

interface CreditScore {
score: number;
rating: string;
trend: {
direction: 'up' | 'down' | 'stable';
change: number;
};
}

export const CreditScoreCard: React.FC = () => {
const [creditScore, setCreditScore] = useState<CreditScore | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
const fetchScore = async () => {
try {
const response = await fetch('/api/credit-score');
if (!response.ok) throw new Error('Failed to fetch');
const data = await response.json();
setCreditScore(data);
} catch (err) {
setError('Unable to load credit score');
} finally {
setLoading(false);
}
};

fetchScore();
}, []);

if (loading) return <div className="skeleton" />;
if (error) return <div className="error">{error}</div>;
if (!creditScore) return null;

return (
<div className="credit-score-card">
<div className="score-display">
<span className="score-value">{creditScore.score}</span>
<span className="score-rating">{creditScore.rating}</span>
</div>

<div className="score-trend">
{creditScore.trend.direction === 'up' && '↑'}
{creditScore.trend.direction === 'down' && '↓'}
{creditScore.trend.change} points this month
</div>
</div>
);
};

Error Handling

Implement robust error handling:

API Error Handler
class SavvyMoneyError extends Error {
constructor(code, message, details) {
super(message);
this.code = code;
this.details = details;
}
}

const handleApiResponse = async (response) => {
if (response.ok) {
return response.json();
}

const error = await response.json();

switch (response.status) {
case 400:
throw new SavvyMoneyError('VALIDATION_ERROR', error.message, error.details);
case 401:
throw new SavvyMoneyError('UNAUTHORIZED', 'Invalid or expired token');
case 403:
throw new SavvyMoneyError('FORBIDDEN', 'Access denied');
case 404:
throw new SavvyMoneyError('NOT_FOUND', 'Resource not found');
case 429:
throw new SavvyMoneyError('RATE_LIMITED', 'Too many requests');
default:
throw new SavvyMoneyError('UNKNOWN', 'An unexpected error occurred');
}
};

Caching Strategy

Implement appropriate caching for performance:

Caching Layer
const cache = new Map();

const getCachedCreditScore = async (userId) => {
const cacheKey = `score:${userId}`;
const cached = cache.get(cacheKey);

// Return cached if less than 1 hour old
if (cached && Date.now() - cached.timestamp < 3600000) {
return cached.data;
}

// Fetch fresh data
const data = await getCreditScore(userId);

cache.set(cacheKey, {
data,
timestamp: Date.now()
});

return data;
};

Best Practices

  1. Cache access tokens - Don't fetch a new token for every request
  2. Implement retry logic - Handle transient failures gracefully
  3. Log API interactions - For debugging and audit purposes
  4. Monitor rate limits - Track usage against your quotas
  5. Secure credentials - Use environment variables, never commit secrets

Next Steps