Credit Card APR Refresh
This guide walks through how to request a refreshed Annual Percentage Rate (APR) for a credit card and read the updated value back from Spinwheel.
Overview
Credit Card APR Refresh lets you request an up-to-date APR for a user's credit card. At a high level, you will:
- Retrieve the user and check whether the credit card supports APR refresh.
- Start a liability refresh request with the
ANNUAL_PERCENTAGE_RATEcapability. - Wait for a terminal refresh status by webhook, or poll the refresh status endpoint.
- Retrieve the user again and read the refreshed APR from
data.creditCards[].aprs.
APR refresh is an on-demand, asynchronous request.
How This Fits With Other Refresh Flows
If you have already integrated another Spinwheel refresh flow, APR refresh should feel familiar:
- Like Real-Time Balance, you check a liability capability, start a refresh, wait for status, and then retrieve the user.
- You request a specific capability through
POST /v1/users/{userId}/liabilities/refresh. - APR refresh is a one-time request. It does not create a weekly or monthly subscription.
Before You Start
Use these rules to keep your integration predictable:
- Check the capabilities matrix before every refresh request.
- Only request APR for credit cards where
annualPercentageRate.availabilityisSUPPORTED. - Use a unique
extRequestIdfor every refresh request. - Subscribe to
REFRESH_TRANSACTION_STATUSwebhooks so you do not have to poll. - Call
GET /v1/usersto read the final APR value, when needed.
Real-time APR retrieval is not available for every credit card or institution. If a card is not eligible, the capabilities matrix will explain why.
Step 1: Check APR Eligibility
Each credit card returned by GET /v1/users includes a capabilities matrix. For APR refresh, check data.creditCards[].capabilities.data.annualPercentageRate.
Supported Example:
{
"capabilities": {
"data": {
"annualPercentageRate": {
"availability": "SUPPORTED",
"nextEligibleRefreshOn": 1770000000000
}
}
}
}Unsupported Example:
{
"capabilities": {
"data": {
"annualPercentageRate": {
"availability": "NOT_SUPPORTED",
"description": "INSTITUTION_NOT_SUPPORTED"
}
}
}
}If availability is not SUPPORTED, do not send a refresh request for that card. The request will not succeed.
When nextEligibleRefreshOn is present, it is the earliest time, in epoch milliseconds, when another APR refresh can be requested for that card.
Step 2: Start the APR Refresh
Call POST /v1/users/:userId/liabilities/refresh and include the credit card ID with the ANNUAL_PERCENTAGE_RATE capability.
{
"extRequestId": "partner-request-123",
"creditCards": [
{
"id": "007035e0-7865-49f3-be50-c922b1c63dc6",
"capabilities": ["ANNUAL_PERCENTAGE_RATE"]
}
]
}The response confirms that the refresh has started. APR refresh rows use capability: "ANNUAL_PERCENTAGE_RATE".
{
"status": {
"code": 200,
"desc": "success"
},
"data": [
{
"refreshTransactionStatus": "IN_PROGRESS",
"refreshTransactionId": "b790770b-33c7-49d4-b086-e6e18864ba34",
"creditCardId": "007035e0-7865-49f3-be50-c922b1c63dc6",
"capability": "ANNUAL_PERCENTAGE_RATE",
"nextEligibleRefreshOn": 1770000000000
}
]
}You can request APR and real-time balance in the same call if the card supports both capabilities. Each card and capability pair is processed as its own refresh transaction.
{
"extRequestId": "partner-request-124",
"creditCards": [
{
"id": "007035e0-7865-49f3-be50-c922b1c63dc6",
"capabilities": ["REALTIME_BALANCE","ANNUAL_PERCENTAGE_RATE"]
}
]
}For multi-card APR requests, Spinwheel validates every requested APR card before creating any APR refresh transaction. If one requested card is unsupported, the request returns a 400 instead of creating a partial set of APR refreshes.
Step 3: Track Refresh Status
APR refresh is asynchronous. The initial API response tells you the refresh is IN_PROGRESS; it does not mean the APR is ready yet.
The recommended approach is to subscribe to the REFRESH_TRANSACTION_STATUS webhook. Spinwheel sends this event when the refresh reaches a terminal state.
You can also poll GET /v1/users/:userId/liabilities/refresh/:extRequestId.
Polling returns refresh transaction status rows. It does not return APR values.
{
"status": {
"code": 200,
"desc": "success"
},
"data": [
{
"refreshTransactionStatus": "COMPLETED",
"refreshTransactionId": "b790770b-33c7-49d4-b086-e6e18864ba34",
"creditCardId": "007035e0-7865-49f3-be50-c922b1c63dc6",
"capability": "ANNUAL_PERCENTAGE_RATE",
"nextEligibleRefreshOn": 1770000000000
}
]
}Step 4: Retrieve the Updated APR
After the refresh status is COMPLETED, call GET /v1/users and read data.creditCards[].aprs.
The refreshed APR includes source: "REAL_TIME" and an updatedOn timestamp.
The same APR shape also appears on credit card entries in the POST /v1/users/:userId/debtProfile response.
Webhook Payloads
APR refresh uses the existing REFRESH_TRANSACTION_STATUS webhook event. A completed APR refresh includes the refreshed APR in data.aprs:
{
"eventType": "REFRESH_TRANSACTION_STATUS",
"userId": "user-id",
"extRequestId": "partner-request-123",
"refreshTransactionId": "b790770b-33c7-49d4-b086-e6e18864ba34",
"creditCardId": "007035e0-7865-49f3-be50-c922b1c63dc6",
"refreshTransactionStatus": "COMPLETED",
"data": {
"aprs": [
{
"rate": 19.99,
"type": "PURCHASE",
"source": "REAL_TIME",
"updatedOn": 1770000000000
}
]
}
}A failed APR refresh uses the same envelope without the data object:
{
"eventType": "REFRESH_TRANSACTION_STATUS",
"userId": "user-id",
"extRequestId": "partner-request-123",
"refreshTransactionId": "b790770b-33c7-49d4-b086-e6e18864ba34",
"creditCardId": "007035e0-7865-49f3-be50-c922b1c63dc6",
"refreshTransactionStatus": "FAILED"
}Webhook notes:
data.aprsis included only when the APR refresh isCOMPLETED.- If you miss a webhook, poll the refresh status endpoint to recover the terminal status.
- Polling does not return APR values. After the refresh completes, call GET /v1/users.
Reading APR Values
APR values are returned under data.creditCards[].aprs.
Each APR has a source:
DERIVED: inferred from credit report data.REAL_TIME: retrieved directly through an APR refresh.
When a derived APR and a real-time APR exist for the same type, the real-time APR takes precedence. Derived APRs without a real-time replacement are retained, and real-time APRs for new types are appended.
An APR object may include these fields:
{
"rate": 19.99,
"type": "PURCHASE",
"applicableBalance": 2500,
"expirationDate": "2026-12-31",
"source": "DERIVED",
"updatedOn": 1770000000000
}Implementation Checklist
Before going live, confirm that your integration does the following:
- Reads
annualPercentageRate.availabilityfrom the latestGET /v1/usersresponse. - Skips cards where APR refresh is not
SUPPORTED. - Sends
ANNUAL_PERCENTAGE_RATEonly for eligible credit card IDs. - Stores or logs
extRequestIdand refreshTransactionIdfor troubleshooting. - Handles both
COMPLETEDandFAILEDwebhook statuses. - Calls
GET /v1/usersto retrieve the APR, when needed.

