---
title: "Custom Enterprise API Pricing with Private Plans"
description: "Every enterprise deal that needs custom pricing is a hole in your self-serve funnel. Zuplo's private plans close it without a quoting system (CPQ), a coupon hack, or a separate invoicing pipeline."
canonicalUrl: "https://zuplo.com/blog/2026/04/16/custom-enterprise-api-pricing-without-cpq"
pageType: "blog"
date: "2026-04-16"
authors: "martyn"
tags: "API Monetization"
image: "https://zuplo.com/og?text=Custom%20Enterprise%20API%20Pricing%20with%20Private%20Plans"
---
A deal lands in your inbox at a price your pricing page doesn't have. A design
partner wants to try your API for a quarter before committing. Your team is
running a closed beta for exactly seven companies.

Your pricing page has three plans. This deal needs a fourth, visible only to the
co-founder who signed it. The temptation is to wire something together: Stripe
coupons, an off-platform invoice, a hidden plan with an obscure URL. Every one
of those paths costs you metering visibility, audit trail, or the ability to
scale.

Private plans in Zuplo are built for exactly this shape of problem: a plan that
exists, bills, and meters like any other, but is invisible to everyone except
the emails you invite.

<CalloutAudience
  variant="useIf"
  items={[
    "Fielding one-off enterprise deals that don't fit your public pricing",
    "Running a design partner program with bespoke terms per partner",
    "Gating a closed beta to an invite list of a few specific companies",
    "Taking board-brokered introductions that arrive with a pre-negotiated price",
  ]}
/>

## Three scenarios that break your pricing page

Custom pricing requests arrive in three flavors, all landing at the same
engineering problem.

- **Enterprise deals.** A signed contract at a negotiated rate, usually
  per-request pricing with a flat commit. Nothing on your public pricing table
  reflects it.
- **Design partners.** Early customers at a discount or free for a fixed window.
  You need them on the same metering infrastructure as paying customers so you
  get real usage data, but their billing sheet is different.
- **Closed beta access.** A new API surface you're not ready to charge for,
  available only to a handpicked list. Same gateway, same meters, same developer
  portal, different visibility.

From the engineer's seat, all three are identical: a plan that exists, bills
correctly, and meters usage, but is invisible to everyone except a short list of
emails. Teams that get this right have a portfolio of plans behind the scenes
and a clean public pricing page. A few are written up in
[5 API Monetization Success Stories](/blog/2026/03/18/5-api-monetization-success-stories).

## Where the usual workarounds fall apart

These options seem reasonable on the first deal and fall apart by the third.

- **Stripe coupons on a public plan.** Gets you a discount, not a custom
  entitlement. Your enterprise customer still sees the public plan's limits, and
  you have no clean way to offer a different feature set without cluttering the
  public tier.
- **Off-platform manual invoicing.** You skip Stripe and track usage in a
  spreadsheet. You lose every piece of metering visibility the gateway gives
  you: no usage dashboards, no overage alerts, no audit trail when the customer
  disputes a bill.
- **Forked public plans.** Clone Pro, rename it "Pro Enterprise", tweak the
  price. By the fifth deal your pricing table is a mess and your developer
  portal shows four "Pro" variants to every signed-in user.

Each scales to approximately zero. **Zuplo's answer is the private plan.**

## Private plans in Zuplo

A private plan lives in your monetization configuration, bills through Stripe,
and meters usage through the same gateway pipeline. The one difference: it's
invisible to anyone except users you explicitly invite by email.

You can set these up two ways. In the **Zuplo portal UI**, which is the fastest
path if invites are the whole story and you just want the plan shipped. Or
through the **monetization API**, which is the right choice when you're building
Zuplo more deeply into your own systems (an internal admin tool, CI/CD,
infrastructure-as-code). Same primitive, same capability, different surface.

<CalloutDoc
  title="New to Zuplo Monetization?"
  description="Private plans sit on top of meters, features, and at least one public plan. If you haven't set those up yet, walk through the monetization quickstart first, then come back here."
  href="https://zuplo.com/docs/articles/monetization/quickstart"
  icon="lightning"
/>

### In the Zuplo portal

In your Zuplo project, head to **Services → Monetization → Pricing** and click
**Add Plan**. Fill in the plan name and key, then flip the **Private Plan**
toggle on.

![The Create Plan modal in the Zuplo portal with the Private Plan toggle switched on and set to "Hide this plan from the public pricing page"](/media/posts/2026-04-16-custom-enterprise-api-pricing-without-cpq/create-plan.png)

Click **Create Draft**. From here, configure the plan exactly like you would a
public one: pick the pricing (flat fee, usage-based tiers, or a combination),
attach the features and entitlements for included units, and wire up the meter
that feeds usage into the plan. Then publish.

Once published, the plan lands in the Plans list with a **Private** badge next
to its name and an **Invites** action next to the status.

![The Plans list in the Zuplo portal showing a public Developer plan and a private Enterprise plan, with the Private badge next to the Enterprise name and an Invites link on the row](/media/posts/2026-04-16-custom-enterprise-api-pricing-without-cpq/plan-created.png)

Click **Invites** to add customer emails one at a time. Each invite sends the
customer a subscribe URL. Only invited users can use it.

### With the monetization API

If you'd rather drive plan creation from code (to template it against signed
contract terms, or to sit alongside the rest of your provisioning pipeline), the
same flow is three API calls.

Before you start, grab two values from your Zuplo project: the `ZUPLO_BUCKET_ID`
(your monetization bucket, available in the project's Monetization settings) and
a `ZUPLO_API_KEY` (created in project settings, treat it like any other
server-side secret).

**1. Create the plan with the private-plan flag.** A plan becomes private when
you set `"zuplo_private_plan": "true"` in its `metadata` field. Everything else
works identically to a public plan: rate cards, features and entitlements,
prorating, and Stripe Checkout. `billingCadence` uses ISO 8601 durations, so
`P1M` means monthly.

Below is a realistic enterprise deal: a $10,000 monthly commit including 1
million requests, with $0.002 per request overage. `featureKey` values like
`monthly_fee` and `api` reference features already defined in your monetization
bucket, so adjust to match your keys.

```bash
curl -X POST "https://dev.zuplo.com/v3/metering/${ZUPLO_BUCKET_ID}/plans" \
  -H "Authorization: Bearer ${ZUPLO_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "enterprise_acme",
    "name": "Acme Enterprise",
    "description": "Custom tier for Acme (negotiated 2026-04-16)",
    "billingCadence": "P1M",
    "currency": "USD",
    "metadata": {
      "zuplo_private_plan": "true"
    },
    "phases": [
      {
        "key": "default",
        "name": "Default",
        "duration": null,
        "rateCards": [
          {
            "key": "monthly_commit",
            "name": "Monthly Commit",
            "featureKey": "monthly_fee",
            "type": "flat_fee",
            "billingCadence": "P1M",
            "price": {
              "type": "flat",
              "amount": "10000.00",
              "paymentTerm": "in_advance"
            }
          },
          {
            "key": "api_requests",
            "name": "API Requests",
            "featureKey": "api",
            "type": "usage_based",
            "billingCadence": "P1M",
            "entitlementTemplate": {
              "type": "metered",
              "usagePeriod": "P1M",
              "issueAfterReset": 1000000,
              "isSoftLimit": true,
              "preserveOverageAtReset": false
            },
            "price": {
              "type": "tiered",
              "mode": "graduated",
              "tiers": [
                {
                  "upToAmount": "1000000",
                  "flatPrice": { "type": "flat", "amount": "0" },
                  "unitPrice": null
                },
                {
                  "flatPrice": null,
                  "unitPrice": { "type": "unit", "amount": "0.002" }
                }
              ]
            }
          }
        ]
      }
    ]
  }'
```

Save the `id` from the response. You need it for the next two calls.

**2. Publish the draft.** Plans are created as drafts. A draft plan can't be
subscribed to, so until you publish it, even an invited user sees nothing on the
pricing page.

```bash
curl -X POST "https://dev.zuplo.com/v3/metering/${ZUPLO_BUCKET_ID}/plans/${PLAN_ID}/publish" \
  -H "Authorization: Bearer ${ZUPLO_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{}'
```

Once published, a plan can't be modified. If terms change later, create a new
plan, publish it, and invite the customer. In practice the customer schedules
cancelation of the old subscription at period end and subscribes to the new one.
Gateway traffic doesn't interrupt; the cutover lands on the billing boundary.

**3. Invite the user by email.** The user doesn't need to exist in your Zuplo
tenant yet. The invite is by email and activates the first time they sign in
with that address.

```bash
curl -X POST "https://dev.zuplo.com/v3/metering/${ZUPLO_BUCKET_ID}/plan-invites" \
  -H "Authorization: Bearer ${ZUPLO_API_KEY}" \
  -H "Content-Type: application/json" \
  -d "{
    \"email\": \"cto@acme.com\",
    \"planId\": \"${PLAN_ID}\"
  }"
```

That's the whole flow. No quoting system (CPQ) to build, no coupon juggling, no
off-platform invoice.

<CalloutTip variant="mistake">
  Creating the invite before publishing the plan. The invite is valid, but the
  user sees nothing until the plan moves out of draft state. Publish first,
  invite second.
</CalloutTip>

## What each side actually sees

Uninvited users see your Free, Pro, and other published tiers. The Acme
Enterprise plan doesn't appear on the public pricing table, doesn't surface in
the API responses that back it, and isn't visible to anyone who signs in without
a matching invite.

Invited users: when `cto@acme.com` signs in to the developer portal, the Acme
Enterprise tier appears alongside the public plans. They subscribe through the
same Stripe Checkout flow, get an API key provisioned automatically, and usage
flows through the same meters. If you've already built a developer portal for
your public plans, nothing changes to support private ones. The full portal
setup is covered in
[Building a Monetized API, Part 4](/blog/2026/04/03/building-a-monetized-api-part-4).

Invites are managed through the same metering API, so fixing a typo or revoking
access is another API call, not a support ticket.

A single email can be invited to multiple private plans (useful when an
enterprise customer also gets beta access). Multiple emails can be invited to
one plan, which is how you handle a customer with a team.

## Stacking private and public plans

Private plans sit alongside public plans. A customer can hold multiple active
subscriptions, and that matters for real enterprise deals.

A common pattern is an enterprise customer on a negotiated base plan (private,
fixed commit plus overage) plus an add-on credit pack from your public pricing
page (pay-as-you-go top-up). Two subscriptions, two sets of entitlements, one
customer.

Each subscription gets its own API key by default, so traffic routes to the
right bucket without the customer thinking about it. Usage is tracked
independently, and invoices reflect the two separately.

For your side, this is a lever. You don't need to invent a "private plan with
bundled credit pack" frankenstein when a customer upgrades. Sell them the
existing public credit pack as a second subscription and keep the private base
plan clean. The full subscription state machine is in the
[subscription lifecycle doc](https://zuplo.com/docs/articles/monetization/subscription-lifecycle).

## Private plans, end to end

The full flow, in order:

- Create the plan with the **Private Plan** toggle on (portal UI) or
  `metadata.zuplo_private_plan: "true"` (API), with your rate card.
- Publish the plan to move it out of draft.
- Add invites for each customer email.
- Confirm that signing in with an invited email shows the private tier.
- Record the plan key and plan ID somewhere retrievable.

Custom enterprise pricing is one of the first places a growing API business
starts needing shape it didn't need on day one. Private plans are the primitive
that covers it: same gateway, same meters, same Stripe billing path as your
public tiers, with one piece of metadata that controls who sees them. The three
scenarios this post opened with (enterprise deals, design partners, closed
betas) all collapse to the same recipe above, so the next one, and the one
after, don't need a new system.

<CalloutDoc
  title="Private Plans Reference"
  description="Full API reference for creating private plans, managing invites, and subscribing invited users in Zuplo Monetization."
  href="https://zuplo.com/docs/articles/monetization/private-plans"
  icon="book"
/>