Skip to content

Bring an existing business

An import moves an API you already sell off another billing system: its OpenAPI file becomes your file, and its Customers arrive with their Keys.

An import, from upload to done

Five steps to move a service

  1. Turn the old service's OpenAPI file into your file with npx apis.dev import openapi, then set the prices it charged.
  2. Plan the Stripe import with --plan. It counts each kind, names every conflict, and changes nothing.
  3. Settle each conflict with Import.resolve: keep one row, merge the two, or skip it.
  4. Run it with Import.run. Nothing a Customer sees changes until every row is in.
  5. Point the old hostname at the product as one of your Domains, so old callers change no line.
Terminal
$ npx apis.dev import openapi ./openapi.yaml
✓ 2 Nouns from 2 schemas: Company, Deal
✓ 7 Verbs from 9 operations; 2 listed in import-report.md
✓ wrote definition.ts
$ npx apis.dev import stripe --plan
✓ 312 customers · 287 subscriptions · 1 conflict

The plan names its one conflict and the fixes Import.resolve takes.

Response
HTTP/1.1 200 OK
Content-Type: application/json
X-Cost: price=0, currency="USD"
X-Request-Id: req_6d2f19c0b8a4e37d5f10

{
  "id": "imp_3",
  "kind": "stripe",
  "state": "uploaded",
  "rows": { "customers": 312, "subscriptions": 287, "members": 0, "keys": 0 },
  "plans": { "Team": 241, "Free": 39, "kept": 7 },
  "conflicts": [
    { "row": "sub_1Q4b", "says": "A price of 39 a month matches no plan",
      "fixes": ["keep", "skip"] }
  ]
}

That is GET https://apis.dev/v1/products/pipeline/imports/imp_3. keep holds the price by grandfathering.

The file the first step writes is whole, and check passes it once the prices are set.

definition.ts
16 lines
import { define } from 'apis.dev'

/** Deals for each Customer, imported from the OpenAPI file of the service it replaces. */
export default define({
  name: 'pipeline', domain: 'pipeline.apis.dev',
  Company: { name: 'string', domain: 'url!#', deals: '<-Deal.company' },
  Deal: {
    title: 'string', amount: 'decimal(12,2)', company: '->Company.deals',
    stage: { open: [], qualify: 'opened', win: 'qualified', lose: ['opened', 'qualified'] },
  },
  offer: {
    Free: { month: 0, seats: 1, includes: { 'Deal.open': 25 } },
    Team: { month: 49, seats: 5, includes: { 'Deal.open': 1000 } },
    'Deal.open': 0.001,
  },
})

What each kind becomes

You bringIt becomesWhat a Customer notices
An OpenAPI 3 fileA definition.ts: a Noun per schema, a Verb per operation, and a report of what did not mapNothing
Stripe customersCustomers, each keeping its Stripe customer id; cards stay in Stripe and are never copiedNothing; the next receipt carries your brand
Stripe subscriptionsEach on the plan whose price matches, or on its own price kept by grandfatheringThe same price on the same day
MembersMembers of each Customer's org, with password hashes kept or a sign-in link sentThe same sign-in
KeysEach Key's SHA-256 digest and prefix, tied to its CustomerThe same Key keeps working

An operation that does not map, such as a callback or an untyped body, is listed with its path. Write it by hand, or front it from the API that serves it now.

When an import stops

A run is idempotent, so one that stops resumes at the row it stopped on. Each stop is a Problem with its cure.

What happensThe answer
The file is not OpenAPI 3.0 or 3.1, or is past the limits422 openapi_invalid, with the line it stopped at
Import.run while the plan still names conflicts409 import_conflicts, naming each
A second import of the same kind for one product409 import_running, naming the one running
A row fails, such as a member whose email another org holdsThe import turns failed, naming the row and its Problem; fix it and run again
A password hash in a scheme the platform does not keepNot a failure: that member gets a sign-in link instead
Stripe access is withdrawn during a runfailed at the next row; nothing already imported is touched
Import.undo after 7 days409 undo_closed; export instead

An undo removes what the import created and releases every Stripe match. It never refunds money already charged; the refund rule does that.

Sizes and speed

WhatHow much
An OpenAPI file10 MB and 2,000 operations
Rows in one Stripe import1,000,000 customers, with their subscriptions
SpeedAbout 1,000 rows a second, with progress on Import.get
Password hashes keptbcrypt, scrypt and argon2; any other sends a sign-in link
UndoWithin 7 days of done

Every import has its export

What arrives by import leaves by export, in the same form, so a product is never locked in.

Brought in asTaken out by Product.export as
An OpenAPI filedefinition.ts, each Version, and its OpenAPI document
Stripe customers and subscriptionsThe same Stripe account, which stays yours throughout
MembersOne export per Namespace, with their sign-in methods
KeysTheir digests, so the next server checks every Key without holding one

Leaving APIs.Dev lists the rest, and what Customers keep holds for them either way.