# Client API: Billing

> Invoices, credit balance, top-ups, and hourly billing controls.

Source: https://docs.coritan.com/api/reference/client/billing/

Invoices, credit balance, top-ups, and hourly billing controls.

Base URL: `https://api.coritan.com/api/v1`. Paths below are complete.

To try these requests in the browser, open the [interactive Client API reference](https://api.coritan.com/docs).

## Operations

| Method | Path | Summary |
| --- | --- | --- |
| GET | [`/api/v1/billing/credit`](#op-get-api-v1-billing-credit) | Get credit |
| GET | [`/api/v1/billing/crypto/address`](#op-get-api-v1-billing-crypto-address) | This user's permanent deposit address for the network's chain family |
| GET | [`/api/v1/billing/crypto/assets`](#op-get-api-v1-billing-crypto-assets) | Assets we can accept right now |
| POST | [`/api/v1/billing/crypto/check`](#op-post-api-v1-billing-crypto-check) | Look for this customer's payment now, and say where it stands |
| GET | [`/api/v1/billing/crypto/deposits`](#op-get-api-v1-billing-crypto-deposits) | Deposit history, including transfers still confirming |
| GET | [`/api/v1/billing/crypto/deposits/{deposit_id}`](#op-get-api-v1-billing-crypto-deposits-deposit-id) | One deposit, for polling while it confirms |
| GET | [`/api/v1/billing/crypto/plan`](#op-get-api-v1-billing-crypto-plan) | Every way to pay this amount in crypto, cheapest first |
| GET | [`/api/v1/billing/crypto/quote`](#op-get-api-v1-billing-crypto-quote) | How much to send for a target USD credit, itemised |
| GET | [`/api/v1/billing/hourly-eligibility`](#op-get-api-v1-billing-hourly-eligibility) | Check hourly eligibility |
| GET | [`/api/v1/billing/invoices`](#op-get-api-v1-billing-invoices) | Newest first, ties by id so a page boundary never repeats or skips a row |
| GET | [`/api/v1/billing/invoices/{invoice_id}`](#op-get-api-v1-billing-invoices-invoice-id) | Get invoice |
| POST | [`/api/v1/billing/invoices/{invoice_id}/apply-credit`](#op-post-api-v1-billing-invoices-invoice-id-apply-credit) | Apply account credit to an unpaid invoice (unlocks provision / plan change when paid) |
| POST | [`/api/v1/billing/topup/checkout`](#op-post-api-v1-billing-topup-checkout) | Hosted checkout to buy USD account credit without a saved payment method |
| GET | [`/api/v1/billing/topup/config`](#op-get-api-v1-billing-topup-config) | Get topup config |
| PUT | [`/api/v1/billing/topup/config`](#op-put-api-v1-billing-topup-config) | Update topup config |
| POST | [`/api/v1/billing/topup/confirm-payment`](#op-post-api-v1-billing-topup-confirm-payment) | Finalize a top-up PaymentIntent after SCA and credit the user balance |
| POST | [`/api/v1/billing/topup/manual`](#op-post-api-v1-billing-topup-manual) | Manually top up credit balance by charging a payment method |
| POST | [`/api/v1/billing/topup/pay-intent`](#op-post-api-v1-billing-topup-pay-intent) | Create an in-page Stripe Payment Element intent for a credit top-up |
| POST | [`/api/v1/billing/topup/paypal-capture`](#op-post-api-v1-billing-topup-paypal-capture) | Capture an approved PayPal order and credit the platform user balance |
| GET | [`/api/v1/billing/transactions`](#op-get-api-v1-billing-transactions) | As listinvoices: newest first, ties by id, withtotal for a TransactionPage |

### Get credit {#op-get-api-v1-billing-credit}

`GET /api/v1/billing/credit`

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |

Fields of a `200` response:

| Field | Type |
| --- | --- |
| `balance` | number |
| `currency` | string |
| `recent_transactions` | array of CreditLedgerEntry |
| `recent_transactions[].id` | integer |
| `recent_transactions[].amount` | number |
| `recent_transactions[].balance_after` | number |
| `recent_transactions[].description` | string |
| `recent_transactions[].created_at` | string (date-time) |

### This user's permanent deposit address for the network's chain family {#op-get-api-v1-billing-crypto-address}

`GET /api/v1/billing/crypto/address`

This user's permanent deposit address for the network's chain family.

Uses the write session because a first call issues an address. Repeat calls
return the same one.

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `network` | query | string | yes | Network key, e.g. base or bitcoin |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### Assets we can accept right now {#op-get-api-v1-billing-crypto-assets}

`GET /api/v1/billing/crypto/assets`

Assets we can accept right now.

Excludes anything enabled but unpriced, since a deposit to it would be held
rather than credited.

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |

### Look for this customer's payment now, and say where it stands {#op-post-api-v1-billing-crypto-check}

`POST /api/v1/billing/crypto/check`

Look for this customer's payment now, and say where it stands.

What the refresh button calls, and what the wallet and invoice panels poll
while they are open. Before this the panels could only re-read the database
and wait for the background scan to notice. A customer who had just paid
had no way to ask, and the honest answer to "is it there yet" was "wait".

Rate limited because it reads the chain, which is spending money on the
customer's behalf. The chain read is separately throttled inside, so several
tabs polling on their own timers cost one read rather than one each; below
that threshold this still answers with current deposit state, which is what
was actually being asked for.

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Request body

`application/json`

Type: object or null.

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### Deposit history, including transfers still confirming {#op-get-api-v1-billing-crypto-deposits}

`GET /api/v1/billing/crypto/deposits`

Deposit history, including transfers still confirming.

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Default: `50`. |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### One deposit, for polling while it confirms {#op-get-api-v1-billing-crypto-deposits-deposit-id}

`GET /api/v1/billing/crypto/deposits/{deposit_id}`

One deposit, for polling while it confirms.

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required |
| --- | --- | --- | --- |
| `deposit_id` | path | integer | yes |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### Every way to pay this amount in crypto, cheapest first {#op-get-api-v1-billing-crypto-plan}

`GET /api/v1/billing/crypto/plan`

Every way to pay this amount in crypto, cheapest first.

Answers the question a customer has ("how much do I send to end up with
$X?") instead of showing a rate and a spread and leaving the arithmetic
to them. Each option states what it credits, which is never
below the target, and says outright when a chain's fee would eat the
deposit.

``invoice_id`` targets the invoice's outstanding balance instead of a bare
amount, so the figure shown is the one that settles it.

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required |
| --- | --- | --- | --- |
| `amount` | query | number or string | yes |
| `invoice_id` | query | integer or null | no |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### How much to send for a target USD credit, itemised {#op-get-api-v1-billing-crypto-quote}

`GET /api/v1/billing/crypto/quote`

How much to send for a target USD credit, itemised.

Quoting records an intent, which is what lets the deposit be credited at
the rate shown here and applied to ``invoice_id`` when it lands, instead
of arriving as a balance the customer then has to spend by hand.

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required |
| --- | --- | --- | --- |
| `network` | query | string | yes |
| `asset` | query | string | yes |
| `amount` | query | number or string | yes |
| `invoice_id` | query | integer or null | no |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### Check hourly eligibility {#op-get-api-v1-billing-hourly-eligibility}

`GET /api/v1/billing/hourly-eligibility`

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |

### Newest first, ties by id so a page boundary never repeats or skips a row {#op-get-api-v1-billing-invoices}

`GET /api/v1/billing/invoices`

Newest first, ties by id so a page boundary never repeats or skips a row.

The bare list is the answer every existing caller reads; ``with_total``
wraps the same page in ``InvoicePage``.

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | Default: `1`. |
| `limit` | query | integer | no | Default: `50`. |
| `status_filter` | query | string or null | no |  |
| `with_total` | query | boolean | no | Default: `False`. |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### Get invoice {#op-get-api-v1-billing-invoices-invoice-id}

`GET /api/v1/billing/invoices/{invoice_id}`

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required |
| --- | --- | --- | --- |
| `invoice_id` | path | integer | yes |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

Fields of a `200` response:

| Field | Type |
| --- | --- |
| `id` | integer |
| `invoice_number` | string |
| `user_id` | integer |
| `subtotal` | number |
| `tax` | number |
| `total` | number |
| `amount_paid` | number |
| `currency` | string |
| `status` | string |
| `due_date` | string (date-time) |
| `paid_at` | string (date-time) or null |
| `notes` | string or null |
| `items` | array of InvoiceItemResponse |
| `items[].id` | integer |
| `items[].service_id` | integer or null |
| `items[].description` | string |
| `items[].item_type` | string |
| `items[].quantity` | number |
| `items[].unit_price` | number |
| `items[].total` | number |
| `created_at` | string (date-time) |

### Apply account credit to an unpaid invoice (unlocks provision / plan change when paid) {#op-post-api-v1-billing-invoices-invoice-id-apply-credit}

`POST /api/v1/billing/invoices/{invoice_id}/apply-credit`

Apply account credit to an unpaid invoice (unlocks provision / plan change when paid).

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required |
| --- | --- | --- | --- |
| `invoice_id` | path | integer | yes |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### Hosted checkout to buy USD account credit without a saved payment method {#op-post-api-v1-billing-topup-checkout}

`POST /api/v1/billing/topup/checkout`

Hosted checkout to buy USD account credit without a saved payment method.

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Request body

`application/json` (required)

| Field | Type | Required |
| --- | --- | --- |
| `amount` | number or string | yes |
| `currency` | string or null | no |
| `gateway_name` | string or null | no |
| `return_url` | string or null | no |
| `cancel_url` | string or null | no |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### Get topup config {#op-get-api-v1-billing-topup-config}

`GET /api/v1/billing/topup/config`

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |

Fields of a `200` response:

| Field | Type |
| --- | --- |
| `auto_topup_enabled` | boolean |
| `auto_topup_threshold` | string |
| `auto_topup_amount` | string |
| `credit_balance` | string |
| `total_deposited` | string |
| `hourly_billing_unlocked` | boolean |

### Update topup config {#op-put-api-v1-billing-topup-config}

`PUT /api/v1/billing/topup/config`

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Request body

`application/json` (required)

| Field | Type | Required |
| --- | --- | --- |
| `enabled` | boolean | yes |
| `threshold` | number or string or null | no |
| `amount` | number or string or null | no |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### Finalize a top-up PaymentIntent after SCA and credit the user balance {#op-post-api-v1-billing-topup-confirm-payment}

`POST /api/v1/billing/topup/confirm-payment`

Finalize a top-up PaymentIntent after SCA and credit the user balance.

USD credit comes from PaymentIntent metadata ``base_amount`` (set at
checkout/charge). Optional client ``amount`` may assert within $0.02.

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required |
| --- | --- | --- | --- |
| `gateway_name` | query | string | yes |
| `payment_intent_id` | query | string | yes |
| `amount` | query | number or string or null | no |
| `currency` | query | string or null | no |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### Manually top up credit balance by charging a payment method {#op-post-api-v1-billing-topup-manual}

`POST /api/v1/billing/topup/manual`

Manually top up credit balance by charging a payment method.

``data.amount`` is always USD. The gateway is charged in the user's
preferred pay currency via admin FX rates (same path as auto-topup).

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Request body

`application/json` (required)

| Field | Type | Required |
| --- | --- | --- |
| `amount` | number or string | yes |
| `payment_method_id` | integer | yes |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### Create an in-page Stripe Payment Element intent for a credit top-up {#op-post-api-v1-billing-topup-pay-intent}

`POST /api/v1/billing/topup/pay-intent`

Create an in-page Stripe Payment Element intent for a credit top-up.

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Request body

`application/json` (required)

| Field | Type | Required |
| --- | --- | --- |
| `amount` | number or string | yes |
| `currency` | string or null | no |
| `gateway_name` | string or null | no |
| `save_method` | boolean | no |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### Capture an approved PayPal order and credit the platform user balance {#op-post-api-v1-billing-topup-paypal-capture}

`POST /api/v1/billing/topup/paypal-capture`

Capture an approved PayPal order and credit the platform user balance.

USD credit is derived from the order (``custom_id=topup:<usd>``), not the
client ``amount`` alone. Optional ``amount`` may assert within $0.02.

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required |
| --- | --- | --- | --- |
| `order_id` | query | string | yes |
| `gateway_name` | query | string | yes |
| `amount` | query | number or string or null | no |
| `currency` | query | string or null | no |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |

### As listinvoices: newest first, ties by id, withtotal for a TransactionPage {#op-get-api-v1-billing-transactions}

`GET /api/v1/billing/transactions`

As ``list_invoices``: newest first, ties by id, ``with_total`` for a
``TransactionPage``. ``type_filter=credit`` is credit added or used.

Authentication: an access token, sent as `Authorization: Bearer <token>`.

#### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | integer | no | Default: `1`. |
| `limit` | query | integer | no | Default: `50`. |
| `type_filter` | query | string or null | no |  |
| `with_total` | query | boolean | no | Default: `False`. |

#### Responses

| Status | Meaning |
| --- | --- |
| `200` | Success. |
| `422` | The request is not valid. `detail` lists each problem. |
