Zuplo
API Monetization

Enable Stripe Tax for Your Monetized API

Martyn DaviesMartyn Davies
April 29, 2026
7 min read

Cross-border VAT and sales tax ship audits, not features. Stripe Tax inside Zuplo's billing profile gives you the right rates by customer location and invoice tax lines from one config change, not a separate engine.

Your first paying customer in the UK signs up on a Tuesday. That afternoon, you owe HMRC. Not next quarter, not “once you cross a threshold”, that afternoon, because the UK has charged VAT on digital services from the first pound since 2015. Germany works the same way for any EU customer. Most US states differ in shape but not in spirit: cross a state’s economic nexus threshold (sounds more sci-fi than it is, really just a sales-volume or transaction-count line that triggers a tax obligation) and they expect collection retroactive to the day you crossed.

Tax compliance doesn’t ship features, doesn’t move retention, and does ship audits. Most API teams ignore it until an accountant panics, or they bolt on a separate tax engine and a webhook to keep prices in sync.

Zuplo’s monetization stack uses Stripe Tax directly from the billing profile, so the same subscription that bills the customer also calculates, collects, and lines up the VAT or sales tax for remittance. No second engine, no sync job, no separate invoicing pipeline.

Use this approach if you're:
  • You're charging for API access through Zuplo's monetization and Stripe
  • Your first non-domestic customer is about to sign up, or has already
  • Your finance team has asked where the VAT line on the invoice is going to come from

Why API tax is the work nobody wants

Tax on a digital subscription is a multi-axis problem.

Where you have to register. UK and EU charge VAT from the first sale to a local consumer. The EU’s One-Stop-Shop (OSS) lets you remit for all 27 from one return, but you still have to register somewhere. US economic nexus thresholds vary by state (commonly $100k in sales or 200 transactions a year), and crossing one means collecting from day one, often retroactively.

Who you’re charging. A B2C sale to a French consumer takes French VAT. A B2B sale to a French business with a valid VAT number takes the reverse-charge mechanism instead: zero VAT charged, the buyer accounts for it on their own return, and your invoice has to state that the reverse charge applies.

How prices are quoted. UK and EU custom is tax-inclusive ($9.99 includes VAT). US custom is tax-exclusive ($9.99 plus tax). The same plan needs both behaviours depending on where the customer logs in from.

What category your product is. Stripe Tax categorises every product, and the wrong code calculates the wrong rate. Stripe publishes the full list at docs.stripe.com/tax/tax-codes.

The bolt-on path is a separate tax engine (Avalara, TaxJar, Stripe Tax called from your own service) plus a sync job to keep prices, registrations, and invoice line items aligned. Three systems with three deploy pipelines for a feature that returns no signups.

What Stripe Tax does

Stripe Tax sits inside Stripe and handles the parts that don’t change per customer:

  • Tracks your nexus and registrations across countries and US states.
  • Resolves the customer’s tax location from address, IP, and payment method.
  • Calculates the correct rate at invoice time using the product’s tax code.
  • Returns line items the invoice can render in the customer’s expected format (inclusive or exclusive).
  • Produces the reports you need to file and remit.

It doesn’t decide whether you should be registered somewhere. That’s your accountant’s call. Once the registration exists, calculation, collection, and reporting flow through every invoice without you wiring it up per plan.

Enable Stripe Tax in your billing profile

Zuplo exposes monetization config through its metering API. Your billing profile is the object on that API that holds plan billing, invoicing, and tax settings. Tax collection is a flag on that profile, plus your supplier country. One PUT once your Stripe registrations are in place, and the next invoice carries the right tax line.

Prerequisites

This post assumes you already have a monetized API running in Zuplo. If you don’t, the monetization quickstart covers that setup; come back here once your billing profile exists.

Add tax registrations in Stripe for every country where you’ve registered to collect. Stripe Tax can’t calculate for a jurisdiction it doesn’t know you’re registered in. Registrations live at dashboard.stripe.com/tax/registrations, keyed by your local registration number.

You’ll also need two values from your Zuplo project to call the metering API:

  • ZUPLO_BUCKET_ID, your monetization bucket. Find it under Services → Monetization Service → Bucket Details in the Zuplo portal. It looks like bckt_g1YMyT8DM90S0iZNmVMJiFohe0sWGhxF.
  • ZUPLO_API_KEY, created in project settings. Treat it like any other server-side secret.

Zuplo project Monetization Service panel with the Bucket Details popover open, showing the Bucket ID prefixed with bckt_

Fetch your billing profile

The metering API expects the entire billing profile back on PUT, not a partial diff. Two GETs: one to find the ID, one to write the full body to a file you’ll edit.

Terminalbash
curl -X GET "https://dev.zuplo.com/v3/metering/${ZUPLO_BUCKET_ID}/billing/profiles" \
  -H "Authorization: Bearer ${ZUPLO_API_KEY}" \
  -H "Content-Type: application/json"

The response has an items array. Save the id as BILLING_PROFILE_ID, then:

Terminalbash
curl -X GET "https://dev.zuplo.com/v3/metering/${ZUPLO_BUCKET_ID}/billing/profiles/${BILLING_PROFILE_ID}" \
  -H "Authorization: Bearer ${ZUPLO_API_KEY}" \
  -H "Content-Type: application/json" \
  > billing-profile.json

Edit the tax fields

Open billing-profile.json and update three parts. The supplier country is the one you’re collecting tax from (your company’s country, ISO 3166-1 alpha-2). workflow.tax is the on-switch. invoicing.defaultTaxConfig sets the tax behaviour and product tax code. Leave every other field from the GET response exactly as-is:

JSONjson
{
  "supplier": {
    "name": "Stripe Account",
    "addresses": [
      {
        "country": "GB"
      }
    ]
  },
  "workflow": {
    "tax": {
      "enabled": true,
      "enforced": false
    }
  },
  "invoicing": {
    "autoAdvance": true,
    "draftPeriod": "P0D",
    "dueAfter": "P0D",
    "progressiveBilling": true,
    "defaultTaxConfig": {
      "behavior": "exclusive",
      "stripe": {
        "code": "txcd_10000000"
      }
    }
  }
}

txcd_10000000 is Stripe’s “General - Electronically Supplied Services” code, the broadest sensible default for a metered API. enforced: false puts you in best-effort mode, which is the right starting choice (more on that below). behavior: "exclusive" adds tax on top of the listed price ($9.99 plan + 20% VAT charges $11.99); switch to "inclusive" if your pricing page already shows tax-included prices, which is the European default.

Pro tip:

Zuplo’s tax-collection doc uses txcd_10000000 in its example and shorthand-labels it “SaaS”, but Stripe’s own catalog reserves the SaaS label for txcd_10103000 (personal use) and txcd_10103001 (business use). Swap in txcd_10103001 if your product is closer to a flat-rate business SaaS subscription than a pay-per-call API.

Save the changes

Send the entire billing-profile.json back to the same URL:

Terminalbash
curl -X PUT "https://dev.zuplo.com/v3/metering/${ZUPLO_BUCKET_ID}/billing/profiles/${BILLING_PROFILE_ID}" \
  -H "Authorization: Bearer ${ZUPLO_API_KEY}" \
  -H "Content-Type: application/json" \
  -d @billing-profile.json

The next invoice for a customer in a country you’re registered in will carry the right tax line.

Best-effort first, strict later

The enforced flag controls what Stripe does when tax calculation fails. Calculation can fail for a handful of predictable reasons: a customer in a country where you don’t yet have a Stripe Tax registration, a missing or invalid postal code, or an address Stripe Tax can’t map to a known jurisdiction.

The flag decides what happens next. Either those failures become a silent gap on the invoice, or they become a hard error that blocks the subscription from being created.

enabledenforcedBehaviour
falsefalseNo tax calculation. Invoices are created without tax lines.
truefalseBest-effort. Tax is calculated where possible. If the customer’s country has no registration, no tax.
truetrueStrict. If Stripe Tax errors (no registration, invalid address), the invoice fails and signup is blocked.

Strict mode sounds like the safe default. It’s the loudest way to break signup. A French startup signs up, you don’t have an FR registration yet, the invoice errors, they bounce. Best-effort lets the signup land, the invoice goes out without tax, and you fix the gap by adding the registration. Once you’ve registered everywhere your customers actually live, flip enforced to true so a missing registration becomes a noisy error instead of a silent under-collection.

Common mistake:

Turning on enforced: true before adding registrations for every country your customers can sign up from. The first invoice from an unregistered country fails, the customer can’t subscribe, and you’ve blocked your own funnel. Stay in best-effort mode until your Stripe registrations cover every country a real customer might sign up from, then promote to strict once that map is in place.

What the customer sees

The same defaultTaxConfig produces the right invoice for each jurisdiction:

  • UK consumer: listed price plus a VAT line at 20%, calculated from their billing address.
  • French business with a verified VAT number: listed price plus a “VAT reverse charge” line at zero. Stripe Tax recognises B2B intra-EU sales and applies the reverse-charge mechanism without per-customer wiring.
  • US customer in a state where you have nexus: listed price plus state and local sales tax.
  • US customer in a state where you don’t: listed price, no tax line.

Each invoice carries the right line, and you don’t write any of it.

When to enable tax collection

The trigger is the first paying customer outside your home jurisdiction. Once they sign up, the clock starts on the local rule and the only thing that gets harder by waiting is the back-tax bill. Turn enabled: true with enforced: false the same week you publish your first paid plan, then flip to enforced: true once your registration map catches up with your customer map.

None of this work moves a metric your team cares about. Doing it inside the gateway you’ve already wired up for billing is the difference between shipping a feature and standing up a second system to keep the first one compliant.

Tax Collection Reference

Full reference for enabling Stripe Tax on a Zuplo billing profile, the enforcement modes, tax behaviour config, and the troubleshooting paths for missing tax lines.