# Provision a package

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

Source: https://metrocli.com/docs/guides/provision-a-package/ · Docs index: https://metrocli.com/llms.txt

## 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](/docs/guides/create-a-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](/docs/concepts/packages).

> **Before you start.** Run everything on sandbox first. Writes need `metro mode write`. You need a rate card ID; [Plans and rate cards](/docs/guides/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.

```yaml
# 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
```

```bash
metro package create preflight --file=package.yaml --sandbox --out=plans/package.json
```

```text
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](/docs/guides/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:

```bash
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
```

```text
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:

```bash
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

```bash
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:

```text
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](/docs/guides/create-a-contract).
