full-swing / banking
beta v1.0 internal CPC

Banking API

A single integration surface for building, testing, and measuring landing page hypotheses against the Banking marketplace — primarily high yield savings accounts (HYSAs). Revenue fires on click with no confirmation lag. Read the Pacing section before designing your experiment.

01
POST /session
get a sub_id
02
POST /inputs
capture form fields
03
GET /marketplace
render HYSA offers
04
GET /analytics
vet hypothesis
Pacing is the defining constraint of this vertical. Banking partners operate on monthly CPC budgets. As budgets exhaust, offer availability drops — sometimes to zero. The pacing_status field on each offer and the pacing_impact_rate metric in analytics help you detect and account for this.
This API follows the Vertical Platform Contract. Personal Loans, Credit Cards, Mortgage, and Find a Financial Advisor expose the same four endpoints. Only the pocket field schema and revenue model differ. See Vertical parity.

Quickstart

bash — full lifecycle
# 1. Create a session
RESP=$(curl -s -X POST https://api.nerdwallet.com/v1/banking/session \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"referrer_tag": "your_experiment"}')
SUB_ID=$(echo $RESP | jq -r '.sub_id')

# 2. Submit inputs
curl -s -X POST https://api.nerdwallet.com/v1/banking/inputs \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"sub_id\":\"$SUB_ID\",\"fields\":{\"account_type\":\"savings\",\"deposit_amount\":10000,\"state\":\"CA\",\"citizenship_status\":\"citizen\"}}"

# 3. Render marketplace — check pacing_status on each offer
curl -s "https://api.nerdwallet.com/v1/banking/marketplace?sub_id=$SUB_ID&format=json" \
  -H "Authorization: Bearer YOUR_API_KEY"

# 4. Pull analytics — pacing_impact_rate shows if budget gaps skewed results
curl -s "https://api.nerdwallet.com/v1/banking/analytics?referrer_tag=your_experiment&granularity=summary" \
  -H "Authorization: Bearer YOUR_API_KEY"

Authentication

http header
Authorization: Bearer nw_bk_live_xxxxxxxxxxxxxxxxxxxx
Sandbox vs live. Tokens prefixed nw_bk_test_ return synthetic offers with simulated pacing states and no CPC revenue attributed.

Base URL & versioning

base url
https://api.nerdwallet.com/v1/banking/

POSTCreate session

Generates a sub_id that ties form inputs, marketplace events, and CPC revenue together. Call once per page load.

POST/session
Request body
FieldTypeRequiredDescription
referrer_tagstringrequiredExperiment namespace.e.g. "mikes_hysa_rate_comparison_v1"
user_agentstringoptionalFor device-segmented analytics.
metadataobjectoptionalArbitrary key-value pairs. Max 10 keys.e.g. {"variant": "control", "traffic_source": "organic"}
Response
200 OK
400
401
json
{ "sub_id": "bksn_3m7p9q2rxt", "referrer_tag": "mikes_hysa_rate_comparison_v1", "expires_at": "2024-09-15T18:45:00Z", "sandbox": false }
{ "error": "INVALID_REFERRER_TAG" }
{ "error": "UNAUTHORIZED" }

POSTSubmit inputs

POST/inputs
Request body
FieldTypeRequiredDescription
sub_idstringrequiredSession ID from POST /session.
fieldsobjectrequiredKey-value map. See pocket field schema.
json — example
{ "sub_id": "bksn_3m7p9q2rxt", "fields": { "account_type": "savings", "deposit_amount": 10000, "state": "CA", "citizenship_status": "citizen", "age_range": "35-49" } }

Pocket field schema

required conditional optional
account_type
enum · savings / checking / money_market / cd
deposit_amount
integer · USD · initial deposit intent
state
string · 2-letter ISO
citizenship_status
enum · citizen / perm_resident / visa
cd_term_months
integer · 3, 6, 12, 18, 24, 36, 60 · required if cd
age_range
enum · 18-24 / 25-34 / 35-49 / 50-64 / 65+
existing_bank
string · current primary bank name
min_apy
float · minimum APY filter
online_only
boolean · exclude branch banks
no_monthly_fee
boolean · fee-free accounts only
savings accounts drive the majority of Banking volume. Most partner CPC budgets are allocated to HYSA traffic. Expect significantly more offer availability for account_type=savings, especially late in the month.

GETGet marketplace

Returns matched banking offers. Each offer includes pacing_status indicating the partner's current budget state. Revenue fires automatically on click.

GET/marketplace
Query parameters
ParamTypeRequiredDescription
sub_idstringrequiredSession ID.
formatenumrequired"json" | "iframe" | "js"
max_offersintegeroptionalDefault: 5. Max: 20.
sortenumoptionalDefault: highest_apy."highest_apy" | "recommended" | "nw_revenue"
include_paced_outbooleanoptionalDefault false. If true, includes exhausted offers (shown but not clickable). Useful for UI testing.
Response — format: json
json
{
  "sub_id": "bksn_3m7p9q2rxt", "match_quality": "high",
  "pacing_snapshot": {
    "active_partners": 6, "throttled_partners": 2,
    "exhausted_partners": 3, "budget_reset_date": "2024-10-01"
  },
  "offers": [
    {
      "nw_offer_id": "off_bk_4r8t2z", "bank_name": "Marcus by Goldman Sachs",
      "apy": 5.10, "apy_as_of": "2024-09-12",
      "min_deposit": 0, "monthly_fee": 0, "fdic_insured": true,
      "pacing_status": "active",    // "active" | "throttled" | "paused" | "exhausted"
      "budget_headroom": "high",   // "high" | "medium" | "low" | "exhausted"
      "click_url": "https://www.nerdwallet.com/go/marcus/savings?sub=bksn_3m7p9q2rxt&offer=off_bk_4r8t2z"
    },
    {
      "bank_name": "Ally Bank", "apy": 4.85,
      "pacing_status": "throttled", "budget_headroom": "low"
    }
  ],
  "total_offers": 6, "generated_at": "2024-09-15T14:47:33Z"
}
Throttled offers may disappear between load and click. If a user loads a page with a throttled offer and clicks several minutes later, the partner may have exhausted their budget. Design your UI to handle zero-offer states.
Preserve click_url. Contains the sub_id and nw_offer_id required for CPC attribution. Do not construct your own URLs.

Render formats

jsonFull control. Returns pacing_status and budget_headroom per offer.

Build any UI you want. Attribution is automatic as long as you use the provided click_url.

iframeDrop-in embed. Pacing handled internally.

Returns an embed_url. The NW marketplace renders inside it with pacing-aware display built in.

jsScript tag. Renders into any container.
iframe and js are the "full swing" path. Pacing degrades gracefully, CPC revenue is attributed automatically. Use JSON only when you need custom UI.

GETGet analytics

Query session and CPC revenue data. Includes pacing metrics to detect whether budget exhaustion affected results.

GET/analytics
Query parameters
ParamTypeRequiredDescription
referrer_tagstringone ofFilter to one experiment.
sub_id_liststring[]one ofComma-separated. Max 500.
date_from / date_tostringoptionalISO dates. Defaults: 30 days ago → today.
granularityenumoptionalDefault: summary."row" | "daily" | "summary"
Response — granularity: summary
json
{
  "referrer_tag": "mikes_hysa_rate_comparison_v1",
  "sessions_total": 3410, "marketplace_views": 2690, "offer_clicks": 537,
  "click_rate": 0.200,
  "cpc_rev": 2148.00,        // fires on click, same-day, no confirmation lag
  "rev_per_session": 0.63,    // primary comparison metric
  "top_partners": [{ "bank": "Marcus by Goldman Sachs", "clicks": 188, "cpc_rev": 752.00 }],
  // ── Pacing metrics ──────────────────────────────────────
  "pacing_impact_rate": 0.18,   // fraction of views where ≥1 partner was exhausted
  "zero_offer_rate": 0.04,      // fraction of views with NO active offers
  "offers_available_rate": 0.96,
  "avg_active_offers_per_view": 4.2,
  "pacing_by_day": [
    { "date": "2024-09-01", "active_partners": 9, "exhausted_partners": 0, "zero_offer_rate": 0.00 },
    { "date": "2024-09-25", "active_partners": 3, "exhausted_partners": 6, "zero_offer_rate": 0.21 }
  ]
}
Row-level schema (granularity: row)
json — row
{
  "date": "2024-09-10", "sub_id": "bksn_3m7p9q2rxt", "account_type": "savings",
  "marketplace_viewed": true, "active_offers_shown": 4, "offers_clicked": 1,
  "clicked_bank": "Marcus by Goldman Sachs", "cpc_rev": 4.00,
  "pacing_impact": false,        // true if any partner was exhausted at view time
  "active_partners_at_view": 7
}

Revenue model — CPC

Banking operates on a cost-per-click (CPC) model. Revenue fires on click with no confirmation lag — unlike Personal Loans, there's no funded-loan event to wait for.

cpc_rev
float · USD
Fires on click. Available same-day. This is the settled revenue figure — use directly for hypothesis vetting.
rev_per_session
float · USD
cpc_rev / sessions_total. Primary comparison metric. Filter to pacing_impact=false rows for cleanest read.
rev_per_click
float · USD
Average CPC rate across active partners. A drop late in the month often reflects budget-constrained partners reducing bids, not behavior change.

Pacing overview

Banking partners allocate a fixed monthly CPC budget. As that budget is consumed, partners throttle and eventually pause their offers. This is the central operational constraint of the Banking vertical.

active
Budget available. Offer displays and is clickable.
throttled
Running low. Shown but may pause at any time.
paused
Manually paused. May resume same day.
exhausted
Monthly budget gone. Resets on the 1st.

Early in the month, most partners are active. By mid-month some throttle. By month-end, multiple partners may be exhausted.

high headroom
medium
low
exhausted
Pacing resets on the 1st of every month. The best time to run a hypothesis test is the first 10 days when all partner budgets are fresh and offer availability is at its peak. Tests run in the last week may see significantly reduced offer density and suppressed click rates.

Experiment strategy for pacing

DORun tests in the first 10 days of the month

Partner budgets reset on the 1st. The first 10 days have the most active partners, highest offer density, and most stable CPC rates.

DOFilter analytics to pacing_impact: false rows

Use granularity=row and filter to sessions where pacing_impact=false for the cleanest signal on whether your hypothesis actually drives clicks.

DOMonitor zero_offer_rate daily

Pull granularity=daily each day. If zero_offer_rate spikes above 10%, your test is likely generating noise. Pause and resume next month.

DON'TCompare variants that ran in different weeks of the month

Pacing conditions will likely dominate any real signal. Run both variants simultaneously, or compare after filtering to pacing_impact=false rows.

Error codes

  • 400INVALID_REFERRER_TAGMust match [a-z0-9_-]{3,64}.
  • 400INVALID_FIELD_VALUEPocket field failed validation. Response includes fields_invalid array.
  • 400MISSING_CD_TERMaccount_type=cd requires cd_term_months.
  • 400SUB_ID_EXPIREDSession exceeded 4-hour TTL. Create a new session.
  • 401UNAUTHORIZEDAPI key missing, malformed, or expired.
  • 403VERTICAL_MISMATCHBanking keys are prefixed nw_bk_.
  • 200ALL_PARTNERS_EXHAUSTEDReturns 200 with empty offers array and no_match_reason: "all_partners_exhausted". Design your UI to handle this state.
  • 429RATE_LIMIT_EXCEEDEDCheck X-RateLimit-Reset. Use exponential backoff.

Rate limits

500
requests/min per key
50
analytics queries/min
10k
sessions/day per key
100
analytics rows per page

Vertical parity contract

VerticalBase pathToken prefixRev modelStatus
Personal Loans/v1/personal-loans/nw_pl_CPL confirmedstable
Credit Cards/v1/credit-cards/nw_cc_CPCbeta
Mortgage/v1/mortgage/nw_mo_CPL confirmedbeta
Banking/v1/banking/nw_bk_CPC + pacingbeta
Find a Financial Advisor/v1/find-advisor/nw_fa_CPLplanned