Endpoints
Calculate Earned Rewards
POST /reward/earnedURL: https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/earned
Calculate earned credit card rewards for a provided list of transactions. Use this endpoint to show users how much reward value they’ve already received based on their spending history.
Authentication
- Required.
- Supply client-id and api-key headers with every request.
Request Headers
| Header | Type | Required | Description |
| client-id | string | Yes | Your Uthrive client ID |
| api-key | string | Yes | Your Uthrive API key |
| Content-Type | string | Yes | application/json |
Request Schema
The request body is a JSON object with an array of transaction objects.
| Field | Type | Required | Description |
|---|---|---|---|
| transactions | array | Yes | List of transaction objects |
Each transaction object:
| Field | Type | Required | Example |
|---|---|---|---|
| transactionId | string | Yes | “12345” |
| transactionAmount | number | Yes | 100.50 |
| transactionDate | string | Yes | “2023-10-01” |
| merchant | string | Yes | “Amazon” |
| cardName | string | Yes | “Chase Sapphire Preferred” |
Example Request
{
"transactions": [
{
"transactionId": "12345",
"transactionAmount": 100.50,
"transactionDate": "2023-10-01",
"merchant": "Amazon",
"cardName": "Chase Sapphire Preferred"
},
{
"transactionId": "67890",
"transactionAmount": 250.75,
"transactionDate": "2023-10-02",
"merchant": "Walmart",
"cardName": "American Express Blue Cash EveryDay"
}
]
}
Response
Returns a JSON object with two main fields:
- data: Contains the rewards breakdown
- message: Status message
Example Success Response (200 OK)
{
"data": {
"earnedRewards": [
{
"transactionId": "67890",
"transactionAmount": 250.75,
"transactionDate": "2023-10-02",
"merchant": "Walmart",
"cardName": "American Express Blue Cash EveryDay",
"rewardValue": 0.01,
"rewardMultiplier": 1,
"rewardAmount": 2.5075
},
{
"transactionId": "12345",
"transactionAmount": 100.5,
"transactionDate": "2023-10-01",
"merchant": "Amazon",
"cardName": "Chase Sapphire Preferred",
"rewardValue": 0.013,
"rewardMultiplier": 1,
"rewardAmount": 1.3065
}
],
"totalEarnedReward": 3.814
},
"message": "Success"
}
Response Field Details
| Field | Type | Description |
| earnedRewards | array | List of calculated rewards, one per input transaction |
| totalEarnedReward | number | Sum of all reward values earned across the input transactions |
| message | string | Operation status message |
Each item in earnedRewards:
| Field | Type | Description |
| transactionId | string | The transaction’s unique reference |
| transactionAmount | number | Spend amount for this transaction |
| transactionDate | string | Purchase date (YYYY-MM-DD) |
| merchant | string | Merchant name |
| cardName | string | The card used |
| rewardValue | number | Dollar equivalent per point/mile for this card |
| rewardMultiplier | number | Multiplier applied (depends on merchant/category) |
| rewardAmount | number | Total reward value earned for the transaction |
Error Responses
| HTTP Code | Meaning | Example Response |
| 400 | Malformed input or missing fields | { “message”: “Invalid request body” } |
| 401 | Authentication failed | { “message”: “Unauthorized” } |
| 500 | Internal server error | { “message”: “Internal error” } |
curl -X POST "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/earned" \
-H "Content-Type: application/json" \
-H "client-id: YOUR_CLIENT_ID" \
-H "api-key: YOUR_API_KEY" \
-d '{
"transactions": [
{
"transactionId": "12345",
"transactionAmount": 100.50,
"transactionDate": "2023-10-01",
"merchant": "Amazon",
"cardName": "Chase Sapphire Preferred"
}
]
}'
const axios = require('axios');
async function postEarnedRewards() {
const url = 'https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/earned';
const headers = {
'Content-Type': 'application/json',
'client-id': 'YOUR_CLIENT_ID',
'api-key': 'YOUR_API_KEY'
};
const data = {
transactions: [
{
transactionId: "12345",
transactionAmount: 100.50,
transactionDate: "2023-10-01",
merchant: "Amazon",
cardName: "Chase Sapphire Preferred"
}
]
};
try {
const response = await axios.post(url, data, { headers });
console.log('Response:', response.data);
} catch (error) {
if (error.response) {
console.error('API Error:', error.response.status, error.response.data);
} else {
console.error('Error:', error.message);
}
}
}
postEarnedRewards();
Async Python example with aiohttp
pip install aiohttp
import aiohttp
import asyncio
async def post_earned_rewards():
url = "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/earned"
headers = {
"Content-Type": "application/json",
"client-id": "YOUR_CLIENT_ID",
"api-key": "YOUR_API_KEY"
}
data = {
"transactions": [
{
"transactionId": "12345",
"transactionAmount": 100.50,
"transactionDate": "2023-10-01",
"merchant": "Amazon",
"cardName": "Chase Sapphire Preferred"
}
]
}
async with aiohttp.ClientSession() as session:
async with session.post(url, json=data, headers=headers) as resp:
print("Status code:", resp.status)
response_json = await resp.json()
print("Response body:", response_json)
# Run the async function
asyncio.run(post_earned_rewards())
Using Spring RestTemplate
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;
import java.util.*;
public class UthriveApiSpring {
public static void main(String[] args) {
RestTemplate restTemplate = new RestTemplate();
String url = "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/earned";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("client-id", "YOUR_CLIENT_ID");
headers.set("api-key", "YOUR_API_KEY");
Map<String, Object> transaction = new HashMap<>();
transaction.put("transactionId", "12345");
transaction.put("transactionAmount", 100.50);
transaction.put("transactionDate", "2023-10-01");
transaction.put("merchant", "Amazon");
transaction.put("cardName", "Chase Sapphire Preferred");
Map<String, Object> reqBody = new HashMap<>();
reqBody.put("transactions", Arrays.asList(transaction));
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(reqBody, headers);
ResponseEntity<String> response = restTemplate.postForEntity(url, entity, String.class);
System.out.println("Status code: " + response.getStatusCodeValue());
System.out.println("Response body: " + response.getBody());
}
}
Java 11+ with Jackson for JSON construction
If you prefer strong typing and JSON serialization
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.*;
import java.util.*;
public class UthriveApiJackson {
public static void main(String[] args) throws Exception {
String url = "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/earned";
HttpClient client = HttpClient.newHttpClient();
Map<String, Object> transaction = new HashMap<>();
transaction.put("transactionId", "12345");
transaction.put("transactionAmount", 100.50);
transaction.put("transactionDate", "2023-10-01");
transaction.put("merchant", "Amazon");
transaction.put("cardName", "Chase Sapphire Preferred");
Map<String, Object> reqBody = new HashMap<>();
reqBody.put("transactions", Arrays.asList(transaction));
ObjectMapper mapper = new ObjectMapper();
String jsonBody = mapper.writeValueAsString(reqBody);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.header("client-id", "YOUR_CLIENT_ID")
.header("api-key", "YOUR_API_KEY")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}
Android Example Using OkHttp
- Add OkHttp to Your build.gradle (Module):
dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
}- Android Java code example
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import okhttp3.*;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
private static final String URL = "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/earned";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// You may wish to run this inside an AsyncTask, background thread, or ViewModel (for production)
new Thread(this::postUthriveEarnedReward).start();
}
private void postUthriveEarnedReward() {
OkHttpClient client = new OkHttpClient();
try {
JSONObject transaction = new JSONObject();
transaction.put("transactionId", "12345");
transaction.put("transactionAmount", 100.50);
transaction.put("transactionDate", "2023-10-01");
transaction.put("merchant", "Amazon");
transaction.put("cardName", "Chase Sapphire Preferred");
JSONArray transactions = new JSONArray();
transactions.put(transaction);
JSONObject requestBodyJson = new JSONObject();
requestBodyJson.put("transactions", transactions);
RequestBody body = RequestBody.create(
requestBodyJson.toString(),
MediaType.get("application/json; charset=utf-8")
);
Request request = new Request.Builder()
.url(URL)
.post(body)
.addHeader("Content-Type", "application/json")
.addHeader("client-id", "YOUR_CLIENT_ID")
.addHeader("api-key", "YOUR_API_KEY")
.build();
Response response = client.newCall(request).execute();
if (response.isSuccessful() && response.body() != null) {
String responseStr = response.body().string();
runOnUiThread(() -> {
// Handle the API response (update UI, Toast, Log, etc.)
// Example:
System.out.println("Response: " + responseStr);
});
} else {
runOnUiThread(() -> {
System.err.println("Error: " + response.code());
});
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Best Practices
- transactionId can be any string unique to a transaction in your system.
- Ensure cardName matches Uthrive’s supported list for best accuracy.
- The sum of rewardAmount fields will equal totalEarnedReward.
Calculate Missed Rewards
POST /reward/missedURL: https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/missed
Calculate missed rewards for a batch of transactions, showing how much additional reward value a user could have earned if they had used the optimal card—including both cards they already have and cards they may want to apply for.
Authentication
- Required.
- Supply client-id and api-key headers with every request
Request Headers
| Header | Type | Required | Description |
| client-id | string | Yes | Your Uthrive client ID |
| api-key | string | Yes | Your Uthrive API key |
| Content-Type | string | Yes | application/json |
Request Body Schema
| Field | Type | Required | Description |
| transactions | array | Yes | List of transaction objects (see below) |
| yourCards | array | Yes | List of card names you currently hold |
Each transaction object:
| Field | Type | Required | Description |
| transactionId | string | Yes | Unique ID |
| transactionAmount | number | Yes | Spend amount |
| transactionDate | string | Yes | YYYY-MM-DD |
| merchant | string | Yes | Merchant name |
| cardName | string | Yes | Card used |
yourCards is an array of card name strings (must match Uthrive’s supported cards list).
Example Request
{
"transactions": [
{
"transactionId": "12345",
"transactionAmount": 100.5,
"transactionDate": "2023-10-01",
"merchant": "Amazon",
"cardName": "Chase Sapphire Preferred"
}
],
"yourCards": [
"American Express Platinum",
"Chase Sapphire Preferred"
]
}
Success Response (200 OK)
Returns a breakdown of:
- missed rewards if a different card in the user’s wallet (yourCards) was used,
- missed rewards if a potential new card (not in wallet) was used, and summary statistics.
{
"data": {
"missedRewards": [
{
"transactionId": "12345",
"transactionAmount": 100.5,
"transactionDate": "2023-10-01",
"merchant": "Amazon",
"cardName": "Chase Sapphire Preferred",
"rewardAmount": 1.3065,
"missedRewardYourCards": [
{
"cardId": 43,
"cardName": "Capital One QuickSilver",
"transactionId": "12345",
"rewardValue": 0.01,
"rewardMultiplier": 1.5,
"missedReward": 0.201
}
],
"missedRewardNewCards": [
{
"cardId": 99,
"cardName": "Amex Green",
"transactionId": "12345",
"rewardValue": 0.02,
"rewardMultiplier": 2,
"missedReward": 0.5
}
],
"bestCard": {
"cardId": 99,
"cardName": "Amex Green",
"transactionId": "12345",
"rewardValue": 0.02,
"rewardMultiplier": 2,
"missedReward": 0.5
}
}
],
"overallMissedRewardsExisting": 0.201,
"overallMissedRewardsNew": 0.5
},
"message": "Success"
}Response Fields
Top-level:
| Field | Type | Description |
| data.missedRewards | array | List of per-transaction missed reward breakdowns |
| data.overallMissedRewardsExisting | number | Total missed by not using the best card you have |
| data.overallMissedRewardsNew | number | Total missed if you had access to all cards |
| message | string | Success or error string |
Inside each object in missedRewards:
| Field | Type | Description |
| transactionId | string | The transaction’s unique ID |
| transactionAmount | number | Spend amount |
| transactionDate | string | Purchase date (YYYY-MM-DD) |
| merchant | string | Merchant |
| cardName | string | The card actually used for the transaction |
| rewardAmount | number | Rewards earned with the actual card used |
| missedRewardYourCards | array | Missed rewards for other cards you hold |
| missedRewardNewCards | array | Missed rewards if you had other (potential) cards |
| bestCard | object | Theoretical card with maximum possible reward |
Each object in missedRewardYourCards and missedRewardNewCards:
| Field | Type | Description |
| cardId | integer | Uthrive-internal card ID |
| cardName | string | Card name |
| transactionId | string | Transaction ID |
| rewardValue | number | $ value per point/mile |
| rewardMultiplier | number | Points multiplier |
| missedReward | number | Extra dollars you would have earned |
curl -X POST "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/missed" \
-H "Content-Type: application/json" \
-H "client-id: YOUR_CLIENT_ID" \
-H "api-key: YOUR_API_KEY" \
-d '{
"transactions": [
{
"transactionId": "12345",
"transactionAmount": 100.5,
"transactionDate": "2023-10-01",
"merchant": "Amazon",
"cardName": "Chase Sapphire Preferred"
}
],
"yourCards": [
"American Express Platinum",
"Chase Sapphire Preferred"
]
}'
- Install Axios
npm install axios- Async/Await Node.js Script with Error Handling
const axios = require('axios');
async function postMissedRewards() {
const url = "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/missed";
const headers = {
'Content-Type': 'application/json',
'client-id': 'YOUR_CLIENT_ID',
'api-key': 'YOUR_API_KEY'
};
const data = {
transactions: [
{
transactionId: "12345",
transactionAmount: 100.5,
transactionDate: "2023-10-01",
merchant: "Amazon",
cardName: "Chase Sapphire Preferred"
}
],
yourCards: [
"American Express Platinum",
"Chase Sapphire Preferred"
]
};
try {
const response = await axios.post(url, data, { headers });
console.log('Status:', response.status);
console.log('Response:', JSON.stringify(response.data, null, 2));
} catch (error) {
if (error.response) {
// Server responded with an error status
console.error('API Error:', error.response.status);
console.error('Error body:', JSON.stringify(error.response.data, null, 2));
} else if (error.request) {
// Request was made but no response
console.error('No response received:', error.request);
} else {
// Error in setting up the request
console.error('Request setup error:', error.message);
}
}
}
postMissedRewards();
- Using Aync/Await with error handling
pip install requests
import requests
url = "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/missed"
headers = {
"Content-Type": "application/json",
"client-id": "YOUR_CLIENT_ID",
"api-key": "YOUR_API_KEY"
}
data = {
"transactions": [
{
"transactionId": "12345",
"transactionAmount": 100.5,
"transactionDate": "2023-10-01",
"merchant": "Amazon",
"cardName": "Chase Sapphire Preferred"
}
],
"yourCards": [
"American Express Platinum",
"Chase Sapphire Preferred"
]
}
try:
response = requests.post(url, json=data, headers=headers, timeout=10)
response.raise_for_status() # Raises HTTPError for bad HTTP status codes
print("Status:", response.status_code)
print("Response:", response.json())
except requests.exceptions.HTTPError as e:
print(f"HTTP error occurred: {e} - Response body: {response.text}")
except requests.exceptions.ConnectionError as e:
print("A connection error occurred:", e)
except requests.exceptions.Timeout as e:
print("Request timed out:", e)
except requests.exceptions.RequestException as e:
print("An error occurred while making the request:", e)
- Using Async request with aiohttp
import asyncio
import aiohttp
async def post_missed_rewards():
url = "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/missed"
headers = {
"Content-Type": "application/json",
"client-id": "YOUR_CLIENT_ID",
"api-key": "YOUR_API_KEY"
}
data = {
"transactions": [
{
"transactionId": "12345",
"transactionAmount": 100.5,
"transactionDate": "2023-10-01",
"merchant": "Amazon",
"cardName": "Chase Sapphire Preferred"
}
],
"yourCards": [
"American Express Platinum",
"Chase Sapphire Preferred"
]
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(url, json=data, headers=headers, timeout=10) as resp:
print("Status:", resp.status)
resp_json = await resp.json()
print("Response:", resp_json)
resp.raise_for_status()
except aiohttp.ClientResponseError as e:
print(f"HTTP error occurred: {e.status} - {e.message}")
except aiohttp.ClientConnectionError as e:
print("A connection error occurred:", e)
except asyncio.TimeoutError as e:
print("Request timed out:", e)
except Exception as e:
print("An error occurred while making the request:", e)
asyncio.run(post_missed_rewards())
Using standard HttpClient API
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class UthriveMissedRewardExample {
public static void main(String[] args) {
String url = "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/missed";
String clientId = "YOUR_CLIENT_ID";
String apiKey = "YOUR_API_KEY";
String jsonBody = """
{
"transactions": [
{
"transactionId": "12345",
"transactionAmount": 100.5,
"transactionDate": "2023-10-01",
"merchant": "Amazon",
"cardName": "Chase Sapphire Preferred"
}
],
"yourCards": [
"American Express Platinum",
"Chase Sapphire Preferred"
]
}
""";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.header("client-id", clientId)
.header("api-key", apiKey)
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
int status = response.statusCode();
String body = response.body();
System.out.println("Status: " + status);
System.out.println("Response body:\n" + body);
if (status >= 200 && status < 300) {
// Success
// Optionally, parse the JSON here using a library like Jackson or Gson
} else {
System.err.println("API returned an error. Status: " + status);
}
} catch (IOException e) {
System.err.println("I/O Exception during API call: " + e.getMessage());
e.printStackTrace();
} catch (InterruptedException e) {
System.err.println("Request was interrupted: " + e.getMessage());
Thread.currentThread().interrupt();
} catch (Exception e) {
System.err.println("Unexpected error: " + e.getMessage());
e.printStackTrace();
}
}
}
Using Spring
If you are using Maven
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.30</version> <!-- or your compatible version -->
</dependency>If you use Gradle
implementation 'org.springframework:spring-web:5.3.30'Spring RestTemplate example
import org.springframework.http.*;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.util.*;
public class UthriveSpringExample {
public static void main(String[] args) {
String url = "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/missed";
// Build the request JSON as Java Maps
Map<String, Object> transaction = new HashMap<>();
transaction.put("transactionId", "12345");
transaction.put("transactionAmount", 100.5);
transaction.put("transactionDate", "2023-10-01");
transaction.put("merchant", "Amazon");
transaction.put("cardName", "Chase Sapphire Preferred");
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("transactions", Arrays.asList(transaction));
requestBody.put("yourCards", Arrays.asList("American Express Platinum", "Chase Sapphire Preferred"));
// Set HTTP headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("client-id", "YOUR_CLIENT_ID");
headers.set("api-key", "YOUR_API_KEY");
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
RestTemplate restTemplate = new RestTemplate();
try {
ResponseEntity<String> response = restTemplate.exchange(
url, HttpMethod.POST, entity, String.class);
int statusCode = response.getStatusCodeValue();
System.out.println("Status: " + statusCode);
System.out.println("Body:\n" + response.getBody());
if (statusCode >= 200 && statusCode < 300) {
// Success, parse the JSON if needed (Jackson auto-mapping available)
} else {
System.err.println("API returned non-success status.");
}
} catch (RestClientException e) {
System.err.println("Exception during API request: " + e.getMessage());
}
}
}
Using Spring WebClient with error handling.
WebClient is a part of Spring WebFlux, suitable for both reactive and non-blocking use cases in modern Spring Boot projects.
Add dependency
For maven , add to pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
For Gradle:
implementation 'org.springframework.boot:spring-boot-starter-webflux'
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.util.*;
public class UthriveWebClientExample {
public static void main(String[] args) {
String url = "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/missed";
Map<String, Object> transaction = new HashMap<>();
transaction.put("transactionId", "12345");
transaction.put("transactionAmount", 100.5);
transaction.put("transactionDate", "2023-10-01");
transaction.put("merchant", "Amazon");
transaction.put("cardName", "Chase Sapphire Preferred");
Map<String, Object> body = new HashMap<>();
body.put("transactions", List.of(transaction));
body.put("yourCards", List.of("American Express Platinum", "Chase Sapphire Preferred"));
WebClient webClient = WebClient.builder()
.baseUrl(url)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader("client-id", "YOUR_CLIENT_ID")
.defaultHeader("api-key", "YOUR_API_KEY")
.build();
webClient.post()
.body(BodyInserters.fromValue(body))
.retrieve()
.toEntity(String.class)
.doOnSuccess(response -> {
System.out.println("Status: " + response.getStatusCode().value());
System.out.println("Body:\n" + response.getBody());
})
.doOnError(error -> {
System.err.println("Error during API request: " + error.getMessage());
})
.block(); // For demo main method usage; in production use subscribe() or chain in reactive flow
}
}
Controller/Service WebFlux example with POJO mapping
Project Setup
Add to your pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Or with Gradle
implementation 'org.springframework.boot:spring-boot-starter-webflux'POJO Model Classes
// MissedRewardResponse.java
public class MissedRewardResponse {
private Data data;
private String message;
// getters and setters
public static class Data {
private double overallMissedRewardsExisting;
private double overallMissedRewardsNew;
// Add missedRewards etc. as needed
// getters and setters
}
}
Service Layer using WebClient
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Map;
@Service
public class UthriveService {
private final WebClient webClient;
public UthriveService(
@Value("${uthrive.api.client-id}") String clientId,
@Value("${uthrive.api.api-key}") String apiKey
) {
this.webClient = WebClient.builder()
.baseUrl("https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader("client-id", clientId)
.defaultHeader("api-key", apiKey)
.build();
}
public Mono<MissedRewardResponse> getMissedRewards(List<Map<String, Object>> transactions, List<String> yourCards) {
Map<String, Object> body = Map.of(
"transactions", transactions,
"yourCards", yourCards
);
return webClient.post()
.uri("/reward/missed")
.body(BodyInserters.fromValue(body))
.retrieve()
.bodyToMono(MissedRewardResponse.class);
}
}
Controller Layer
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class UthriveController {
@Autowired
private UthriveService uthriveService;
@PostMapping("/reward/missed/proxy")
public Mono<MissedRewardResponse> proxyMissedReward(@RequestBody MissedRewardRequest request) {
return uthriveService.getMissedRewards(request.getTransactions(), request.getYourCards());
}
}
// --- You would also need this request DTO for clean deserialization ---
class MissedRewardRequest {
private List<Map<String, Object>> transactions;
private List<String> yourCards;
public List<Map<String, Object>> getTransactions() { return transactions; }
public void setTransactions(List<Map<String, Object>> transactions) { this.transactions = transactions; }
public List<String> getYourCards() { return yourCards; }
public void setYourCards(List<String> yourCards) { this.yourCards = yourCards; }
}
Example using OkHttp for the HttpClient and Gson for deserialization
Add dependencies in build.gradle
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.google.code.gson:gson:2.10.1'Model classes (POJOs)
public class MissedRewardResponse {
private Data data;
private String message;
public Data getData() { return data; }
public String getMessage() { return message; }
public static class Data {
private double overallMissedRewardsExisting;
private double overallMissedRewardsNew;
// Add other fields as needed
public double getOverallMissedRewardsExisting() { return overallMissedRewardsExisting; }
public double getOverallMissedRewardsNew() { return overallMissedRewardsNew; }
// Add other getters/setters...
}
}
Make the HTTP request
Run as a background thread
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import okhttp3.*;
import com.google.gson.Gson;
import java.io.IOException;
import java.util.*;
public class MainActivity extends AppCompatActivity {
private static final String URL = "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/reward/missed";
private static final Gson gson = new Gson();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Run in a background thread:
new Thread(this::postMissedReward).start();
}
private void postMissedReward() {
OkHttpClient client = new OkHttpClient();
Map<String, Object> transaction = new HashMap<>();
transaction.put("transactionId", "12345");
transaction.put("transactionAmount", 100.5);
transaction.put("transactionDate", "2023-10-01");
transaction.put("merchant", "Amazon");
transaction.put("cardName", "Chase Sapphire Preferred");
Map<String, Object> requestBodyMap = new HashMap<>();
requestBodyMap.put("transactions", Collections.singletonList(transaction));
requestBodyMap.put("yourCards", Arrays.asList("American Express Platinum", "Chase Sapphire Preferred"));
String requestBodyStr = gson.toJson(requestBodyMap);
RequestBody body = RequestBody.create(
requestBodyStr,
MediaType.parse("application/json; charset=utf-8")
);
Request request = new Request.Builder()
.url(URL)
.post(body)
.addHeader("Content-Type", "application/json")
.addHeader("client-id", "YOUR_CLIENT_ID")
.addHeader("api-key", "YOUR_API_KEY")
.build();
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful() && response.body() != null) {
String responseBody = response.body().string();
MissedRewardResponse missedRewardResponse = gson.fromJson(responseBody, MissedRewardResponse.class);
runOnUiThread(() -> {
// Use your POJO here (update UI, etc.)
System.out.println("Success! Message: " + missedRewardResponse.getMessage());
System.out.println("Overall Missed Existing: " + missedRewardResponse.getData().getOverallMissedRewardsExisting());
});
} else {
runOnUiThread(() -> {
System.err.println("API error: " + response.code());
});
}
} catch (IOException e) {
runOnUiThread(() -> {
System.err.println("Network or parsing error: " + e.getMessage());
});
}
}
}
Use WorkManager/LiveData/Coroutines for modern Android apps. To update the UI, use runOnUiThread only when necessary.
Error Responses
| HTTP Code | Description | Example Response |
| 400 | Malformed input, missing fields | { “message”: “Invalid request body” } |
| 401 | Authentication failed | { “message”: “Unauthorized” } |
| 500 | Internal server error | { “message”: “Internal error” } |
Best Practices
- Card name values in both transactions.cardName and yourCards must match the supported card list for accurate matching.
- Use this endpoint after transactions post to help users see real, actionable “missed value.”
- Use overallMissedRewardsExisting to surface “missed with your current cards” and overallMissedRewardsNew for “missed if you had all available cards.”
Best card recommendation
POST /recommend/adviceURL: https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/recommend/advice
Get the best personalized card usage advice for a specific purchase scenario. This endpoint tells you which of the user’s cards (and optionally, any new/potential cards) will maximize rewards for a given merchant and spend amount.
Authentication
- Required: Include client-id and api-key headers in every request
Request Headers
| Header | Type | Required | Description |
| client-id | string | Yes | Your Uthrive client ID |
| api-key | string | Yes | Your Uthrive API key |
| Content-Type | string | Yes | application/json |
Request Body Schema
| Field | Type | Required | Description |
| merchant | string | Yes | Merchant name (e.g., “Amazon”) |
| yourCards | array | Yes | List of card names you currently hold |
| spendAmount | number | Yes | Amount (USD) to be spent |
Example Request:
{
"merchant": "Amazon",
"yourCards": [
"American Express Platinum",
"Chase Sapphire Preferred"
],
"spendAmount": 300
}
Success Response (200 OK)
Returns:
- The best card in the user’s wallet for this merchant/spend amount.
- Other cards in the wallet, with their respective reward calculations (ordered by reward value).
Example:
{
"data": {
"bestCard": {
"cardName": "Chase Sapphire Preferred",
"rewardValue": 0.013,
"rewardMultiplier": 1,
"rewardAmount": 3.9,
"bonus": 0.013
},
"otherCards": [
{
"cardName": "American Express Platinum",
"rewardValue": 0.01,
"rewardMultiplier": 1,
"rewardAmount": 3,
"bonus": 0.01
}
]
},
"message": "Success"
}
Response Fields
Top-level:
| Field | Type | Description |
| data | object | Card advice data (see below) |
| message | string | Status message |
Inside data:
| Field | Type | Description |
| bestCard | object | Card with the maximum total reward for this scenario |
| otherCards | array | Other cards in wallet, with computed reward amounts |
Each card object:
| Field | Type | Description |
| cardName | string | Card name |
| rewardValue | number | $ value per reward point/mile |
| rewardMultiplier | number | Multiplier for this merchant/amount |
| rewardAmount | number | Total reward earned for spendAmount (in USD) |
| bonus | number | Bonus reward value, if applicable |
curl -X POST "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/recommend/advice" \
-H "Content-Type: application/json" \
-H "client-id: YOUR_CLIENT_ID" \
-H "api-key: YOUR_API_KEY" \
-d '{
"merchant": "Amazon",
"yourCards": [
"American Express Platinum",
"Chase Sapphire Preferred"
],
"spendAmount": 300
}'
- Install dependencies
npm install axios
npm install --save-dev typescript @types/node- Create recommendAdvice.ts
import axios from 'axios';
// ---- POJO Type Definitions ----
interface CardReward {
cardName: string;
rewardValue: number;
rewardMultiplier: number;
rewardAmount: number;
bonus: number;
}
interface AdviceResponse {
data: {
bestCard: CardReward;
otherCards: CardReward[];
};
message: string;
}
async function getRecommendationAdvice(): Promise<void> {
const url = "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/recommend/advice";
const headers = {
"Content-Type": "application/json",
"client-id": "YOUR_CLIENT_ID",
"api-key": "YOUR_API_KEY"
};
const body = {
merchant: "Amazon",
yourCards: [
"American Express Platinum",
"Chase Sapphire Preferred"
],
spendAmount: 300
};
try {
const response = await axios.post<AdviceResponse>(url, body, { headers });
const advice = response.data;
// POJO mapping in action
console.log("Best Card Advice:", advice.data.bestCard);
advice.data.otherCards.forEach((card, i) => {
console.log(`Other Card ${i + 1}:`, card);
});
console.log("Message:", advice.message);
// Example individual field access:
// console.log("Best card name:", advice.data.bestCard.cardName);
} catch (error: any) {
if (error.response) {
console.error('API Error:', error.response.status);
console.error('Error response body:', error.response.data);
} else if (error.request) {
console.error('No response received:', error.request);
} else {
console.error('Request setup error:', error.message);
}
}
}
// Run the function
getRecommendationAdvice();
The above example returns a strongly-typed POJO (AdviceResponse and CardReward)—true POJO mapping as in Java.
Object oriented approach
- Define Python classes for POJO mapping
from typing import List, Optional
class CardReward:
def __init__(self, cardName: str, rewardValue: float, rewardMultiplier: float, rewardAmount: float, bonus: float):
self.card_name = cardName
self.reward_value = rewardValue
self.reward_multiplier = rewardMultiplier
self.reward_amount = rewardAmount
self.bonus = bonus
@classmethod
def from_dict(cls, data):
return cls(
cardName=data.get("cardName"),
rewardValue=data.get("rewardValue"),
rewardMultiplier=data.get("rewardMultiplier"),
rewardAmount=data.get("rewardAmount"),
bonus=data.get("bonus")
)
def __repr__(self):
return (f"CardReward(card_name={self.card_name!r}, reward_value={self.reward_value}, "
f"reward_multiplier={self.reward_multiplier}, reward_amount={self.reward_amount}, bonus={self.bonus})")
class AdviceResponse:
def __init__(self, best_card: CardReward, other_cards: List[CardReward], message: str):
self.best_card = best_card
self.other_cards = other_cards
self.message = message
@classmethod
def from_dict(cls, data):
best_card = CardReward.from_dict(data["data"]["bestCard"])
other_cards = [CardReward.from_dict(card) for card in data["data"].get("otherCards", [])]
message = data.get("message", "")
return cls(best_card, other_cards, message)
def __repr__(self):
return (f"AdviceResponse(best_card={self.best_card}, other_cards={self.other_cards}, message={self.message!r})")
- Request Function With Error Handling
import requests
def get_recommendation_advice(client_id: str, api_key: str) -> Optional[AdviceResponse]:
url = "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/recommend/advice"
headers = {
"Content-Type": "application/json",
"client-id": client_id,
"api-key": api_key
}
data = {
"merchant": "Amazon",
"yourCards": [
"American Express Platinum",
"Chase Sapphire Preferred"
],
"spendAmount": 300
}
try:
response = requests.post(url, json=data, headers=headers, timeout=10)
response.raise_for_status()
advice = AdviceResponse.from_dict(response.json())
return advice
except requests.exceptions.HTTPError as e:
print(f"HTTP error occurred: {e} - Response body: {getattr(e.response, 'text', '')}")
except requests.exceptions.ConnectionError as e:
print("A connection error occurred:", e)
except requests.exceptions.Timeout as e:
print("Request timed out:", e)
except requests.exceptions.RequestException as e:
print("A general error occurred while making the request:", e)
except Exception as e:
print("Unexpected error:", e)
return None
- Example Usage
if __name__ == "__main__":
CLIENT_ID = "YOUR_CLIENT_ID"
API_KEY = "YOUR_API_KEY"
advice = get_recommendation_advice(CLIENT_ID, API_KEY)
if advice:
print("Best Card:")
print(advice.best_card)
print("\nOther Cards:")
for card in advice.other_cards:
print(card)
print("\nMessage:", advice.message)
Using aiohttp async library with DTO validation
Install prerequisites
pip install aiohttp pydanticimport asyncio
import aiohttp
from pydantic import BaseModel, ValidationError, conlist, constr, condecimal
from typing import List, Optional
# --- DTOs and Response Mapping ---
class AdviceRequest(BaseModel):
merchant: constr(min_length=1)
yourCards: conlist(constr(min_length=2), min_items=1)
spendAmount: condecimal(ge=0)
class CardReward(BaseModel):
cardName: str
rewardValue: float
rewardMultiplier: float
rewardAmount: float
bonus: float
class AdviceData(BaseModel):
bestCard: CardReward
otherCards: List[CardReward]
class AdviceResponseDTO(BaseModel):
data: AdviceData
message: str
# --- Async Function ---
async def get_recommendation_advice(client_id: str, api_key: str, request_body: AdviceRequest) -> Optional[AdviceResponseDTO]:
url = "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/recommend/advice"
headers = {
"Content-Type": "application/json",
"client-id": client_id,
"api-key": api_key,
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(url, json=request_body.dict(), headers=headers, timeout=10) as resp:
print("Status:", resp.status)
resp_json = await resp.json()
if resp.status >= 200 and resp.status < 300:
try:
advice = AdviceResponseDTO.parse_obj(resp_json)
return advice
except ValidationError as ve:
print("Response validation error:", ve)
else:
print('API Error:', resp.status, resp_json)
except asyncio.TimeoutError:
print("Request timed out")
except aiohttp.ClientConnectionError as e:
print("Connection error:", e)
except Exception as e:
print("Error:", e)
return None
# --- Example usage ---
async def main():
CLIENT_ID = "YOUR_CLIENT_ID"
API_KEY = "YOUR_API_KEY"
try:
# Validate DTO input before request!
request_body = AdviceRequest(
merchant="Amazon",
yourCards=[
"American Express Platinum",
"Chase Sapphire Preferred"
],
spendAmount=300
)
except ValidationError as ve:
print("Input validation error:", ve)
return
advice = await get_recommendation_advice(CLIENT_ID, API_KEY, request_body)
if advice:
print("\nBest Card:")
print(advice.data.bestCard)
print("\nOther Cards:")
for card in advice.data.otherCards:
print(card)
print("\nMessage:", advice.message)
if __name__ == "__main__":
asyncio.run(main())
How this works:
- DTO Validation: Input (your request body) is validated with pydantic, ensuring correct schema before the request.
- HTTP POST with aiohttp, with async/await for maximum efficiency.
- Response Mapping: JSON result is mapped to a pydantic object (AdviceResponseDTO). Any validation error is clearly printed.
- Error Handling: Handles timeouts, connection errors, and reports validation errors (both request and response)
Spring Boot with WebFlux
- Add dependencies for Maven projects
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Optionally, Lombok for POJOs: -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
- POJO (DTO) classes
AdviceRequest.java
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class AdviceRequest {
private String merchant;
private List<String> yourCards;
private BigDecimal spendAmount;
}
CardReward.java
import lombok.Data;
@Data
public class CardReward {
private String cardName;
private double rewardValue;
private double rewardMultiplier;
private double rewardAmount;
private double bonus;
}
AdviceData.java
import lombok.Data;
import java.util.List;
@Data
public class AdviceData {
private CardReward bestCard;
private List<CardReward> otherCards;
}
AdviceResponseDTO.java
import lombok.Data;
@Data
public class AdviceResponseDTO {
private AdviceData data;
private String message;
}
WebClientServiceBean
UthriveAdviceService.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Mono;
@Service
public class UthriveAdviceService {
private final WebClient webClient;
public UthriveAdviceService(
@Value("${uthrive.api.client-id}") String clientId,
@Value("${uthrive.api.api-key}") String apiKey
) {
this.webClient = WebClient.builder()
.baseUrl("https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader("client-id", clientId)
.defaultHeader("api-key", apiKey)
.build();
}
public Mono<AdviceResponseDTO> getAdvice(AdviceRequest request) {
return webClient.post()
.uri("/recommend/advice")
.bodyValue(request)
.retrieve()
.onStatus(status -> !status.is2xxSuccessful(),
response -> response.bodyToMono(String.class)
.flatMap(body -> Mono.error(new RuntimeException("Api error: " + body)))
)
.bodyToMono(AdviceResponseDTO.class)
.doOnError(WebClientResponseException.class, ex -> {
System.err.println("WebClientResponseException: " + ex.getRawStatusCode() + " " + ex.getResponseBodyAsString());
})
.doOnError(Exception.class, ex -> {
System.err.println("General exception: " + ex.getMessage());
});
}
}
Controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/api/uthrive")
public class UthriveAdviceController {
@Autowired
private UthriveAdviceService adviceService;
@PostMapping("/recommend/advice")
public Mono<ResponseEntity<AdviceResponseDTO>> recommendAdvice(@RequestBody AdviceRequest request) {
return adviceService.getAdvice(request)
.map(ResponseEntity::ok)
.onErrorResume(e -> Mono.just(ResponseEntity.badRequest().body(null)));
}
}
Configure your credentials In application.properties or application.yml
uthrive.api.client-id=YOUR_CLIENT_ID
uthrive.api.api-key=YOUR_API_KEY
- Add dependencies in your build.gradle
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.google.code.gson:gson:2.10.1'
- Create POJO(DTOs) for mapping
AdviceRequest.java
import java.util.List;
public class AdviceRequest {
private String merchant;
private List<String> yourCards;
private double spendAmount;
public AdviceRequest(String merchant, List<String> yourCards, double spendAmount) {
this.merchant = merchant;
this.yourCards = yourCards;
this.spendAmount = spendAmount;
}
// getters and setters (or use Lombok)...
}
CardReward.java
public class CardReward {
private String cardName;
private double rewardValue;
private double rewardMultiplier;
private double rewardAmount;
private double bonus;
// getters and setters...
}AdviceData.java
import java.util.List;
public class AdviceData {
private CardReward bestCard;
private List<CardReward> otherCards;
// getters and setters...
}
AdviceResponseDTO.java
public class AdviceResponseDTO {
private AdviceData data;
private String message;
public AdviceData getData() { return data; }
public String getMessage() { return message; }
}
Service class
UthriveAdviceService.java
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import okhttp3.*;
public class UthriveAdviceService {
private final String url = "https://h0n277p4vd.execute-api.us-east-1.amazonaws.com/staging/recommend/advice";
private final OkHttpClient client = new OkHttpClient();
private final Gson gson = new Gson();
private final String clientId;
private final String apiKey;
private final Executor executor = Executors.newSingleThreadExecutor();
public UthriveAdviceService(String clientId, String apiKey) {
this.clientId = clientId;
this.apiKey = apiKey;
}
public interface Callback {
void onSuccess(AdviceResponseDTO advice);
void onError(Exception e);
}
public void getRecommendationAdvice(AdviceRequest adviceRequest, Callback callback) {
executor.execute(() -> {
try {
RequestBody body = RequestBody.create(
gson.toJson(adviceRequest),
MediaType.get("application/json; charset=utf-8")
);
Request request = new Request.Builder()
.url(url)
.post(body)
.addHeader("Content-Type", "application/json")
.addHeader("client-id", clientId)
.addHeader("api-key", apiKey)
.build();
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful() && response.body() != null) {
String responseStr = response.body().string();
try {
AdviceResponseDTO adviceResp = gson.fromJson(responseStr, AdviceResponseDTO.class);
callback.onSuccess(adviceResp);
} catch (JsonSyntaxException je) {
callback.onError(new Exception("Response parsing error: " + je.getMessage()));
}
} else {
callback.onError(new Exception("API Error: Code " + response.code() + " Body: " +
(response.body() != null ? response.body().string() : "")));
}
}
} catch (IOException e) {
callback.onError(new Exception("Network error: " + e.getMessage(), e));
} catch (Exception e) {
callback.onError(e);
}
});
}
}
Use in your Activity/Fragment
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Arrays;
public class MainActivity extends AppCompatActivity {
private static final String CLIENT_ID = "YOUR_CLIENT_ID";
private static final String API_KEY = "YOUR_API_KEY";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
UthriveAdviceService service = new UthriveAdviceService(CLIENT_ID, API_KEY);
AdviceRequest req = new AdviceRequest(
"Amazon",
Arrays.asList("American Express Platinum", "Chase Sapphire Preferred"),
300.0
);
service.getRecommendationAdvice(req, new UthriveAdviceService.Callback() {
@Override
public void onSuccess(AdviceResponseDTO advice) {
runOnUiThread(() -> {
// Access POJO fields
if (advice != null && advice.getData() != null) {
CardReward best = advice.getData().getBestCard();
if (best != null) {
System.out.println("Best card: " + best.getCardName() + " Reward: " + best.getRewardAmount());
}
// And so on...
}
});
}
@Override
public void onError(Exception e) {
runOnUiThread(() -> {
System.err.println("Error: " + e.getMessage());
});
}
});
}
}
Error Responses
| HTTP Code | Description | Example Response |
| 400 | Malformed or incomplete input | { “message”: “Invalid request body” } |
| 401 | Authentication failed | { “message”: “Unauthorized” } |
| 500 | Internal server error | { “message”: “Internal error” } |
Usage
- The Merchant must match a recognized merchant in Uthrive’s system for best results.
- yourCards must be an array of the user’s actual card names (must match Uthrive’s supported list).
- All returned reward calculations assume the provided spend amount and merchant.
- Use this endpoint for real-time “which card should I use?” advice in apps, widgets, or POS scenarios.
