Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Enterprise Contract Onboarding

A complete walkthrough of an enterprise deal: ramp pricing, true-up billing, and contract amendment when the deal expands. All examples use curl.

Scenario: Acme Corp signs a $325k annual contract with ramp pricing:

  • Q1: $50k (land and expand)
  • Q2: $75k (ramping up)
  • Q3-Q4: $100k/quarter (full rate)

Step 1: Create the Customer and Contract

# Create the enterprise customer with legal name for invoice header
CUSTOMER=$(curl -s -X POST https://api.bill.sh/admin/v1/customers \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Acme Corp",
    "legal_name": "Acme Corporation, Inc.",
    "email": "billing@acmecorp.com",
    "currency": "USD",
    "account_type": "Organization",
    "payment_terms_days": 30,
    "tax_id": "US-EIN-12-3456789"
  }')

CUSTOMER_ID=$(echo $CUSTOMER | jq -r '.id')
echo "Customer: $CUSTOMER_ID"
# Create the contract with ramp terms
CONTRACT=$(curl -s -X POST https://api.bill.sh/admin/v1/contracts \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"customer_id\": \"$CUSTOMER_ID\",
    \"name\": \"Acme Corp — Enterprise Platform 2026\",
    \"start_date\": \"2026-01-01\",
    \"end_date\": \"2026-12-31\",
    \"terms\": [
      {
        \"phase\": 1,
        \"start_date\": \"2026-01-01\",
        \"end_date\": \"2026-03-31\",
        \"committed_amount_nanos\": \"50000000000000000\",
        \"description\": \"Q1 — Ramp: \$50k\"
      },
      {
        \"phase\": 2,
        \"start_date\": \"2026-04-01\",
        \"end_date\": \"2026-06-30\",
        \"committed_amount_nanos\": \"75000000000000000\",
        \"description\": \"Q2 — Ramp: \$75k\"
      },
      {
        \"phase\": 3,
        \"start_date\": \"2026-07-01\",
        \"end_date\": \"2026-12-31\",
        \"committed_amount_nanos\": \"200000000000000000\",
        \"description\": \"Q3-Q4 — Full rate: \$100k/quarter\"
      }
    ]
  }")

CONTRACT_ID=$(echo $CONTRACT | jq -r '.id')
echo "Contract: $CONTRACT_ID (status: $(echo $CONTRACT | jq -r '.status'))"

Enterprise customers typically get a custom plan with rate overrides. Here we subscribe them to the Enterprise plan and link it to the contract:

# Create the subscription linked to the contract
SUB=$(curl -s -X POST https://api.bill.sh/v1/subscriptions \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: sub-acme-enterprise-2026" \
  -d "{
    \"customer_id\": \"$CUSTOMER_ID\",
    \"plan_id\": \"$ENTERPRISE_PLAN_ID\",
    \"contract_id\": \"$CONTRACT_ID\",
    \"currency\": \"USD\",
    \"start_date\": \"2026-01-01\"
  }")

SUB_ID=$(echo $SUB | jq -r '.id')
echo "Subscription: $SUB_ID ($(echo $SUB | jq -r '.status'))"

Step 3: Monthly Billing with Commit Draw-Down

Each month, trigger billing. The platform draws down from the quarterly commit before charging the payment method:

# End of January — trigger billing
curl -s -X POST "https://api.bill.sh/admin/v1/subscriptions/$SUB_ID/bill" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Idempotency-Key: "bill-acme-2026-01"

# List the resulting Draft invoice
INVOICES=$(curl -s "https://api.bill.sh/admin/v1/invoices?customer_id=$CUSTOMER_ID&status=Draft" \
  -H "Authorization: Bearer $ADMIN_TOKEN")
INV_ID=$(echo $INVOICES | jq -r '.[0].id')
echo "Draft invoice: $INV_ID"

# Finalize the invoice
INV=$(curl -s -X POST "https://api.bill.sh/admin/v1/invoices/$INV_ID/finalize" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Idempotency-Key: finalize-$INV_ID")
echo "Invoice: $(echo $INV | jq -r '.invoice_number') — \$$(echo $INV | jq -r '.total_nanos | tonumber / 1e12 | tostring')"

Step 4: True-Up at Quarter End

At Q1 end, if the customer used less than their $50k commit, a true-up charge covers the difference:

# Request a true-up calculation for Q1
TRUE_UP=$(curl -s -X POST \
  "https://api.bill.sh/admin/v1/contracts/$CONTRACT_ID/true-up" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "period_start": "2026-01-01",
    "period_end": "2026-03-31"
  }')

SHORTFALL=$(echo $TRUE_UP | jq -r '.shortfall_nanos')
if [ "$SHORTFALL" != "0" ] && [ "$SHORTFALL" != "null" ]; then
  SHORTFALL_USD=$(echo "scale=2; $SHORTFALL / 1000000000000" | bc)
  echo "True-up required: \$$SHORTFALL_USD"
  # The true-up creates an invoice line item automatically
  TRUE_UP_INV_ID=$(echo $TRUE_UP | jq -r '.invoice_id')
  echo "True-up invoice: $TRUE_UP_INV_ID"
else
  echo "No true-up required — customer met commit"
fi

Step 5: Contract Amendment When Deal Expands

In June, Acme adds a Data Analytics module, expanding the contract to $450k total. Amendment is immutable — it creates a new contract linked to the old one:

# Amend the contract
AMENDED=$(curl -s -X POST \
  "https://api.bill.sh/admin/v1/contracts/$CONTRACT_ID/amend" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Expansion — Data Analytics module added per SOW-2026-06",
    "effective_date": "2026-07-01",
    "additional_terms": [
      {
        "phase": 4,
        "start_date": "2026-07-01",
        "end_date": "2026-12-31",
        "committed_amount_nanos": "250000000000000000",
        "description": "Q3-Q4 expanded — \$125k/quarter (was \$100k)"
      }
    ]
  }')

NEW_CONTRACT_ID=$(echo $AMENDED | jq -r '.id')
PARENT_CONTRACT_ID=$(echo $AMENDED | jq -r '.parent_contract_id')
echo "New contract: $NEW_CONTRACT_ID"
echo "Amendment chain: $PARENT_CONTRACT_ID → $NEW_CONTRACT_ID"

Key Takeaways

  • Contracts track commercial terms; subscriptions drive billing — they’re separate concerns
  • Ramp pricing is modeled as multiple ContractTerm phases with different committed_amount_nanos
  • True-up runs at phase boundaries to collect any shortfall
  • Amendment is immutable: the parent_contract_id field traces the full history
  • Rate overrides (RateOverride) can apply custom pricing at the SKU level without touching the catalog

See Contracts for the full API reference.