# Invoices

Invoices let you request crypto payments to a specific address for a specific token amount. Vilna tracks incoming transactions in real time, detects partial payments, overpayments, and confirms payments once they reach the required block depth. Each invoice has a public URL you can share with payers - no authentication required on their end. To see how invoices fit into a broader payment flow, check the [payment processing use case](/business/use-cases#multi-network-payment-processing).

Before you begin
You need a Vilna API key with `api:invoice:read` and `api:invoice:write` permissions. See [Authentication](/guides/authentication) for details.

## Invoice lifecycle

An invoice moves through the following statuses:

| Status | Description |
|  --- | --- |
| `pending` | Waiting for payment. No transactions received yet. |
| `underpaid` | Partial payment received. The amount is below the requested total. |
| `paid` | Full amount received. Waiting for block confirmations. |
| `confirmed` | Payment confirmed with sufficient block confirmations. |
| `overpaid` | Received more than the requested amount. |
| `cancelled` | Cancelled by the merchant. |
| `expired` | The invoice passed its deadline without receiving full payment. |


Status transitions:

| From | To | Trigger |
|  --- | --- | --- |
| `pending` | `paid` | Full payment received |
| `pending` | `underpaid` | Partial payment received |
| `pending` | `overpaid` | More than required amount received |
| `pending` | `cancelled` | Cancelled by merchant |
| `pending` | `expired` | Deadline passed |
| `underpaid` | `paid` | Remaining amount received |
| `underpaid` | `overpaid` | More than remaining amount received |
| `underpaid` | `cancelled` | Cancelled by merchant |
| `underpaid` | `expired` | Deadline passed |
| `paid` | `confirmed` | Block confirmations reached |
| `overpaid` | `confirmed` | Block confirmations reached |


`confirmed`, `cancelled`, and `expired` are terminal - no further transitions.

## Create a crypto payment invoice

To create an invoice, specify the deposit address, token (as a CAIP-19 asset identifier), amount in base units, and an expiration time. The address must belong to your project and must not already have an active invoice.

curl

```bash
curl -X POST "https://api.vilna.io/v1/invoices" \
  -H "X-Api-Key: ${VILNA_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
    "asset_gid": "eip155:56/bep20:0x55d398326f99059fF775485246999027B3197955",
    "amount": "42500000",
    "expires_at": "2026-06-15T18:00:00Z",
    "metadata": {
      "order_id": "ORD-20260615-1042",
      "customer": "john@example.com"
    }
  }'
```

TypeScript

```typescript
const { data, error } = await client.POST("/invoices", {
  body: {
    address: "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
    asset_gid: "eip155:56/bep20:0x55d398326f99059fF775485246999027B3197955",
    amount: "42500000",
    expires_at: "2026-06-15T18:00:00Z",
    metadata: {
      order_id: "ORD-20260615-1042",
      customer: "john@example.com",
    },
  },
});

if (error) {
  console.error(error.detail);
} else {
  console.log("Invoice created:", data.item.id);
  console.log("Status:", data.item.status); // "pending"
}
```

**Response** (abbreviated):


```json
{
  "item": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
    "asset_gid": "eip155:56/bep20:0x55d398326f99059fF775485246999027B3197955",
    "amount": "42500000",
    "received_amount": "0",
    "status": "pending",
    "expires_at": "2026-06-15T18:00:00Z",
    "metadata": {
      "order_id": "ORD-20260615-1042",
      "customer": "john@example.com"
    },
    "created_at": "2026-06-15T10:30:00Z",
    "updated_at": "2026-06-15T10:30:00Z",
    "status_logs": [
      {
        "status": "pending",
        "comment": null,
        "changed_at": "2026-06-15T10:30:00Z"
      }
    ],
    "transactions": []
  },
  "references": {
    "tokens": {},
    "blockchains": {}
  }
}
```

Key fields:

- **`amount`** - the requested payment in base units (smallest token denomination). For USDT on BSC with 6 decimals, `"42500000"` equals 42.5 USDT.
- **`metadata`** - optional key-value pairs for linking invoices to your internal records.
- **`expires_at`** - the payment deadline. After this time, the invoice moves to `expired` if full payment has not arrived.


One invoice per address
An address can have only one active invoice at a time. Attempting to create a second invoice for the same address returns a `409 Conflict` error.

## Share the payment page

Every invoice has a public endpoint that requires no authentication. The invoice UUID acts as the access token - anyone with the link can view the payment details.

curl

```bash
curl "https://api.vilna.io/v1/public/invoices/550e8400-e29b-41d4-a716-446655440000"
```

TypeScript

```typescript
// No API key needed - this is a public endpoint
const response = await fetch(
  "https://api.vilna.io/v1/public/invoices/550e8400-e29b-41d4-a716-446655440000"
);
const invoice = await response.json();

console.log("Pay to:", invoice.item.address);
console.log("Amount:", invoice.item.amount.formatted, "USDT");
```

The public response includes the address, amount, status, and transaction history - everything a payer needs. It excludes merchant-only fields like `metadata`.

Use this endpoint to build custom payment pages, or direct payers to your hosted payment UI.

## Check invoice status

Retrieve full invoice details, including the status log and detected transactions.

curl

```bash
curl "https://api.vilna.io/v1/invoices/550e8400-e29b-41d4-a716-446655440000" \
  -H "X-Api-Key: ${VILNA_API_KEY}"
```

TypeScript

```typescript
const { data, error } = await client.GET("/invoices/{invoice_id}", {
  params: { path: { invoice_id: "550e8400-e29b-41d4-a716-446655440000" } },
});

if (data) {
  console.log("Status:", data.item.status);
  console.log("Received:", data.item.received_amount);
  console.log("Transactions:", data.item.transactions.length);
}
```

Once a payment arrives, the response includes transaction details:


```json
{
  "item": {
    "status": "paid",
    "received_amount": "42500000",
    "transactions": [
      {
        "hash": "0xabc123def456789012345678901234567890123456789012345678901234abcd",
        "block_number": 12345678,
        "is_success": true,
        "detected_at": "2026-06-15T12:05:00Z",
        "confirmed_at": null,
        "deposits": [
          {
            "asset_gid": "eip155:56/bep20:0x55d398326f99059fF775485246999027B3197955",
            "amount": "42500000",
            "is_matched": true
          }
        ]
      }
    ],
    "status_logs": [
      { "status": "pending", "comment": null, "changed_at": "2026-06-15T10:30:00Z" },
      { "status": "paid", "comment": null, "changed_at": "2026-06-15T12:05:00Z" }
    ]
  }
}
```

Each transaction includes:

- **`is_success`** - whether the transaction succeeded on-chain. Failed transactions appear in the list but do not count toward payment.
- **`confirmed_at`** - `null` until the transaction reaches the required number of block confirmations.
- **`deposits`** - individual token movements within the transaction. The `is_matched` flag indicates whether the token matches the one the invoice expects.


## Handle partial payments and overpayments

Vilna tracks every deposit and updates the invoice status automatically:

- **Partial payment**: The invoice moves to `underpaid`. The `received_amount` field reflects the running total. Additional payments accumulate until the full amount is reached.
- **Full payment**: The invoice moves to `paid`, then to `confirmed` once block confirmations are sufficient.
- **Overpayment**: If the total received exceeds the requested amount, the status moves to `overpaid`. Handle refunds in your application logic.


You can monitor these transitions by polling the invoice endpoint or by setting up [webhook notifications](/guides/events) for invoice status changes.

## List invoices

Retrieve all invoices with optional status filtering and pagination.

curl

```bash
# List all pending and underpaid invoices
curl "https://api.vilna.io/v1/invoices?status=pending&status=underpaid&limit=20&page=1" \
  -H "X-Api-Key: ${VILNA_API_KEY}"
```

TypeScript

```typescript
const { data, error } = await client.GET("/invoices", {
  params: {
    query: {
      status: ["pending", "underpaid"],
      limit: 20,
      page: 1,
    },
  },
});

if (data) {
  for (const invoice of data.items) {
    console.log(invoice.id, invoice.status, invoice.amount);
  }
  console.log("Total:", data.meta.total);
}
```

The list endpoint returns `InvoiceSummary` objects (without `status_logs` and `transactions`). Use [`GET /invoices/{invoice_id}`](/apis/platform/api/invoice/get-invoice) for full details on a specific invoice.

## Cancel an invoice

Cancel an active invoice to free the address for new invoices. Only invoices in `pending` or `underpaid` status can be cancelled. You can include an optional comment (up to 64 characters) explaining the reason.

curl

```bash
curl -X POST "https://api.vilna.io/v1/invoices/550e8400-e29b-41d4-a716-446655440000/actions/cancel" \
  -H "X-Api-Key: ${VILNA_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "comment": "Customer requested cancellation"
  }'
```

TypeScript

```typescript
const { error } = await client.POST(
  "/invoices/{invoice_id}/actions/cancel",
  {
    params: { path: { invoice_id: "550e8400-e29b-41d4-a716-446655440000" } },
    body: {
      comment: "Customer requested cancellation",
    },
  }
);

if (!error) {
  console.log("Invoice cancelled");
}
```

A successful cancellation returns `204 No Content`. The operation is idempotent - cancelling an already-cancelled invoice returns the same response.

Request body is optional
The `comment` field is optional. You can send an empty body or omit the request body entirely.

## Next steps

Events & Monitoring
Set up webhooks to receive real-time invoice status updates instead of polling

Core Concepts
Learn about CAIP identifiers, base unit amounts, and the response envelope pattern

Integration Patterns
Deposit detection, payment processing, and other common integration scenarios

Platform API reference
Full endpoint catalog including invoice endpoints with request and response schemas