> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developers.upwardli.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.upwardli.com/_mcp/server.

# Card Repayments

> Pay down a credit builder card's outstanding balance and track every repayment attempt using the Card Repayments API.

## 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:

| Operation                   | Scope                               |
| --------------------------- | ----------------------------------- |
| Create a repayment          | `api:write` or `api:purchase:write` |
| Retrieve or list repayments | `api:read` or `api:purchase:read`   |

---

## Prerequisites

* Valid OAuth 2.0 Bearer token ([see Authentication](/concepts/authentication/introduction))
* 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

| Status   | Meaning                                                                |
| -------- | ---------------------------------------------------------------------- |
| `posted` | Repayment settled — the card balance was paid down                     |
| `error`  | The attempt failed. The record is kept for auditing and no funds moved |

### Which account is debited

| `account_id` in the request | Account used                                                                             |
| --------------------------- | ---------------------------------------------------------------------------------------- |
| Omitted                     | The DDA account attached to the card. It must be active, otherwise the request fails     |
| Provided                    | The 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`.

> **Note**
>
> 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

| Parameter         | Type | Description                                     |
| ----------------- | ---- | ----------------------------------------------- |
| `payment_card_id` | UUID | External ID of the payment card being paid down |

### Request body

| Field        | Type    | Required | Description                                                                                                 |
| ------------ | ------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| `amount`     | decimal | Yes      | Repayment amount in USD. Minimum `0.01`, maximum is the card's outstanding balance. Up to 2 decimal places. |
| `rail`       | string  | No       | `book_transfer` (default), `ach`, or `instant`. Only `book_transfer` settles today.                         |
| `account_id` | UUID    | No       | External ID of the account to debit. Defaults to the DDA account attached to the card.                      |

### Example request

```bash
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:

```bash
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

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

> **Note**
>
> `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 →](/api-access/api-reference/payment-cards/create-card-payment)

---

## 2. Retrieve a Card Repayment

### Endpoint

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

### Path parameters

| Parameter         | Type | Description                                      |
| ----------------- | ---- | ------------------------------------------------ |
| `payment_card_id` | UUID | External ID of the payment card                  |
| `payment_id`      | UUID | The `id` returned when the repayment was created |

### Example request

```bash
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

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

> **Note**
>
> 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 →](/api-access/api-reference/payment-cards/retrieve-card-payment)

---

## 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

| Parameter   | Type    | Default | Description                               |
| ----------- | ------- | ------- | ----------------------------------------- |
| `page`      | integer | 1       | Page number                               |
| `page_size` | integer | 50      | Results per page. 50 is also the maximum. |

### Example request

```bash
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

```json
{
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": "a1b2c3d4-0000-4000-8000-000000000002",
      "amount": "25.00",
      "rail": "book_transfer",
      "account_id": "7cb94a21-1a3b-4891-9e12-6d1f5a2bc099",
      "status": "error"
    },
    {
      "id": "a1b2c3d4-0000-4000-8000-000000000001",
      "amount": "50.00",
      "rail": "book_transfer",
      "account_id": "7cb94a21-1a3b-4891-9e12-6d1f5a2bc099",
      "status": "posted"
    }
  ]
}
```

### Full API reference

[List Card Repayments →](/api-access/api-reference/payment-cards/payments/list-card-payments)

---

## 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 →](/concepts/webhooks/event-catalog#book-transfer-webhooks)

---

## Error Reference

All card repayment errors share the same body:

```json
{
  "error": "validation_error",
  "error_code": "api_error",
  "message": ["Amount exceeds outstanding balance."]
}
```

### Create errors

| HTTP status | `error`            | Cause                                                                                                | How to resolve                                                                               |
| ----------- | ------------------ | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `400`       | `autopay_enabled`  | Autopay is enabled on the card                                                                       | Disable autopay before submitting a manual repayment                                         |
| `400`       | `validation_error` | The card has no loan application attached                                                            | The card is not fully provisioned — contact Upwardli                                         |
| `400`       | `validation_error` | `account_id` was not found, is inactive, or is not owned by the cardholder                           | Verify the account external ID                                                               |
| `400`       | `validation_error` | `account_id` was omitted and the card has no active DDA account                                      | Send an explicit `account_id`                                                                |
| `400`       | `validation_error` | `amount` is above the outstanding balance                                                            | Fetch the balance and resend a smaller amount                                                |
| `400`       | `Validation error` | The request body is malformed — missing `amount`, amount below `0.01`, unknown `rail`                | Fix the payload; `error_code` is `validation_error` and `message` lists the offending fields |
| `400`       | `payment_error`    | The repayment could not be settled, including `ach` and `instant` rails that are not yet implemented | Retry on `book_transfer`; if it persists, open a support ticket                              |
| `403`       | `forbidden`        | The card belongs to a consumer outside your partner network                                          | Verify the `payment_card_id`                                                                 |
| `404`       | `not_found`        | No card matches `payment_card_id`                                                                    | Verify the `payment_card_id`                                                                 |
| `422`       | `payment_error`    | Unexpected server error                                                                              | Open a support ticket                                                                        |

### Retrieve and list errors

| HTTP status | `error`               | Cause                                                                            |
| ----------- | --------------------- | -------------------------------------------------------------------------------- |
| `400`       | `validation_error`    | The card has no loan application attached, or it does not belong to your partner |
| `404`       | `not_found`           | No repayment matches `payment_id` on this card, or the ID is malformed           |
| `422`       | `Invalid Consumer ID` | The cardholder could not be validated against the token                          |
| `422`       | `payment_error`       | Unexpected server error                                                          |

### Authentication errors

| HTTP status | Cause                                    |
| ----------- | ---------------------------------------- |
| `401`       | Missing or invalid Bearer token          |
| `403`       | Token scope does not cover the operation |