API & Integration Reference
The PointsVest Partner API lets you connect your rewards programme so your members' points are automatically converted and invested. Below is everything your engineering team needs to integrate.
All API calls use OAuth 2.0 Client Credentials. Exchange your credentials for an access token and include it as a Bearer token on every request.
POST https://auth.pointsvest.com/oauth/token Content-Type: application/x-www-form-urlencoded grant_type=client_credentials &client_id=ci_xxxxxxxxxxx &client_secret=cs_xxxxxxxxxxx &scope=rewards:read rewards:debit account:link
| Header | Required | Description |
|---|---|---|
| Authorization | Always | Bearer {access_token} |
| Idempotency-Key | Mutating calls | UUID v4 — unique per operation, safe to reuse on retry |
| X-PV-Partner-ID | Always | Your partner ID from registration |
| X-PV-Signature | Mutating calls | HMAC-SHA256 of request body, hex-encoded |
| X-PV-Timestamp | Mutating calls | Unix epoch seconds — rejected if >300s clock skew |
Register your organisation and manage your partner configuration.
Register your organisation. Returns OAuth client credentials and a sandbox URL. Status starts as pending_verification until compliance review and mTLS certificate upload are complete.
{
"partner": {
"legal_name": "Chase Bank, N.A.",
"brand_name": "Chase",
"partner_type": "card_issuer",
"regulatory_id": "RSSD-852218",
"contact": {
"technical_email": "api-ops@yourbank.com",
"compliance_email": "compliance@yourbank.com"
},
"program_metadata": {
"program_name": "Ultimate Rewards",
"currency_label": "points",
"base_cpp": 0.01
},
"webhook_endpoint": "https://api.yourbank.com/pv/events"
}
}{
"partner_id": "ptnr_xxxxx",
"client_id": "ci_xxxxxxxxxxx",
"client_secret": "cs_xxxxxxxxxxx",
"status": "pending_verification",
"sandbox_base_url": "https://sandbox.api.pointsvest.com/v1"
}{
"partner_id": "ptnr_xxxxx",
"status": "active",
"capabilities": ["rewards_credit", "rewards_debit", "balance_inquiry"],
"activated_at": "2025-01-20T09:00:00Z"
}Update webhook URL, rotate public key, or update your base CPP rate. Partial updates accepted.
{
"webhook_endpoint": "https://api.yourbank.com/pv/events-v2",
"program_metadata": { "base_cpp": 0.012 }
}Link and manage your members' rewards accounts. Bank accounts are linked via Plaid — see the full internal spec for details.
Link a member's rewards account after consent is granted. The :link suffix indicates this is a command, not a resource creation.
{
"partner_id": "ptnr_xxxxx",
"partner_account_id": "your_acct_9872341",
"pv_customer_id": "cust_pv_78123",
"customer_pii_hash": "sha256:a3f9c2...",
"program_code": "CHASE_UR",
"consent": {
"consent_id": "cns_98712",
"granted_at": "2025-01-15T10:05:00Z",
"scopes": ["balance_read", "rewards_debit"],
"expiry": "2026-01-15T10:05:00Z"
},
"initial_balance": {
"units": 72540, "currency": "points"
}
}{
"pv_account_id": "pvacc_xxxxx",
"status": "active",
"linked_at": "2025-01-15T10:05:30Z",
"estimated_value_usd": 725.40
}| Field | Type | Description |
|---|---|---|
| partner_account_idrequired | string | Your internal ID for this member account |
| pv_customer_idrequired | string | Member's PointsVest ID from onboarding |
| customer_pii_hashrequired | string | SHA-256 of name + DOB — for identity matching without raw PII |
| consent.scopesrequired | string[] | Scopes the member granted — see Consent Scopes |
| initial_balance.unitsoptional | integer | Current balance at linking time |
Unlink a member's account. Triggers a sweep of any invested balance back to your programme. Note: uses POST :unlink rather than DELETE so the request body (reason, disposition) remains valid per HTTP spec.
{
"reason": "customer_requested",
"effective_date": "2025-02-01T00:00:00Z",
"pending_balance_disposition": "return_to_issuer"
}Notify PointsVest of a PII change. PV stores a hash only — you remain the source of truth for all member data.
{
"update_type": "name_change",
"customer_pii_hash": "sha256:b7d2e1...",
"effective_date": "2025-02-01"
}Credit rewards to PointsVest for investment, and check the invested balance at any time.
Credit points or miles to PointsVest for conversion and investment. Use your transaction ID as the idempotency_key to safely retry on network failure.
{
"idempotency_key": "idem_yourtxn_20250115_001",
"transaction": {
"partner_transaction_id": "your_txn_445521",
"credit_units": 2400,
"currency": "points",
"transaction_type": "earn",
"earn_category": "dining",
"transaction_date": "2025-01-15T18:32:00Z"
},
"valuation": {
"cpp_at_time": 0.01,
"usd_equivalent": 24.00
}
}{
"pv_transaction_id": "pvtxn_00912344",
"status": "credited",
"pv_account_balance": 74940,
"invested_amount_usd": 24.00,
"auto_invest_triggered": true,
"credited_at": "2025-01-15T18:32:05Z"
}| Field | Type | Description |
|---|---|---|
| idempotency_keyrequired | string | Use your transaction ID — safe to retry with same key |
| transaction.credit_unitsrequired | integer | Whole number of points or miles — no fractional units |
| transaction.currencyrequired | enum | points or miles |
| valuation.cpp_at_timerequired | decimal | Your programme's cents-per-point at time of transaction |
| transaction.earn_categoryoptional | string | Category for member reporting (dining, travel, grocery…) |
{
"balance": {
"invested_units": 72540,
"invested_usd_value": 2050.30,
"last_credited_at": "2025-01-14T22:00:00Z"
},
"as_of": "2025-01-15T10:00:00Z"
}Subscribe to platform events. Eliminates the need to poll — you'll be notified the moment something changes for your members.
X-PV-Signature header (HMAC-SHA256 of the raw body, signed with your signing_secret). Always verify this before processing.{
"partner_id": "ptnr_xxxxx",
"endpoint_url": "https://api.yourbank.com/pv/events",
"events": [
"account.linked", "account.unlinked",
"rewards.invested", "rewards.redeemed",
"rewards.credit_failed", "customer.consent_revoked"
],
"signing_secret": "whsec_xxxxxxxxx"
}{
"event_id": "evt_00112233",
"event_type": "rewards.invested",
"partner_id": "ptnr_xxxxx",
"pv_account_id": "pvacc_xxxxx",
"data": {
"units_invested": 2400,
"usd_value": 24.00,
"goal": "aggressive_growth"
},
"timestamp": "2025-01-15T18:33:01Z",
"signature": "sha256=xxxxxxxxxxx"
}| Event | Triggered when |
|---|---|
| account.linked | Member successfully links their account |
| account.unlinked | Account unlinked — by customer or system |
| rewards.invested | Points converted and invested into portfolio |
| rewards.redeemed | Member redeems value (cashout or return to programme) |
| rewards.credit_failed | Credit operation failed — retry with same idempotency key |
| customer.consent_revoked | Member revoked access — account will be unlinked |
Only call operations covered by the scopes the member has granted. Attempting an out-of-scope operation returns 403 FORBIDDEN.
| Scope | Required for | Revocable |
|---|---|---|
| balance_read | All features — minimum viable consent | No — base scope |
| rewards_debit | PointsVest debiting points for investment | Yes |
| transaction_read | RoundVest round-up calculation | Yes |
| cash_debit | FloatVest cash sweep from linked bank account | Yes |
All errors use a consistent envelope. The code field is machine-readable and stable across API versions.
{
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Account has 3,000 points but 5,000 were requested.",
"request_id": "req_xxxxxxxxx",
"retry_after": null
}
}| HTTP | Code | Meaning | Retry? |
|---|---|---|---|
| 400 | INVALID_REQUEST | Malformed body or missing field | No — fix request |
| 401 | UNAUTHORIZED | Invalid or expired token | Yes — refresh token |
| 403 | FORBIDDEN | Scope not granted or consent revoked | No — re-authorise |
| 404 | NOT_FOUND | Account or resource not found | No |
| 409 | IDEMPOTENCY_CONFLICT | Same key, different payload | No — investigate |
| 422 | INSUFFICIENT_BALANCE | Not enough points | No |
| 429 | RATE_LIMITED | Too many requests — check Retry-After | Yes — after delay |
| 500 | INTERNAL_ERROR | Platform error | Yes — backoff |
| 503 | SERVICE_UNAVAILABLE | Maintenance window | Yes — check status |