Card Repayments

Overview

A credit builder card accumulates an outstanding balance as the cardholder spends. Card repayments pay that balance down by debiting an account the cardholder owns and applying the funds to the card’s loan application.

Every attempt — successful or not — is stored as a repayment record, so the list endpoint is both the payment history and the audit trail for the card.

Common use cases:

  • Letting a cardholder pay their statement balance from your application
  • Paying a card down programmatically when autopay is turned off
  • Reconciling the payoff history of a card

Token scopes required:

OperationScope
Create a repaymentapi:write or api:purchase:write
Retrieve or list repaymentsapi:read or api:purchase:read

Prerequisites

  • Valid OAuth 2.0 Bearer token (see Authentication)
  • The payment card must belong to a consumer on your partner network
  • The payment card must have a loan application attached (created during card issuance)
  • Autopay must be disabled on the card, unless your partner is configured for partner-FBO settlement
  • The account being debited must be active and owned by the cardholder

How It Works

Partner App Upwardli API Ledger / Core Banking
│ │ │
│ POST /v2/payment-cards/{id}/payments/ │
│───────────────────────────────────>│ │
│ │ Resolve card + loan application │
│ │ Check autopay │
│ │ Resolve debit account │
│ │ Check outstanding balance │
│ │ │
│ │ Record repayment │
│ │ Settle payoff │
│ │───────────────────────────────────>│
│ │ │
│ 201 { id, status: "posted" } │ │
│<───────────────────────────────────│ │

Settlement happens synchronously. A 201 response means the balance has already been paid down — there is no intermediate pending state to poll for. If settlement fails, the API returns an error status code and the repayment is stored with status: error.

Repayment statuses

StatusMeaning
postedRepayment settled — the card balance was paid down
errorThe attempt failed. The record is kept for auditing and no funds moved

Which account is debited

account_id in the requestAccount used
OmittedThe DDA account attached to the card. It must be active, otherwise the request fails
ProvidedThe account matching that external ID. It must be active and owned by the cardholder

Autopay

Manual repayments require autopay to be disabled on the card. A card with autopay enabled returns 400 autopay_enabled.

Partners configured to settle repayments from a partner FBO account are exempt from this restriction — their repayments run on a separate payoff cycle. Contact Upwardli if you need this configuration.

Amount limits

amount must be at least 0.01 and no greater than the card’s current outstanding balance. Anything higher is rejected with 400 validation_error and the message Amount exceeds outstanding balance.

Rails

rail defaults to book_transfer, which is the only rail currently implemented. ach and instant are accepted by the request schema but are reserved for future use: sending either records the repayment with status: error and returns 400 payment_error.


API Endpoints

Base URLs

Production: https://api.upwardli.com
Sandbox: https://api-sandbox.upwardli.com

1. Create a Card Repayment

Endpoint

POST /v2/payment-cards/{payment_card_id}/payments/

Authentication

Authorization: Bearer <token>

Path parameters

ParameterTypeDescription
payment_card_idUUIDExternal ID of the payment card being paid down

Request body

FieldTypeRequiredDescription
amountdecimalYesRepayment amount in USD. Minimum 0.01, maximum is the card’s outstanding balance. Up to 2 decimal places.
railstringNobook_transfer (default), ach, or instant. Only book_transfer settles today.
account_idUUIDNoExternal ID of the account to debit. Defaults to the DDA account attached to the card.

Example request

$curl -X POST https://api.upwardli.com/v2/payment-cards/3fa85f64-5717-4562-b3fc-2c963f66afa6/payments/ \
> -H "Authorization: Bearer <token>" \
> -H "Content-Type: application/json" \
> -d '{
> "amount": 50.00,
> "rail": "book_transfer",
> "account_id": "7cb94a21-1a3b-4891-9e12-6d1f5a2bc099"
> }'

Paying from the account attached to the card takes a single field:

$curl -X POST https://api.upwardli.com/v2/payment-cards/3fa85f64-5717-4562-b3fc-2c963f66afa6/payments/ \
> -H "Authorization: Bearer <token>" \
> -H "Content-Type: application/json" \
> -d '{ "amount": 50.00 }'

Response — 201 Created

1{
2 "id": "a1b2c3d4-0000-4000-8000-000000000001",
3 "amount": "50.00",
4 "rail": "book_transfer",
5 "account_id": "7cb94a21-1a3b-4891-9e12-6d1f5a2bc099",
6 "status": "posted"
7}

amount is returned as a decimal string with two decimal places on every card repayment response, while the request accepts a JSON number. Parse it as a decimal, not as a float, when reconciling.

Full API reference

Create Card Repayment →


2. Retrieve a Card Repayment

Endpoint

GET /v2/payment-cards/{payment_card_id}/payments/{payment_id}/

Path parameters

ParameterTypeDescription
payment_card_idUUIDExternal ID of the payment card
payment_idUUIDThe id returned when the repayment was created

Example request

$curl https://api.upwardli.com/v2/payment-cards/3fa85f64-5717-4562-b3fc-2c963f66afa6/payments/a1b2c3d4-0000-4000-8000-000000000001/ \
> -H "Authorization: Bearer <token>"

Response — 200 OK

1{
2 "id": "a1b2c3d4-0000-4000-8000-000000000001",
3 "amount": "50.00",
4 "rail": "book_transfer",
5 "account_id": "7cb94a21-1a3b-4891-9e12-6d1f5a2bc099",
6 "status": "posted"
7}

A repayment that belongs to a different card returns 404, and so does a malformed payment_id. This is intentional and prevents information leakage.

Full API reference

Get Card Repayment →


3. List Card Repayments

Returns a paginated list of the repayments recorded against the card, newest first.

Endpoint

GET /v2/payment-cards/{payment_card_id}/payments/

Query parameters

ParameterTypeDefaultDescription
pageinteger1Page number
page_sizeinteger50Results per page. 50 is also the maximum.

Example request

$curl "https://api.upwardli.com/v2/payment-cards/3fa85f64-5717-4562-b3fc-2c963f66afa6/payments/?page=1&page_size=50" \
> -H "Authorization: Bearer <token>"

Response — 200 OK

1{
2 "count": 2,
3 "next": null,
4 "previous": null,
5 "results": [
6 {
7 "id": "a1b2c3d4-0000-4000-8000-000000000002",
8 "amount": "25.00",
9 "rail": "book_transfer",
10 "account_id": "7cb94a21-1a3b-4891-9e12-6d1f5a2bc099",
11 "status": "error"
12 },
13 {
14 "id": "a1b2c3d4-0000-4000-8000-000000000001",
15 "amount": "50.00",
16 "rail": "book_transfer",
17 "account_id": "7cb94a21-1a3b-4891-9e12-6d1f5a2bc099",
18 "status": "posted"
19 }
20 ]
21}

Full API reference

List Card Repayments →


Webhooks

Card repayments do not have dedicated webhook events. Repayments settled from a partner FBO account move funds as a book transfer and therefore emit the standard Payment.Transfer.Created event; repayments settled directly against the card’s ledger emit no webhook.

Full Webhook Event Catalog →


Error Reference

All card repayment errors share the same body:

1{
2 "error": "validation_error",
3 "error_code": "api_error",
4 "message": ["Amount exceeds outstanding balance."]
5}

Create errors

HTTP statuserrorCauseHow to resolve
400autopay_enabledAutopay is enabled on the cardDisable autopay before submitting a manual repayment
400validation_errorThe card has no loan application attachedThe card is not fully provisioned — contact Upwardli
400validation_erroraccount_id was not found, is inactive, or is not owned by the cardholderVerify the account external ID
400validation_erroraccount_id was omitted and the card has no active DDA accountSend an explicit account_id
400validation_erroramount is above the outstanding balanceFetch the balance and resend a smaller amount
400Validation errorThe request body is malformed — missing amount, amount below 0.01, unknown railFix the payload; error_code is validation_error and message lists the offending fields
400payment_errorThe repayment could not be settled, including ach and instant rails that are not yet implementedRetry on book_transfer; if it persists, open a support ticket
403forbiddenThe card belongs to a consumer outside your partner networkVerify the payment_card_id
404not_foundNo card matches payment_card_idVerify the payment_card_id
422payment_errorUnexpected server errorOpen a support ticket

Retrieve and list errors

HTTP statuserrorCause
400validation_errorThe card has no loan application attached, or it does not belong to your partner
404not_foundNo repayment matches payment_id on this card, or the ID is malformed
422Invalid Consumer IDThe cardholder could not be validated against the token
422payment_errorUnexpected server error

Authentication errors

HTTP statusCause
401Missing or invalid Bearer token
403Token scope does not cover the operation