> ## Documentation Index
> Fetch the complete documentation index at: https://docs-api.kravata.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Prepaid package

> Pay your monthly prepaid package with PSE, Bre-B, USDC or USDT, check your balance, and what to do when an operation is blocked with PREPAID_REQUIRED.

Some companies operate with a **monthly prepaid package**: you pay a plan in advance, and Kravata draws the service costs of your operations from that balance, as set in your agreement. If your company has the prepaid package enabled and this month's payment is missing, new operations are blocked until it is paid: both the ones your users create and the ones your server creates through the API with your partner token.

This guide shows how to list the plans, pay with PSE, Bre-B, USDC or USDT, and check the balance.

## Who can pay

Only **one user per company**, the **payer user**, can list plans, pay and check the balance. By default it is your company's own user in Kravata, the one whose user id is your client id. Kravata can configure another user as the payer instead: ask your Kravata point of contact which user it is, or to change it. Any other user gets `403 PREPAID_PAYER_ONLY`.

The calls are made **as the payer user**, with that user's own token, like the Kravata widget. They do not use the partner token of your server.

| Environment | Base URL |
| - | - |
| Test | `https://test-api-kore.kravata.co` |
| Production | `https://api-kore.kravata.co` |

> [!WARNING]
>
> Send the **user token** in both headers on every call: `Authorization: Bearer <token>` **and** `Cookie: accessToken=<token>`. Some routes check one header at the gateway and some check the other. See [WhatsApp channel](https://docs-api.kravata.co/stack/whatsapp-channel.md#credentials-and-tokens) for how user tokens work.

The examples below use the test domain.

## How the month works

* The month is the calendar month in **Colombia time** (`America/Bogota`).
* A payment counts in the month in which it is **completed**, not the month in which you created it. A payment still pending at midnight on the last day of the month counts in the next month.
* Your **first** package payment is prorated: you pay for the days left in a 30-day month, counting today. `prorateDays` is `30` on day 1, `16` on day 15 and `1` from day 30 on. The amount is `monthlyAmount × prorateDays / 30`, rounded to 2 decimals.
* The balance you can still use is `paid − used`. At the end of the month the unused balance is not carried over.
* Your company can have **one pending payment at a time**.

## 1. List the plans

Call `GET /api/v1/prepaid/plans`. It returns your company's active plans, cheapest first.

```bash theme={"dark"}
curl https://test-api-kore.kravata.co/api/v1/prepaid/plans \
  -H "Authorization: Bearer USER_TOKEN" \
  -H "Cookie: accessToken=USER_TOKEN"
```

```json theme={"dark"}
[
  {
    "id": "5e2d0c9b-1a3f-4c6e-8b7a-2f4d6e8a0c1b",
    "code": "PLAN_BASIC",
    "name": "Basic",
    "monthlyAmount": 500.0,
    "currency": "USD",
    "amountDue": 266.67,
    "prorateDays": 16
  }
]
```

| Field | Description |
| - | - |
| `code` | Plan code. You send it as `planCode` when you pay. |
| `monthlyAmount` | Monthly price of the plan. |
| `currency` | Currency of the balance (the plan's currency). |
| `amountDue` | What you pay today for this plan. It equals `monthlyAmount`, or the prorated amount if it is your first payment. |
| `prorateDays` | Days used for the proration. `null` when it is not your first payment. |

An empty list means your company has no active plans. If the plans cannot be read right now you get `503 PREPAID_PLANS_UNAVAILABLE`: try again in a moment.

## 2. Pay

Call `POST /api/v1/prepaid/payments` with **exactly one** of `planCode` (a plan from step 1) or `amount` (a custom amount in the balance currency).

| Field | Type | Required | Description |
| - | - | - | - |
| `paymentMethod` | string | Yes | `PSE`, `BREB`, `USDC` or `USDT`. |
| `planCode` | string | One of the two | Code of an active plan. |
| `amount` | number | One of the two | Custom amount, greater than 0. |
| `network` | string | No | USDC / USDT only: network of the payer's wallet, for example `STELLAR`. |
| `bank` | string | PSE | PSE bank `code`, from [Get Banks](https://docs-api.kravata.co/stack/api-reference/accounts/get-banks.md). |
| `redirectUrl` | string | No | PSE: public http(s) URL where the user returns after paying. |
| `phone`, `expirationDate` | string | No | Optional data for the payment link. |

The response is `201` and the payment starts in `PENDING`:

| Field | Description |
| - | - |
| `operation` | The prepaid payment operation, with its `id` and `status`. |
| `paymentInstructions` | How to complete the payment. It depends on the method, see below. |
| `fundAmount`, `fundCurrency` | What is added to your balance when the payment completes. |
| `prorateDays` | Only on your first payment. |

> [tab] PSE
>
> The balance is in USD or COP. If it is in USD, the amount is converted to COP at the current exchange rate and **rounded up to whole pesos**.
>
> ```bash theme={"dark"}
> curl -X POST https://test-api-kore.kravata.co/api/v1/prepaid/payments \
>   -H "Authorization: Bearer USER_TOKEN" \
>   -H "Cookie: accessToken=USER_TOKEN" \
>   -H "Content-Type: application/json" \
>   -d '{
>     "paymentMethod": "PSE",
>     "planCode": "PLAN_BASIC",
>     "bank": "1022",
>     "redirectUrl": "https://your-app.example.com/prepaid/return"
>   }'
> ```
>
> `paymentInstructions` contains `amount` and `currency` (`COP`), `paymentMethod`, and `pseLink`. Send the user to `pseLink` to pay.

> [tab] Bre-B
>
> The amount is converted to COP and rounded up to whole pesos, like PSE.
>
> ```bash theme={"dark"}
> curl -X POST https://test-api-kore.kravata.co/api/v1/prepaid/payments \
>   -H "Authorization: Bearer USER_TOKEN" \
>   -H "Cookie: accessToken=USER_TOKEN" \
>   -H "Content-Type: application/json" \
>   -d '{ "paymentMethod": "BREB", "planCode": "PLAN_BASIC" }'
> ```
>
> `paymentInstructions` contains `amount` and `currency` (`COP`), `paymentMethod`, `brebKey` and `ownerName`. The payer transfers **exactly that amount** to the Bre-B key. A Bre-B payment that is not made in time is cancelled, and you can create a new one.

> [tab] USDC / USDT
>
> The payment is made from the payer user's own wallet in Kravata, so it needs enough balance of that token. It requires a balance in **USD**.
>
> ```bash theme={"dark"}
> curl -X POST https://test-api-kore.kravata.co/api/v1/prepaid/payments \
>   -H "Authorization: Bearer USER_TOKEN" \
>   -H "Cookie: accessToken=USER_TOKEN" \
>   -H "Content-Type: application/json" \
>   -d '{ "paymentMethod": "USDC", "planCode": "PLAN_BASIC", "network": "STELLAR" }'
> ```
>
> `paymentInstructions` contains `amount`, `currency`, `network` and `transferOperationId`. There is nothing else to do: Kravata moves the tokens from the payer's wallet. The payment completes when the transfer is confirmed.

```json theme={"dark"}
{
  "operation": {
    "id": "b7a1f3c2-9d4e-4a6b-8c5d-1e2f3a4b5c6d",
    "status": "PENDING"
  },
  "paymentInstructions": {
    "amount": "266.67",
    "currency": "USDC",
    "network": "STELLAR",
    "transferOperationId": "0d9c8b7a-6f5e-4d3c-b2a1-9f8e7d6c5b4a"
  },
  "fundAmount": 266.67,
  "fundCurrency": "USD",
  "prorateDays": 16
}
```

The `operation` object has more fields; the example shows only the ones you need here.

The payment is `COMPLETED` when the money arrives, and `REJECTED` if it fails or the payment is rejected. A `REJECTED` payment adds nothing to your balance and you can pay again.

### Errors when paying

Errors have the shape `{"error": {"code": "...", "message": "...", "request_id": "..."}}`.

| Status | Code | What to do |
| - | - | - |
| 403 | `PREPAID_PAYER_ONLY` | The user is not the payer user. Use the payer's token. |
| 409 | `PREPAID_PAYMENT_PENDING` | Your company already has a pending payment. `error.details[0].message` is its operation `id`. Wait until it completes or is rejected. |
| 400 | `BAD_REQUEST` | USDC / USDT: the payer's wallet does not have enough balance. |
| 422 | `VALIDATION_ERROR` | Invalid request: you sent both or neither of `planCode` and `amount`, an invalid `paymentMethod`, a `planCode` that is not an active plan, an amount of 0 or less, or the payer has no wallet of that token or network. USDC / USDT also need a balance in USD. The message says which one. |
| 422 | `VALIDATION_ERROR` | Kravata has no receiving wallet configured for that network, or the PSE / Bre-B payment could not be created. Contact your Kravata point of contact with the `request_id`. |
| 502 | `UPSTREAM_ERROR` | The PSE / Bre-B payment service did not answer. The payment may have been created: check [the status](#3-check-the-status) and your pending payment before trying again. |
| 503 | `PREPAID_PLANS_UNAVAILABLE` | The plans cannot be read right now. Try again. |
| 503 | `SERVICE_UNAVAILABLE` | PSE / Bre-B payments, or the exchange rate, are not available right now. Try again. |

## 3. Check the status

Call `GET /api/v1/prepaid/status`.

```bash theme={"dark"}
curl https://test-api-kore.kravata.co/api/v1/prepaid/status \
  -H "Authorization: Bearer USER_TOKEN" \
  -H "Cookie: accessToken=USER_TOKEN"
```

```json theme={"dark"}
{
  "period": "2026-11",
  "fundCurrency": "USD",
  "plan": { "code": "PLAN_BASIC", "name": "Basic", "monthlyAmount": 500.0, "currency": "USD" },
  "paidAmount": 266.67,
  "usedAmount": 40.0,
  "availableAmount": 226.67,
  "minimumRequired": 266.67,
  "prepaidRequired": true,
  "isFirstPeriod": true
}
```

| Field | Description |
| - | - |
| `period` | Current month, `YYYY-MM`, Colombia time. |
| `fundCurrency` | Currency of the balance. |
| `plan` | Plan that your payments correspond to this month. `null` if none. |
| `paidAmount` | Completed payments of the month. Pending payments do not count. |
| `usedAmount` | Balance used by your operations this month. |
| `availableAmount` | `paidAmount − usedAmount`, never below 0. |
| `minimumRequired` | Minimum you must have paid this month to operate: the price of the cheapest plan, prorated in your first month. `null` if your company has no plans. |
| `prepaidRequired` | `true` if operations are blocked while `paidAmount` is below `minimumRequired`. |
| `isFirstPeriod` | `true` while you are in your first month of prepaid payments. |

## The `PREPAID_REQUIRED` error

When the prepaid package is enabled for your company and `paidAmount` is below `minimumRequired`, these calls are rejected.

Made by your users, with their user token:

* `POST /api/v1/operations`
* `POST /api/v1/operations/deposit`
* `POST /api/v1/operations/withdrawal`
* `POST /api/v1/operations/transfer`

Made by your server through the API, with your partner token:

* `POST /api/v1/admin/operation/{user_id}/deposit`
* `POST /api/v1/admin/operation/{user_id}/withdrawal`
* `POST /api/v1/admin/operation/{user_id}/transfer`

```json theme={"dark"}
{
  "error": {
    "code": "PREPAID_REQUIRED",
    "message": "Debes realizar el pago de tu paquete prepaid para poder seguir operando usando el endpoint indicado en la documentación",
    "request_id": "8f1c2a3b-4d5e-6f70-8192-a3b4c5d6e7f8"
  }
}
```

The status is `403`. The message is in Spanish. It points to this guide.

**What to do:**

**1. Pay the package**

Have the payer user pay as in [step 2](#2-pay).

**2. Wait for COMPLETED**

The block ends when the payment is `COMPLETED` and `paidAmount` reaches `minimumRequired`. Check it with [the status](#3-check-the-status).

**3. Try the operation again**

Operations that were rejected with `PREPAID_REQUIRED` were not created: create them again.

> [!NOTE]
>
> Paying is never blocked by this error. If your company has no active plans, the error does not apply.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.