Billing

Premium is paid from prepaid credit. You add credit with a crypto deposit, and spend it on premium time. Billing belongs to accounts: anonymous sessions have no balance, and every endpoint here except the public lists needs a logged-in session. API keys cannot call billing endpoints.

Amounts

  • Every amount is USD as a decimal string with exactly 18 fractional digits: "4.990000000000000000" is $4.99. Parse it as a decimal or as an integer number of 10^-18 USD units. Never parse it as a floating-point number.
  • Crypto amounts (amount, min_deposit) are integer strings in the asset's smallest unit, with decimals places. For example min_deposit "1000000" with decimals 6 is 1 USDC.
  • Credit enters by deposit or referral reward and leaves only by being spent. There is no withdrawal or refund endpoint.

GET/v1/billing/plans

Get plans

The plans you can buy. No authentication.

Response

An array of plans.

  • Name
    plan
    Type
    string
    Description

    premium.

  • Name
    price_per_month
    Type
    string
    Description

    Price of one 30-day period, USD.

  • Name
    max_months
    Type
    integer
    Description

    Most months one purchase can cover.

Request

GET
/v1/billing/plans
curl https://mail.nullnet.sh/v1/billing/plans

Response

[
  {
    "plan": "premium",
    "price_per_month": "4.990000000000000000",
    "max_months": 12
  }
]

GET/v1/billing/balance

Get the balance

The account's credit and subscription.

Response

  • Name
    balance
    Type
    string
    Description

    Credit balance, USD.

  • Name
    currency
    Type
    string
    Description

    USD.

  • Name
    decimals
    Type
    integer
    Description

    18.

  • Name
    subscription
    Type
    object | null
    Description

    plan, period_end (timestamp) and active (whether period_end is in the future), or null if the account never subscribed.

Request

GET
/v1/billing/balance
curl https://mail.nullnet.sh/v1/billing/balance \
  -H "Authorization: Bearer {token}"

Response

{
  "balance": "12.500000000000000000",
  "currency": "USD",
  "decimals": 18,
  "subscription": {
    "plan": "premium",
    "period_end": "2026-11-25T08:00:00Z",
    "active": true
  }
}

GET/v1/billing/ledger

List ledger entries

Every change to the balance, newest first, as a page. The balance is the sum of all delta values.

Optional query parameters

  • Name
    limit
    Type
    integer
    Description

    Page size, 1–100. Default 25.

  • Name
    cursor
    Type
    string
    Description

    next_cursor from the previous page.

Entry fields

  • Name
    id
    Type
    string
    Description

    Entry id.

  • Name
    delta
    Type
    string
    Description

    Signed change, USD. Negative for spending.

  • Name
    kind
    Type
    string
    Description

    What caused it (see below).

  • Name
    ref_id
    Type
    string | null
    Description

    Related object, for example the deposit id for deposit.

  • Name
    memo
    Type
    string | null
    Description

    Description.

  • Name
    created_at
    Type
    timestamp
    Description

    When it was posted.

Kinds

  • Name
    deposit
    Description

    A confirmed crypto deposit. ref_id is the deposit id.

  • Name
    premium_charge
    Description

    Premium time bought.

  • Name
    referral_reward
    Description

    Your share of a referred account's deposit. See referrals.

  • Name
    referral_bonus
    Description

    A one-off referral bonus.

  • Name
    gift_code
    Description

    Credit from a gift code.

  • Name
    gift_code_purchase
    Description

    Credit spent on a gift code.

  • Name
    manual_payout, admin_adjust
    Description

    Entries made by support, with a memo.

  • Name
    reversal
    Description

    Reverses an earlier entry.

Request

GET
/v1/billing/ledger
curl -G https://mail.nullnet.sh/v1/billing/ledger \
  -H "Authorization: Bearer {token}" \
  -d limit=25

Response

{
  "items": [
    {
      "id": "019a2fa0-1b2c-7d3e-8f40-5a6b7c8d9e0f",
      "delta": "-4.990000000000000000",
      "kind": "premium_charge",
      "ref_id": null,
      "memo": null,
      "created_at": "2026-10-26T08:10:00Z"
    },
    {
      "id": "019a2f9a-7c8d-7e9f-a0b1-c2d3e4f5a6b7",
      "delta": "17.490000000000000000",
      "kind": "deposit",
      "ref_id": "019a2f95-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
      "memo": null,
      "created_at": "2026-10-26T08:02:00Z"
    }
  ],
  "next_cursor": null
}

POST/v1/billing/subscribe

Buy premium

Spends credit on premium time: price_per_month × months is charged at once. The subscription is extended by 30 days per month, from its current end if it is still active, otherwise from now. The account becomes premium immediately; fetch GET /v1/account again for the new limits.

If the balance is short, the request returns 402 payment_required with the balance and the price in the message, and nothing changes.

When the paid period ends, the account returns to the free tier automatically. Inboxes above the free limit are kept until they expire; new inboxes follow the free limits.

Required attributes

  • Name
    months
    Type
    integer
    Description

    1 to max_months from the plan.

Response

  • Name
    charged
    Type
    string
    Description

    Amount charged, USD.

  • Name
    period_end
    Type
    timestamp
    Description

    New end of the paid period.

  • Name
    balance
    Type
    string
    Description

    Balance after the charge, USD.

Request

POST
/v1/billing/subscribe
curl https://mail.nullnet.sh/v1/billing/subscribe \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"months": 1}'

Response

{
  "charged": "4.990000000000000000",
  "period_end": "2026-11-25T08:10:00Z",
  "balance": "12.500000000000000000"
}

Deposits

Credit is bought with crypto only. The flow:

  1. List the assets you can deposit with GET /v1/billing/assets.
  2. Ask for your address for one asset with POST /v1/billing/deposit.
  3. Send at least min_deposit to that address from any wallet.
  4. Poll GET /v1/billing/deposits (every 15 seconds or so) while the deposit confirms. When its status becomes credited, the USD value has been added to your balance as a deposit ledger entry, exactly once.

Each account has one address per asset family, for life. EVM networks share one address, so the address for USDC on Base is the same as for ETH on Ethereum. A deposit below the asset's minimum is never credited.

The deposit endpoints return 503 unavailable if payments are temporarily not available.


GET/v1/billing/assets

List deposit assets

What can be deposited. No authentication. Build your asset picker from this list rather than hard-coding it.

Response

An array of assets.

  • Name
    key
    Type
    string
    Description

    Asset key used in POST /v1/billing/deposit, <SYMBOL>@<network>.

  • Name
    symbol
    Type
    string
    Description

    Ticker.

  • Name
    network
    Type
    string
    Description

    Network id.

  • Name
    network_display
    Type
    string
    Description

    Network name for display.

  • Name
    decimals
    Type
    integer
    Description

    Decimal places of the smallest unit.

  • Name
    min_deposit
    Type
    string
    Description

    Smallest creditable deposit, in smallest units.

  • Name
    confirmations
    Type
    integer
    Description

    Confirmations before a deposit is credited.

  • Name
    est_confirmation_secs
    Type
    integer
    Description

    Rough time to reach those confirmations.

Request

GET
/v1/billing/assets
curl https://mail.nullnet.sh/v1/billing/assets

Response

[
  {
    "key": "BTC@bitcoin",
    "symbol": "BTC",
    "network": "bitcoin",
    "network_display": "Bitcoin",
    "decimals": 8,
    "min_deposit": "10000",
    "confirmations": 2,
    "est_confirmation_secs": 1200
  },
  {
    "key": "USDC@base",
    "symbol": "USDC",
    "network": "base",
    "network_display": "Base",
    "decimals": 6,
    "min_deposit": "1000000",
    "confirmations": 20,
    "est_confirmation_secs": 40
  }
  // ... ETH@base, ETH@ethereum, USDC@ethereum
]

POST/v1/billing/deposit

Get a deposit address

Returns your deposit address for an asset. The same address comes back every time. Returns 400 bad_request for an unknown asset.

Required attributes

  • Name
    asset
    Type
    string
    Description

    An asset key from GET /v1/billing/assets, case-insensitive.

Response

  • Name
    asset
    Type
    string
    Description

    Asset key.

  • Name
    network
    Type
    string
    Description

    Network id.

  • Name
    address
    Type
    string
    Description

    Your deposit address.

  • Name
    uri
    Type
    string
    Description

    Payment URI for wallets and QR codes: BIP-21 for Bitcoin, EIP-681 for EVM assets.

  • Name
    min_deposit
    Type
    string
    Description

    Smallest creditable deposit, in smallest units.

  • Name
    decimals
    Type
    integer
    Description

    Decimal places of the smallest unit.

  • Name
    confirmations
    Type
    integer
    Description

    Confirmations before crediting.

Request

POST
/v1/billing/deposit
curl https://mail.nullnet.sh/v1/billing/deposit \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"asset": "USDC@base"}'

GET/v1/billing/deposits

List deposits

The account's deposits, newest first (up to 100), as an array.

Deposit fields

  • Name
    id
    Type
    string
    Description

    Deposit id. Matches ref_id of the ledger entry once credited.

  • Name
    asset
    Type
    string
    Description

    Asset key.

  • Name
    network
    Type
    string
    Description

    Network id.

  • Name
    amount
    Type
    string
    Description

    Amount received, in smallest units.

  • Name
    decimals
    Type
    integer
    Description

    Decimal places of amount.

  • Name
    status
    Type
    string
    Description

    See below.

  • Name
    confirmations
    Type
    integer
    Description

    Confirmations so far.

  • Name
    tx_hash
    Type
    string
    Description

    Transaction hash.

  • Name
    credited_usd
    Type
    string | null
    Description

    USD credited, once credited.

  • Name
    seen_at
    Type
    timestamp
    Description

    When the deposit was first seen.

  • Name
    credited_at
    Type
    timestamp | null
    Description

    When it was credited.

Statuses

  • Name
    seen
    Description

    Detected on chain.

  • Name
    confirming
    Description

    Waiting for confirmations.

  • Name
    credited
    Description

    Final. The USD value is in your balance.

  • Name
    reversed
    Description

    The transaction disappeared from the chain after being credited.

  • Name
    below_minimum
    Description

    Smaller than min_deposit. Never credited.

  • Name
    pending_review
    Description

    Held for manual review.

Request

GET
/v1/billing/deposits
curl https://mail.nullnet.sh/v1/billing/deposits \
  -H "Authorization: Bearer {token}"

Response

[
  {
    "id": "019a2f95-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
    "asset": "USDC@base",
    "network": "base",
    "amount": "17490000",
    "decimals": 6,
    "status": "credited",
    "confirmations": 20,
    "tx_hash": "0x5e2b…",
    "credited_usd": "17.490000000000000000",
    "seen_at": "2026-10-26T08:01:20Z",
    "credited_at": "2026-10-26T08:02:00Z"
  }
]