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

# Credits in pricing plans

This page covers how to configure credits inside a pricing plan: granting credits to customers, pricing usage-based products in credits instead of currency, and handling overage when a wallet runs out.

---

## Granting credits

A wallet grant attaches to a revenue item in a pricing plan. When a subscription is activated, Solvimon deposits the configured credits into the customer's wallet.

To add a wallet grant, click the **three dots** menu on a revenue item and select **Add wallet grant**. You can attach a wallet grant to both recurring and one-off revenue items. On a recurring item, credits are deposited at subscription activation and renewed at the start of each billing period. On a one-off item, credits are deposited once at activation.

![Three dots menu on a revenue item showing the Add wallet grant option](/_fern-files/_img/7f552e024793b7ed727c91308b887b2f1c614b7a41b41ffeda81ead74824a5b6.webp)

Select the **Wallet** to fund, set the **Balance topup** amount, and configure the **Expiry period** if you want unused credits to expire at the end of each period.

![Wallet grant configuration form](/_fern-files/_img/914477cb95482101f4375e2590b0eae35d9cb65cefbd93d998df6a15c0bd48c0.webp)

### When the grant is created

A recurring grant is created at the **start of the pricing period it belongs to**, not when the invoice covering that period is issued and not when it is paid. The customer can spend the credits from the first day of the period, whether you bill in advance or in arrears, and whether or not the invoice has been settled.

Each pricing period produces its own wallet grant with its own `start_at` and `expire_at`, rather than adding to an existing one. That is why a customer can hold several grants in one wallet at the same time, and why consumption follows the [earliest expiry first](/platform-guides/wallets-credits/credit-consumption-and-expiry) rule.

Where the pricing period is shorter than the billing period, one invoice carries several periods and therefore several grants. A monthly item billed twice a year is charged for six months on one invoice and deposits six monthly grants, each valid for its own month, rather than one grant for the whole invoice. A monthly `expiry_policy` then behaves as configured: the customer gets a fresh monthly allowance that expires unused, not half a year of credits at once.

> **Warning**
>
> Because credits are granted at cycle start rather than on payment, a customer who never pays the invoice can still consume that period's credits. If credits must be withheld until payment, price them as a [top-up](/platform-guides/wallets-credits/wallet-top-ups) purchase instead of a recurring grant.

### Grant fields in the API

A grant configured on a pricing item produces a `WalletGrant` on the subscription. The fields you will read back:

| Field                    | Description                                                                            |
| ------------------------ | -------------------------------------------------------------------------------------- |
| `wallet_id`              | The wallet the credits land in                                                         |
| `type`                   | `DEFAULT` for grants that come from the plan, `PROMOTION` for grants issued outside it |
| `grant_credits`          | Credits granted, when the wallet is funded with a credit type                          |
| `grant_amount`           | Monetary amount granted, when the wallet is funded with an amount                      |
| `paid_amount`            | Cash received for the grant. Zero for free and promotional grants                      |
| `start_at` / `expire_at` | When the credits become spendable, and when they stop being spendable                  |

On the pricing item, a credits grant can convert an amount into credits using a conversion rate against a credit type, so the same plan can express the grant in currency and deposit credits. See [Credit revenue & breakage](/platform-guides/wallets-credits/credit-revenue-and-breakage) for how `paid_amount` drives revenue recognition.

### Request

POST [https://test.api.solvimon.com/v\{version}/wallet-grants](https://test.api.solvimon.com/v\{version}/wallet-grants)

**`WalletGrants_postWalletGrants_example`**

```curl WalletGrants_postWalletGrants_example
curl -X POST https://test.api.solvimon.com/v1/wallet-grants \
     -H "X-API-KEY: <apiKey>" \
     -H "Content-Type: application/json" \
     -d '{
  "customer_id": "cust_lwDeeN0vxxHxMWCVef1S",
  "billing_entity_id": "bile_iwDeeN0vxxHxNDCUNS1G",
  "wallet_id": "wall_jwDeeN0vxxHxXDCnRA1I",
  "type": "PROMOTION",
  "description": "Welcome credits – 5,000 Lumen credits",
  "grant_credits": {
    "quantity": "5000",
    "credit_type_id": "ctyp_xwDeeN0vxxHxVWCZif14"
  },
  "start_at": "2026-10-01T00:00:00+02:00",
  "expire_at": "2027-01-01T00:00:00+01:00"
}'
```

**`WalletGrants_postWalletGrants_example`**

```python WalletGrants_postWalletGrants_example
import requests

url = "https://test.api.solvimon.com/v1/wallet-grants"

payload = {
    "customer_id": "cust_lwDeeN0vxxHxMWCVef1S",
    "billing_entity_id": "bile_iwDeeN0vxxHxNDCUNS1G",
    "wallet_id": "wall_jwDeeN0vxxHxXDCnRA1I",
    "type": "PROMOTION",
    "description": "Welcome credits – 5,000 Lumen credits",
    "grant_credits": {
        "quantity": "5000",
        "credit_type_id": "ctyp_xwDeeN0vxxHxVWCZif14"
    },
    "start_at": "2026-10-01T00:00:00+02:00",
    "expire_at": "2027-01-01T00:00:00+01:00"
}
headers = {
    "X-API-KEY": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

**`WalletGrants_postWalletGrants_example`**

```javascript WalletGrants_postWalletGrants_example
const url = 'https://test.api.solvimon.com/v1/wallet-grants';
const options = {
  method: 'POST',
  headers: {'X-API-KEY': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"customer_id":"cust_lwDeeN0vxxHxMWCVef1S","billing_entity_id":"bile_iwDeeN0vxxHxNDCUNS1G","wallet_id":"wall_jwDeeN0vxxHxXDCnRA1I","type":"PROMOTION","description":"Welcome credits – 5,000 Lumen credits","grant_credits":{"quantity":"5000","credit_type_id":"ctyp_xwDeeN0vxxHxVWCZif14"},"start_at":"2026-10-01T00:00:00+02:00","expire_at":"2027-01-01T00:00:00+01:00"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

**`WalletGrants_postWalletGrants_example`**

```go WalletGrants_postWalletGrants_example
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://test.api.solvimon.com/v1/wallet-grants"

	payload := strings.NewReader("{\n  \"customer_id\": \"cust_lwDeeN0vxxHxMWCVef1S\",\n  \"billing_entity_id\": \"bile_iwDeeN0vxxHxNDCUNS1G\",\n  \"wallet_id\": \"wall_jwDeeN0vxxHxXDCnRA1I\",\n  \"type\": \"PROMOTION\",\n  \"description\": \"Welcome credits – 5,000 Lumen credits\",\n  \"grant_credits\": {\n    \"quantity\": \"5000\",\n    \"credit_type_id\": \"ctyp_xwDeeN0vxxHxVWCZif14\"\n  },\n  \"start_at\": \"2026-10-01T00:00:00+02:00\",\n  \"expire_at\": \"2027-01-01T00:00:00+01:00\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("X-API-KEY", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

**`WalletGrants_postWalletGrants_example`**

```ruby WalletGrants_postWalletGrants_example
require 'uri'
require 'net/http'

url = URI("https://test.api.solvimon.com/v1/wallet-grants")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-API-KEY"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"customer_id\": \"cust_lwDeeN0vxxHxMWCVef1S\",\n  \"billing_entity_id\": \"bile_iwDeeN0vxxHxNDCUNS1G\",\n  \"wallet_id\": \"wall_jwDeeN0vxxHxXDCnRA1I\",\n  \"type\": \"PROMOTION\",\n  \"description\": \"Welcome credits – 5,000 Lumen credits\",\n  \"grant_credits\": {\n    \"quantity\": \"5000\",\n    \"credit_type_id\": \"ctyp_xwDeeN0vxxHxVWCZif14\"\n  },\n  \"start_at\": \"2026-10-01T00:00:00+02:00\",\n  \"expire_at\": \"2027-01-01T00:00:00+01:00\"\n}"

response = http.request(request)
puts response.read_body
```

**`WalletGrants_postWalletGrants_example`**

```java WalletGrants_postWalletGrants_example
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://test.api.solvimon.com/v1/wallet-grants")
  .header("X-API-KEY", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"customer_id\": \"cust_lwDeeN0vxxHxMWCVef1S\",\n  \"billing_entity_id\": \"bile_iwDeeN0vxxHxNDCUNS1G\",\n  \"wallet_id\": \"wall_jwDeeN0vxxHxXDCnRA1I\",\n  \"type\": \"PROMOTION\",\n  \"description\": \"Welcome credits – 5,000 Lumen credits\",\n  \"grant_credits\": {\n    \"quantity\": \"5000\",\n    \"credit_type_id\": \"ctyp_xwDeeN0vxxHxVWCZif14\"\n  },\n  \"start_at\": \"2026-10-01T00:00:00+02:00\",\n  \"expire_at\": \"2027-01-01T00:00:00+01:00\"\n}")
  .asString();
```

**`WalletGrants_postWalletGrants_example`**

```php WalletGrants_postWalletGrants_example
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://test.api.solvimon.com/v1/wallet-grants', [
  'body' => '{
  "customer_id": "cust_lwDeeN0vxxHxMWCVef1S",
  "billing_entity_id": "bile_iwDeeN0vxxHxNDCUNS1G",
  "wallet_id": "wall_jwDeeN0vxxHxXDCnRA1I",
  "type": "PROMOTION",
  "description": "Welcome credits – 5,000 Lumen credits",
  "grant_credits": {
    "quantity": "5000",
    "credit_type_id": "ctyp_xwDeeN0vxxHxVWCZif14"
  },
  "start_at": "2026-10-01T00:00:00+02:00",
  "expire_at": "2027-01-01T00:00:00+01:00"
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'X-API-KEY' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

**`WalletGrants_postWalletGrants_example`**

```csharp WalletGrants_postWalletGrants_example
using RestSharp;

var client = new RestClient("https://test.api.solvimon.com/v1/wallet-grants");
var request = new RestRequest(Method.POST);
request.AddHeader("X-API-KEY", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"customer_id\": \"cust_lwDeeN0vxxHxMWCVef1S\",\n  \"billing_entity_id\": \"bile_iwDeeN0vxxHxNDCUNS1G\",\n  \"wallet_id\": \"wall_jwDeeN0vxxHxXDCnRA1I\",\n  \"type\": \"PROMOTION\",\n  \"description\": \"Welcome credits – 5,000 Lumen credits\",\n  \"grant_credits\": {\n    \"quantity\": \"5000\",\n    \"credit_type_id\": \"ctyp_xwDeeN0vxxHxVWCZif14\"\n  },\n  \"start_at\": \"2026-10-01T00:00:00+02:00\",\n  \"expire_at\": \"2027-01-01T00:00:00+01:00\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

**`WalletGrants_postWalletGrants_example`**

```swift WalletGrants_postWalletGrants_example
import Foundation

let headers = [
  "X-API-KEY": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "customer_id": "cust_lwDeeN0vxxHxMWCVef1S",
  "billing_entity_id": "bile_iwDeeN0vxxHxNDCUNS1G",
  "wallet_id": "wall_jwDeeN0vxxHxXDCnRA1I",
  "type": "PROMOTION",
  "description": "Welcome credits – 5,000 Lumen credits",
  "grant_credits": [
    "quantity": "5000",
    "credit_type_id": "ctyp_xwDeeN0vxxHxVWCZif14"
  ],
  "start_at": "2026-10-01T00:00:00+02:00",
  "expire_at": "2027-01-01T00:00:00+01:00"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://test.api.solvimon.com/v1/wallet-grants")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Response (201)

```json
{
  "object_type": "string",
  "id": "string",
  "customer_id": "string",
  "billing_entity_id": "string",
  "wallet_id": "string",
  "type": "DEFAULT",
  "source": "FIXED",
  "seat": {
    "customer_id": "string",
    "product_item_id": "string"
  },
  "over_granted": {
    "credits": {
      "quantity": "string",
      "credit_type_id": "string",
      "credit_type": {
        "object_type": "string",
        "id": "string",
        "reference": "string",
        "status": "DRAFT",
        "name": "string",
        "description": "string",
        "unit_name": {
          "singular": "string",
          "plural": "string"
        },
        "created_at": "string",
        "updated_at": "string"
      }
    },
    "amount": {
      "quantity": "string",
      "currency": "AED"
    }
  },
  "description": "string",
  "grant_amount": {
    "quantity": "string",
    "currency": "AED"
  },
  "grant_credits": {
    "quantity": "string",
    "credit_type_id": "string",
    "credit_type": {
      "object_type": "string",
      "id": "string",
      "reference": "string",
      "status": "DRAFT",
      "name": "string",
      "description": "string",
      "unit_name": {
        "singular": "string",
        "plural": "string"
      },
      "created_at": "string",
      "updated_at": "string"
    }
  },
  "paid_amount": {
    "quantity": "string",
    "currency": "AED"
  },
  "start_at": "2024-01-15T09:30:00Z",
  "expire_at": "2024-01-15T09:30:00Z",
  "created_at": "string",
  "reverted_at": "string",
  "revert_reason": "INVOICE_VOIDED"
}
```

Create a grant directly through this endpoint to issue credits outside a pricing plan, for example a goodwill grant after an incident. Set `type` to `PROMOTION` so the grant is distinguishable in reporting, and set `paid_amount` to zero so it recognises no revenue when consumed.

---

## Pricing usage in credits

A usage-based revenue item can be configured to consume credits from a wallet instead of charging currency directly. The price is expressed as a number of credits per unit of usage.

When adding a usage-based revenue item to the pricing plan, set the pricing currency to the credit type you want to consume, then define the rate (e.g. 1 credit per 30 seconds, or 1.5 credits per 50 seconds).

You can use meter property filters to set different rates for different conditions, exactly as you would with currency-based usage pricing.

![Usage-based revenue item priced in credits, with two rate rules based on processing mode](/_fern-files/_img/e60f160bb1bd9410cd11f3682724c8ffbd757575fb0143a1785dcf92e07641c8.webp)

> **Note**
>
> Usage items priced in credits appear as "Not billed" on the invoice in terms of currency. The credit deduction is tracked on the wallet balance, not as a monetary charge.

---

## Overage

When a customer's wallet balance reaches zero, you can charge them for additional usage in currency by adding a Credit-type revenue item to the pricing plan.

Start by creating a revenue item in the product catalog and set its **Model type** to **Credits**.

![Revenue item creation form with Model type set to Credits](/_fern-files/_img/c64fc85d94f04852b8439df3836502b12a1b997b3e37d0af65c867b61b19bf53.webp)

Once the revenue item is created, add it to the pricing plan and set the price per credit in currency (e.g. \$0.10 per credit).

![Credit overage revenue item in the pricing plan](/_fern-files/_img/7925b21f6476bf252d53dbe68cff0429151040cfbb0928e23b5b4d3e06029135.webp)

> **Warning**
>
> If you don't add a Credits overage item, usage after the wallet is empty will not be charged. Make sure your pricing plan covers the overage case if that's a requirement.

Overage is one way to handle an empty wallet. The other is to refill it: see [Wallet top-ups](/platform-guides/wallets-credits/wallet-top-ups).

---

## Billing and pricing periods follow the credits item

A pricing item config priced in credits takes its `billing_period` and `pricing_period` from the `CREDITS` item that settles the credit type it consumes. It cannot declare periods of its own.

This keeps overage on the same invoice as the credits it draws on. Where a credits-priced config used to declare its own periods and they disagreed with the credits item's cadence, each pricing period's overage could land on an invoice of its own instead of the invoice for the billing period.

> **Warning**
>
> Solvimon rejects three configurations: a credits-priced config with its own `billing_period` or `pricing_period`, a `CREDITS` item that disagrees with the config's cadence, and a pricing period wider than its billing period.

---