quotastack Docs
Docs / Changelog / 2026-07-25 · Legacy customer import

2026-07-25 · Legacy customer import

Legacy customer import (Phase 1) — mid-plan cutover for existing customers, with your period clocks, remaining credit seeds, and a quiet-by-default webhook story.

Legacy customer import

Additive release. Nothing existing changes behaviour — but if you are migrating customers from another system, the recommended path has changed.

Added — POST /v1/imports/customers

Mid-plan cutover for customers who already exist somewhere else. Previously the only route in was POST /v1/subscriptions, which restarts the billing period at now and fires the plan’s full activation grants — so migrating a customer halfway through an annual plan both reset their renewal date and handed them a second year of credit.

Import takes your period clocks and seeds only what is actually left.

POST /v1/imports/customers
X-API-Key: qs_live_...
Idempotency-Key: import:user_abc
BehaviourPOST /v1/subscriptionsPOST /v1/imports/customers
Period clocksStart at nowYour current_period_start / current_period_end
Activation grantsAlways fireDefault grant_policy: none — no re-grant
Webhookssubscription.created, credit.granted, …Quiet by default; opt into *.imported only
CreditsIssued by plan grantsSeed remaining balance via credit_blocks[]

Scope required: subscriptions:write. Returns 409 when a non-terminal subscription already exists or an Idempotency-Key is reused with a different body, and 422 on validation failure.

Atomicity. Import pre-validates, then writes. On failure it voids the seed and activation blocks and soft-deletes the subscription it created, so a retry with the same Idempotency-Key succeeds cleanly rather than leaving orphaned credit behind.

Run your whole export through options.dry_run: true first — identical validation, no writes.

See Importing Existing Customers for the full cutover recipe.

Added — credit source migration

source accepts migration, for credit blocks that represent a balance carried over from your previous system rather than a purchase, plan grant, or promotion. Amounts are millicredits, as everywhere else.

Added — subscription origin

Subscriptions now carry origin, either api (created through the normal endpoints) or import. Use it to tell migrated customers apart from natively-created ones when reconciling.

Added — customer.imported and subscription.imported webhooks

Both fire only when a request sets options.emit_webhooks: true. Import is silent by default, precisely so a large cutover does not flood your endpoint with thousands of subscription.created and credit.granted deliveries.

Import never emits subscription.created or credit.granted, regardless of this flag.

Changed — guidance for migrations

Creating a subscription and then clawing back the over-granted credit was the previous workaround. It is no longer the recommended approach — use import, which never over-grants in the first place.