Provision a package

Goal, preview, confirm, apply and readback for a new package.

View .md

Goal

Publish a new package, growth, on an existing rate card, then create the customer who will use it. Next you'll create their contract.

A package is a contract template: rate card, billing settings, and optionally commits, credits and subscriptions. It can't be edited after create, so the preview is the moment to get it right. See Packages.

Before you start

Run everything on sandbox first. Writes need metro mode write. You need a rate card ID; Plans and rate cards creates one, and metro catalog bind --sandbox --write finds the ones you have.

Preview

Write the spec. It is the POST /v1/packages/create body, in YAML or JSON.

# package.yaml
name: Acme Growth
rate_card_id: 00000000-0000-4000-8000-000000000103
aliases:
  - name: growth
billing_provider: stripe
delivery_method: direct_to_billing_provider
usage_statement_schedule:
  frequency: MONTHLY
  day: CONTRACT_START
metro package create preflight --file=package.yaml --sandbox --out=plans/package.json
metro package create preflight (sandbox, key sha256:80f0714120b8) - read-only, nothing written to Metronome.
  package: null -> {"name":"Acme Growth",...,"uniqueness_key":"metro-package-ec3d42f870c9802d24271817"}
Steps: POST /v1/packages/create
Payload hash: sha256:d7d5e302bc0d944254b774d65cdc122feed8befba2092e5b48a98064f36462b6
Wrote plan: ./plans/package.json

What preflight checks and fixes:

  • uniqueness_key. If the spec has none, metro derives one, at most 128 characters, and checks it against every package, archived ones too.
  • Aliases. Refuses an alias another package already holds live. To publish a new version behind an existing alias, use Clone a package version, or pass --allow-alias-takeover with a future starting_at on the alias.
  • Offsets. String offsets such as "0" become integers.
  • Subscriptions. Each needs subscription_rate, collection_schedule and proration. QUANTITY_ONLY needs initial_quantity; SEAT_BASED needs seat_config.

Then the customer, with an ingest alias, an external ID and a Stripe customer:

metro customer create preflight --name="Acme Corp" --ingest-alias=acme-corp \
  --external-id=acme-001 \
  --stripe-customer-id=cus_ACME123 --stripe-collection-method=charge_automatically \
  --sandbox --out=plans/customer.json
metro customer create preflight (sandbox, key sha256:80f0714120b8) - read-only, nothing written to Metronome.
  customer: null -> {"name":"Acme Corp","ingest_aliases":["acme-corp"],"custom_fields":{"external_id":"acme-001"},"customer_billing_provider_configurations":[{"billing_provider":"stripe","delivery_method":"direct_to_billing_provider","configuration":{"stripe_customer_id":"cus_ACME123","stripe_collection_method":"charge_automatically"}}]}
Steps: POST /v1/customers
Payload hash: sha256:4c30b21c141f845e307c9eb8c89070b92e06342406d2819a678c649621bf55f8
Wrote plan: ./plans/customer.json

Creating a customer takes no uniqueness key, so the ingest aliases are the anchor. Preflight refuses an alias held by any customer, active or archived, and a custom-field key the tenant doesn't have.

Confirm

Read the diff lines and the hash. An agent shows you both and waits for your yes. Then dry-run, which prints the exact requests and writes nothing:

metro package create apply --file plans/package.json --sandbox \
  --confirm-hash sha256:d7d5e302bc0d944254b774d65cdc122feed8befba2092e5b48a98064f36462b6

If anyone edits the plan file, apply refuses with MTR-PLAN-002. Change the spec and preflight again; a changed field means a new hash.

Apply

metro package create apply --file plans/package.json --sandbox \
  --confirm-hash sha256:d7d5e302bc0d944254b774d65cdc122feed8befba2092e5b48a98064f36462b6 \
  --apply --confirm-live-apply=I_UNDERSTAND_THIS_WILL_WRITE_LIVE_METRONOME_SANDBOX

metro customer create apply --file plans/customer.json --sandbox \
  --confirm-hash sha256:4c30b21c141f845e307c9eb8c89070b92e06342406d2819a678c649621bf55f8 \
  --apply --confirm-live-apply=I_UNDERSTAND_THIS_WILL_WRITE_LIVE_METRONOME_SANDBOX

Customer apply checks the aliases again just before writing, so running the same plan twice fails instead of creating a second customer. On production, swap --sandbox and the token for --prod --confirm-prod-apply=I_UNDERSTAND_BILLING_SIDE_EFFECTS, after a typed yes for this change.

Readback

metro reads both objects back. Every line must say ok; any FAIL exits 3. The package readback compares the created package with the spec field by field (package.name, package.rate_card_id and the rest) and checks package.aliases. The customer readback:

Applied (sandbox).
Read-back (expected vs actual):
  ok   customer.id: expected "present" actual "present"
  ok   customer.name: expected "Acme Corp" actual "Acme Corp"
  ok   customer.ingest_aliases: expected ["acme-corp"] actual ["acme-corp"]
  ok   customer.custom_fields: expected {"external_id":"acme-001"} actual {"external_id":"acme-001"}
  ok   billing provider stripe: expected {"stripe_customer_id":"cus_ACME123","stripe_collection_method":"charge_automatically"} actual {...same...}
Log: ~/.metro/logs/customer-create/apply-2026-11-03T19-21-34-560Z.json

Check the package again any time with metro package list --alias=growth --sandbox --json. Next: Create a contract.

On this page