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
| Behaviour | POST /v1/subscriptions | POST /v1/imports/customers |
|---|---|---|
| Period clocks | Start at now | Your current_period_start / current_period_end |
| Activation grants | Always fire | Default grant_policy: none — no re-grant |
| Webhooks | subscription.created, credit.granted, … | Quiet by default; opt into *.imported only |
| Credits | Issued by plan grants | Seed 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.
Loading…