# API Access

:::note{title="Beta"}

API Monetization is in beta and free to try. The APIs are stable but should be
evaluated in non-production environments first. To go to production, contact
[sales@zuplo.com](mailto:sales@zuplo.com). Production pricing has not yet been
announced.

:::

## Buckets

Each Zuplo project includes three isolated buckets that mirror your
[environment structure](../environments.mdx):

| Bucket           | Purpose                                                       |
| ---------------- | ------------------------------------------------------------- |
| **Working Copy** | Your development sandbox for building and testing             |
| **Preview**      | Staging environments for validating changes before production |
| **Production**   | Your live environment serving real customers                  |

Meters, features, plans, and subscriptions are all scoped to a specific bucket.
This isolation enables independent development workflows where you can:

- **Experiment freely** - Test new pricing models or usage tracking in
  development without affecting production data
- **Validate changes** - Promote your product catalog configuration through
  preview environments before going live
- **Maintain separation** - Keep development test data completely isolated from
  production customer usage

When you're satisfied with your configuration in one bucket, you can recreate
that same configuration in another bucket to promote changes through your
deployment pipeline.

## Authentication

All Monetization API requests require authentication using a Zuplo API key.

All examples in this documentation assume the following environment variables
are set in your terminal:

```bash
# Your Bucket ID (could be working-copy, preview, or production)
# (Found in Project Services > Bucket Details)
export BUCKET_ID=your-bucket-id
# Your Zuplo API Key (Found in Account Settings > Zuplo API Keys)
export ZAPI_KEY=zpka_YOUR_API_KEY
```

Include your API key in the `Authorization` header:

```shell
curl \
  https://dev.zuplo.com/v3/metering/$BUCKET_ID/meters \
  --header "Authorization: Bearer $ZAPI_KEY"
```

## Bucket monetization configuration

Each bucket has an optional `MonetizationConfiguration` that holds bucket-wide
behavior — multi-subscription support, plan display order, plan-level overrides,
and the default payment grace period. The configuration is read by the runtime
and the Developer Portal; it is not stored in OpenMeter.

### Read

```shell
curl \
  https://dev.zuplo.com/v3/metering/$BUCKET_ID/monetization-configuration \
  --header "Authorization: Bearer $ZAPI_KEY"
```

When no configuration row exists for the bucket, the endpoint returns a default
body with `multipleSubscriptionsEnabled: false`, an empty `planOrder`, empty
`planSettings`, and `maxPaymentOverdueDays: 3`.

### Upsert

```shell
curl \
  https://dev.zuplo.com/v3/metering/$BUCKET_ID/monetization-configuration \
  --request PUT \
  --header "Authorization: Bearer $ZAPI_KEY" \
  --header "Content-Type: application/json" \
  --data @- << EOF
{
  "multipleSubscriptionsEnabled": false,
  "planOrder": ["free", "starter", "pro", "enterprise"],
  "planSettings": {
    "pro": { "visiblePhases": ["default"] }
  },
  "maxPaymentOverdueDays": 7
}
EOF
```

| Field                          | Type       | Description                                                                                                                                                                                         |
| ------------------------------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `multipleSubscriptionsEnabled` | `boolean`  | Stored on the bucket. Reserved for future multi-subscription rules; today the Developer Portal create-subscription path enforces a single active subscription per customer regardless of this flag. |
| `planOrder`                    | `string[]` | Ordered list of plan keys; drives pricing-page sort and upgrade/downgrade direction during plan changes                                                                                             |
| `planSettings`                 | `object`   | Per-plan overrides keyed by plan key. The supported sub-key today is `visiblePhases` — an array of phase keys that should appear on the pricing page                                                |
| `maxPaymentOverdueDays`        | `integer`  | Bucket-default payment grace period. Must be ≥ 0. Defaults to `3` when not set                                                                                                                      |

The request body must include at least one of these fields. All four fields are
optional in the request — the upsert preserves any field you don't send.

`planOrder` is consumed when a customer changes plans through the Developer
Portal: a target plan whose index is greater than or equal to the current plan's
index is treated as an upgrade (immediate timing); a lower index is treated as a
downgrade (next-billing-cycle timing). Plans not listed in `planOrder` default
to upgrade timing.

`maxPaymentOverdueDays` is the lowest-precedence default for the payment grace
period. See
[Subscription and payment validation](./monetization-policy.md#subscription-and-payment-validation)
for the full precedence chain (customer metadata → plan metadata → bucket
configuration → built-in default).

### Delete

```shell
curl \
  https://dev.zuplo.com/v3/metering/$BUCKET_ID/monetization-configuration \
  --request DELETE \
  --header "Authorization: Bearer $ZAPI_KEY"
```

After deletion, GET returns the default body again.

## Stripe setup and billing readiness

Most users connect Stripe through the
[Zuplo Portal](./stripe-integration.md#connecting-your-stripe-account). For
automated provisioning — CI scripts, infrastructure-as-code, or self-hosted
control planes — the same flow is available via these API endpoints.

### Install the Stripe app

Connect a Stripe account to a bucket and create the default billing profile in
one call:

```shell
curl \
  https://dev.zuplo.com/v3/metering/$BUCKET_ID/setup/stripe \
  --request POST \
  --header "Authorization: Bearer $ZAPI_KEY" \
  --header "Content-Type: application/json" \
  --data @- << EOF
{
  "apiKey": "rk_test_...",
  "name": "Stripe Billing Profile",
  "taxEnabled": false,
  "taxEnforced": false,
  "country": "US"
}
EOF
```

The endpoint validates the Stripe key prefix against the bucket's environment:

- Working-copy and preview buckets accept `sk_test_*` or `rk_test_*`
- Production buckets accept `sk_live_*` or `rk_live_*`

The response returns the installed `appId`. The endpoint fails with a
`409 Conflict` if a Stripe app is already installed for the bucket.

### Read the current Stripe setup

```shell
curl \
  https://dev.zuplo.com/v3/metering/$BUCKET_ID/setup/stripe \
  --header "Authorization: Bearer $ZAPI_KEY"
```

Returns the connected Stripe app summary and the billing profiles linked to it.

### Create an additional billing profile

To attach more billing profiles to the same Stripe app:

```shell
curl \
  https://dev.zuplo.com/v3/metering/$BUCKET_ID/setup/stripe/$STRIPE_APP_ID/billing-profile \
  --request POST \
  --header "Authorization: Bearer $ZAPI_KEY" \
  --header "Content-Type: application/json" \
  --data @- << EOF
{
  "name": "EU Billing Profile",
  "taxEnabled": true,
  "taxEnforced": false,
  "country": "DE"
}
EOF
```

### Check billing readiness

A lightweight check for tooling that gates deploys on Stripe being connected:

```shell
curl \
  https://dev.zuplo.com/v3/metering/$BUCKET_ID/billing-readiness \
  --header "Authorization: Bearer $ZAPI_KEY"
```

Response:

```json
{
  "hasStripeApp": true,
  "stripeAppId": "app_01H...",
  "hasDefaultBillingProfile": true,
  "defaultBillingProfileId": "bp_01H..."
}
```

Use this in setup wizards to gate the UI on whether Stripe is connected.

### Update a connected app

Rotate the Stripe key on an existing app, or update its name and metadata:

```shell
curl \
  https://dev.zuplo.com/v3/metering/$BUCKET_ID/apps/$APP_ID \
  --request PUT \
  --header "Authorization: Bearer $ZAPI_KEY" \
  --header "Content-Type: application/json" \
  --data @- << EOF
{
  "type": "stripe",
  "name": "Stripe Billing Profile",
  "secretAPIKey": "rk_test_..."
}
EOF
```

The same key-prefix validation applies — a live key is rejected on a
non-production bucket and vice versa.

## API Reference

For complete API operations, see the API Reference documentation:

- [Meters API](../../api/metering-meters)
- [Features API](../../api/metering-features)
- [Plans API](../../api/metering-plans)
