API ReferenceMerchant API

Create a new payment invoice.

Creates an invoice with a deposit address for the specified currency. Returns a checkout URL the customer can visit to pay.

Idempotency: when idempotency_key is supplied, a replay with the same key but a different request body returns 422 with code idempotency_key_mismatch. Replays with the same body are idempotent and return the original invoice.

POST
/v1/invoices
X-API-Key<token>

API key issued by the merchant dashboard. The key's environment (live/test) determines which data the caller can access. Permissions on the key control which endpoints are reachable.

In: header

currency?string

Cryptocurrency code (e.g. BTC, ETH, SOL). Required when deferred is false. Optional when deferred is true (payer chooses at activation time).

Lengthlength <= 20
network?string

Blockchain network (e.g. ethereum, polygon, solana, tron, bsc, arbitrum). Required for multi-chain tokens whose network cannot be unambiguously inferred from the currency ticker (USDT, USDC, ETH). Optional for unambiguous tokens (BTC, SOL, TRX, etc.) — the backend fills it in via currency.InferNetwork when omitted. Sending an unsupported (currency, network) pair returns a 400 validation_error.

Lengthlength <= 30
amount?string

Crypto-denominated amount the merchant wants to collect, as a decimal string in units of currency. Required unless amount_fiat (or the legacy amount_usd) is sent instead. Mutually exclusive with the fiat-amount fields — sending both is a validation error.

Lengthlength <= 50
amount_usd?string

DEPRECATED — legacy alias for amount_fiat with fiat_currency='USD'. Kept accepted during the Phase 4 backward-compat window (plan §C.2 rule 1 / §D). Migrate to amount_fiat + fiat_currency.

Aliasing precedence rules (§C.2, checked in this order):

  1. amount_usd supplied alone (no amount_fiat, and fiat_currency is empty or "USD") → normalized to amount_fiat + fiat_currency='USD'.
  2. BOTH amount_usd AND amount_fiat supplied (fiat_currency implicit or explicit "USD") → amount_fiat wins, amount_usd is ignored for pricing.
  3. amount_usd supplied together with a non-USD fiat_currency → request is rejected with 400 conflicting_amount_fields REGARDLESS of whether amount_fiat is also present. Rule 3 overrides Rule 2 — the conflict check runs before the "amount_fiat wins" resolution, because silently coercing a mixed amount_usd + non-USD fiat_currency payload would mis-bill the merchant by the USD/ cross rate (plan §C.2 rule 3 / financial-ask #2).
Lengthlength <= 50
amount_fiat?string

Fiat-denominated amount in the currency named by fiat_currency (default USD). The invoice is billed against the selected crypto at the activation-time exchange rate. Sent instead of amount to express "charge the customer $50 / €50 / £50 worth of BTC". When sent without currency the invoice behaves like the existing deferred "any crypto" flow. When sent alongside a specific currency + network the invoice is locked to that pair (no currency picker on checkout); this combination requires deferred=true. Mutually exclusive with amount.

When BOTH amount_fiat and the legacy amount_usd are supplied, amount_fiat takes precedence (§C.2 rule 2). However, the conflict guard in §C.2 rule 3 (amount_usd + non-USD fiat_currency → 400 conflicting_amount_fields) is evaluated first and overrides rule 2 — see the amount_usd description for the full precedence list.

Lengthlength <= 50
fiat_currency?string

ISO 4217 code that denominates amount_fiat. Defaults to USD when omitted. Must be one of the server-side fiat allowlist (plan §C.2 rule 7 / appsec-ask #7). Sending a code outside the allowlist returns a 400 invalid_fiat_currency.

Lengthlength <= 3
Value in"USD" | "EUR" | "GBP" | "JPY" | "AUD" | "CAD" | "CHF" | "NZD" | "SEK" | "NOK" | "DKK" | "SGD" | "HKD" | "INR" | "BRL"
deferred?boolean

If true, creates a draft invoice. The payment timer starts when the payer activates via the checkout page.

Defaultfalse
description?string

Human-readable invoice description.

Lengthlength <= 1000
external_id?string

Merchant-supplied external reference ID.

Lengthlength <= 255
idempotency_key?string

Idempotency key to prevent duplicate invoices.

Lengthlength <= 255
metadata?object

Key-value metadata attached to the invoice.

Propertiesproperties <= 50

Empty Object

redirect_url?string

URL to redirect the customer after payment.

Formaturi
Lengthlength <= 2048
ttl_minutes?integer

Invoice expiration time as a minute count (0 = default).

underpayment_threshold?string

Decimal string. Payments below this tolerance are rejected.

Lengthlength <= 50
customer_email?string

Optional customer email address. When present, ss-core dispatches an asynchronous INVOICE_PAYMENT_REQUESTED email to this address after the invoice row is committed (fire-and-forget goroutine; the response does not block on Resend). The value is also copied into payer_email so the manual /send endpoint can reuse it without re-entry.

Dispatch is idempotent against a retried CreateInvoice with the same idempotency_key: a subsequent hit that resolves to the existing invoice does not re-send. Phase 5 extends the same auto-send pipeline to INVOICE_PAID and INVOICE_EXPIRED status transitions, also guarded by per-invoice idempotency timestamps.

Future sends are suppressed if Resend reports a hard bounce for this address via the bounce webhook — once the row's email_bounced flag is set, no further emails are issued regardless of subsequent status transitions.

Formatemail
Lengthlength <= 320

Response Body

const body = JSON.stringify({})fetch("https://api.halfin.xyz/api/v1/invoices", {  method: "POST",  headers: {    "Content-Type": "application/json"  },  body})
{
  "data": {
    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
    "merchant_id": "500924a8-3f5e-4c00-beb8-2efcde988aea",
    "currency": "string",
    "network": "string",
    "amount_requested": "string",
    "amount_paid": "string",
    "amount_usd": "string",
    "amount_fiat": "string",
    "amount_in_base_currency": "string",
    "fiat_currency": "string",
    "status": "draft",
    "environment": "live",
    "deposit_address": "string",
    "deposit_address_tag": "string",
    "description": "string",
    "external_id": "string",
    "idempotency_key": "string",
    "metadata": {},
    "redirect_url": "string",
    "source": "string",
    "static_address_id": "dfe0e820-d96d-4e58-a685-774321eca4db",
    "checkout_url": "string",
    "payments": [
      {
        "tx_hash": "string",
        "amount": "string",
        "confirmations": 0,
        "required_confirmations": 0,
        "status": "string",
        "detected_at": "2019-08-24T14:15:22Z",
        "settlement_conversion": {
          "target_currency": "string",
          "target_network": "string",
          "source_amount": "string",
          "target_gross_amount": "string",
          "fee_amount": "string",
          "net_amount": "string",
          "quote_status": "not_required",
          "execution_status": "not_required",
          "failure_class": "string",
          "quoted_at": "2019-08-24T14:15:22Z",
          "locked_until": "2019-08-24T14:15:22Z"
        }
      }
    ],
    "expires_at": "2019-08-24T14:15:22Z",
    "paid_at": "2019-08-24T14:15:22Z",
    "created_at": "2019-08-24T14:15:22Z",
    "is_deferred": true,
    "draft_expires_at": "2019-08-24T14:15:22Z",
    "activated_at": "2019-08-24T14:15:22Z",
    "activation_count": 0,
    "is_locked_fiat": true,
    "target_settlement_asset": "passthrough",
    "settlement_target_currency": "USDC",
    "settlement_target_network": "ethereum",
    "payer_email": "string",
    "sent_at": "2019-08-24T14:15:22Z"
  },
  "meta": {
    "request_id": "string"
  }
}
{
  "error": {
    "code": "string",
    "message": "string",
    "details": [
      "string"
    ]
  },
  "meta": {
    "request_id": "string"
  }
}
{
  "error": {
    "code": "string",
    "message": "string",
    "details": [
      "string"
    ]
  },
  "meta": {
    "request_id": "string"
  }
}
{
  "error": {
    "code": "string",
    "message": "string",
    "details": [
      "string"
    ]
  },
  "meta": {
    "request_id": "string"
  }
}
{
  "error": {
    "code": "string",
    "message": "string",
    "details": [
      "string"
    ]
  },
  "meta": {
    "request_id": "string"
  }
}
{
  "error": {
    "code": "string",
    "message": "string",
    "details": [
      "string"
    ]
  },
  "meta": {
    "request_id": "string"
  }
}
{
  "error": {
    "code": "string",
    "message": "string",
    "details": [
      "string"
    ]
  },
  "meta": {
    "request_id": "string"
  }
}