Provision a package
Goal, preview, confirm, apply and readback for a new package.
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_STARTmetro package create preflight --file=package.yaml --sandbox --out=plans/package.jsonmetro 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.jsonWhat 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-takeoverwith a futurestarting_aton the alias. - Offsets. String offsets such as
"0"become integers. - Subscriptions. Each needs
subscription_rate,collection_scheduleandproration.QUANTITY_ONLYneedsinitial_quantity;SEAT_BASEDneedsseat_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.jsonmetro 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.jsonCreating 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:d7d5e302bc0d944254b774d65cdc122feed8befba2092e5b48a98064f36462b6If 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_SANDBOXCustomer 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.jsonCheck the package again any time with metro package list --alias=growth --sandbox --json. Next: Create a contract.