# Get Invoice

> Retrieve one invoice by ID, inspect its payment and confirmation state, and reconcile the response with your own external order reference.

`GET /v1/invoices/:invoice_id`

:::endpoint
GET /v1/invoices/:invoice_id
:::

**API key:** `Payment`

Returns the current state of an invoice. It uses the same response contract as invoice creation; the `payment` object appears once a token and network are selected.

## Response (200 OK)

```json
{
  "invoice_id": "inv_5CcyDYmMUGtzYL10q0Iimr",
  "project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
  "status": "paid",
  "is_final": true,
  "is_test": false,
  "payment_url": "https://checkout.paymos.io/invoice/inv_5CcyDYmMUGtzYL10q0Iimr",
  "order": {
    "external_id": "order-12345",
    "client_id": "customer-67890",
    "amount": "50.00",
    "currency": "USDT",
    "network": "TRC20"
  },
  "payment": {
    "currency": "USDT",
    "network": "TRC20",
    "chain_id": 728126428,
    "contract_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
    "expected": "50.00",
    "address": "TLfGqSVPbET2KFczh3xb12Ja2RKfMCBPgp",
    "exchange_rate": "1.000000",
    "paid": "50.00",
    "remaining": "0",
    "fee": "0.50",
    "net": "49.50",
    "transfers": [
      {
        "tx_hash": "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
        "amount": "50.00",
        "status": "confirmed",
        "created_at": 1739280712,
        "confirmed_at": 1739280912,
        "required_confirmations": 19,
        "estimated_confirmation_at": 1739280772,
        "explorer_url": "https://tronscan.org/#/transaction/abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
      }
    ]
  },
  "created_at": 1739280600,
  "updated_at": 1739280912,
  "expires_at": 1739284200,
  "completed_at": 1739280912
}
```

## Payment fields

When `payment` is present, it contains:

| Field | Type | Description |
|-------|------|-------------|
| `payment.currency` | string | Crypto asset symbol selected for payment (e.g. `USDT`, `USDC`) |
| `payment.network` | string | Blockchain network code (e.g. `ERC20`, `TRC20`, `BEP20`, `POLYGON`, `BASE`, `TON`, `SOL`) |
| `payment.chain_id` | integer | Numeric chain id matching EIP-155 / TRON / TON conventions (e.g. `1` for Ethereum mainnet, `728126428` for Tron) |
| `payment.contract_address` | string? | The token's on-chain identifier — contract address on EVM and Tron, jetton master on TON, SPL mint on Solana. Nullable in the schema because a native coin has none; no native coin is an accepted asset, so on a real invoice it always carries a value. `GET /v1/payment-channel-deposits/:id` states the same rule for its own `contract_address` |
| `payment.expected` | string | Expected crypto amount the customer must send |
| `payment.address` | string? | Deposit address the customer pays to. While the invoice sits in `awaiting_client` the whole `payment` object is absent from the response, so this field never surfaces as `null` on a pending invoice — there is simply no `payment` to read yet |
| `payment.exchange_rate` | string? | Fiat-to-crypto exchange rate that produced `expected` |
| `payment.paid` | string? | Sum of confirmed incoming transfers |
| `payment.remaining` | string? | Outstanding amount the customer still needs to send (`expected - paid`, never negative). Only present once at least one transfer was confirmed |
| `payment.fee` | string? | Platform fee amount taken from `paid` |
| `payment.net` | string? | Merchant-receivable amount (`paid - fee`) |
| `payment.transfers` | array? | Per-transfer details — present once at least one on-chain transfer was detected. See below. |

## Transfer Fields

Each entry in `payment.transfers[]`:

| Field | Type | Description |
|-------|------|-------------|
| `tx_hash` | string | On-chain transaction hash (network-specific format) |
| `amount` | string | Transferred crypto amount |
| `status` | string | `confirming` (waiting for confirmations) or `confirmed` |
| `created_at` | unix timestamp | When our pipeline first observed the transfer in a processed block. **Wall-clock on our server**, not on-chain block timestamp — they differ if the pipeline was lagging. Use this (not the top-level invoice `created_at`) for the "seconds remaining" formula below. |
| `confirmed_at` | unix timestamp? | When the transfer reached the required confirmation count. `null` while still pending |
| `required_confirmations` | integer? | Confirmations needed to reach `confirmed` for this transfer's USD value tier. `null` on every network that settles by the chain's own `finalized` commitment instead of a block count — today BNB Smart Chain, Polygon, Solana, Avalanche and Plasma. Branch on the `null`, not on a network list: which chains confirm by finality is operational and can change |
| `estimated_confirmation_at` | unix timestamp? | When the transfer is expected to reach settlement. On block-count networks it is computed at observation time as `transfer.created_at + required_confirmations × network_block_time`; on finality networks, where there is no block count, it is `transfer.created_at` plus that chain's typical finality, so a wait can still be rendered. **Static** — does NOT shift between requests, same value on every poll until the transfer is confirmed (or its USD-tier shifts). For "seconds remaining" compute on the client: `max(0, estimated_confirmation_at − now)`. |
| `explorer_url` | string? | Direct link to this transaction in the network's block explorer (Tronscan / Etherscan / Tonviewer / etc). `null` if the network has no public explorer configured. Safe to render as a clickable link verbatim — no transformation needed. |

## Invoice statuses

Every invoice reports a `status` string. The same value drives the merchant API response, the SSE checkout stream, and the webhook payload — there is one source of truth. The table below is the per-status reference: when the value appears, whether it is terminal, and the precise condition behind it. For the transition diagram and the two creation flows, see [Payment flow](/docs/payment-flow.md).

A **terminal** status is final: the invoice settles there and never moves again. Five statuses are terminal — `paid`, `paid_over`, `underpaid`, `expired`, `cancelled`.

| Status | Terminal | When it applies |
|--------|----------|-----------------|
| `awaiting_client` | No | The opening status for every invoice. No token or network is chosen yet, so no deposit address exists. Cancellation is allowed only here |
| `awaiting_payment` | No | A token, network, and deposit address are locked in. Paymos is watching the address for an incoming transfer |
| `confirming` | No | A transfer landed on-chain and is accruing the confirmations required for its amount tier |
| `underpaid_waiting` | No | Less than the expected amount has cleared and the invoice stays open for the rest. Reached only when `allow_multiple_payments` is `true` |
| `paid` | Yes | The expected amount cleared in full (or within the project's underpayment tolerance) |
| `paid_over` | Yes | More than the expected amount cleared; the whole transfer is credited |
| `underpaid` | Yes | The invoice closed short — either it expired below the expected amount, or a single payment fell short with `allow_multiple_payments` set to `false` |
| `expired` | Yes | The timer ran out with nothing received, or the fiat-flow token-selection window lapsed before a token was picked |
| `cancelled` | Yes | The merchant cancelled the invoice while it was still in `awaiting_client` |

## Errors

See [Error Codes](/docs/errors/codes.md) for the full catalogue. The `type` URI in every error response deep-links to the matching row.
