---
title: "Bring an existing business"
url: /lifecycle/import
---

# Bring an existing business

> Import an OpenAPI file into your file, and Stripe customers, subscriptions, members and Keys into the product, each tried dry first and undone within 7 days.

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](/reference/platform.md#key).

```mermaid title="An import, from upload to done"
stateDiagram-v2
  [*] --> uploaded: Import.create
  uploaded --> ready: no conflict left
  ready --> running: Import.run
  running --> done: every row
  running --> failed: a row fails
  failed --> running: run again
  done --> undone: within 7 days
```

## 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.

```sh title="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.

```http title="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](/commerce/agreements.md#prices).

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

```ts title="definition.ts"
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 bring | It becomes | What a Customer notices |
| --- | --- | --- |
| An OpenAPI 3 file | A `definition.ts`: a Noun per schema, a Verb per operation, and a report of what did not map | Nothing |
| Stripe customers | Customers, each keeping its Stripe customer id; cards stay in Stripe and are never copied | Nothing; the next receipt carries your brand |
| Stripe subscriptions | Each on the plan whose price matches, or on its own price kept by grandfathering | The same price on the same day |
| Members | Members of each Customer's org, with password hashes kept or a sign-in link sent | The same sign-in |
| Keys | Each Key's SHA-256 digest and prefix, tied to its Customer | The 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](/data/front.md) 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](/reference.md#errors) with its cure.

| What happens | The answer |
| --- | --- |
| The file is not OpenAPI 3.0 or 3.1, or is past the limits | 422 [`openapi_invalid`](/reference.md#openapi_invalid), with the `line` it stopped at |
| `Import.run` while the plan still names conflicts | 409 [`import_conflicts`](/reference.md#import_conflicts), naming each |
| A second import of the same kind for one product | 409 [`import_running`](/reference.md#import_running), naming the one running |
| A row fails, such as a member whose email another org holds | The import turns `failed`, naming the row and its Problem; fix it and run again |
| A password hash in a scheme the platform does not keep | Not a failure: that member gets a sign-in link instead |
| Stripe access is withdrawn during a run | `failed` at the next row; nothing already imported is touched |
| `Import.undo` after 7 days | 409 [`undo_closed`](/reference.md#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
| What | How much |
| --- | --- |
| An OpenAPI file | 10 MB and 2,000 operations |
| Rows in one Stripe import | 1,000,000 customers, with their subscriptions |
| Speed | About 1,000 rows a second, with progress on `Import.get` |
| Password hashes kept | bcrypt, scrypt and argon2; any other sends a sign-in link |
| Undo | Within 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 as | Taken out by `Product.export` as |
| --- | --- |
| An OpenAPI file | `definition.ts`, each [Version](/reference/platform.md#version), and its OpenAPI document |
| Stripe customers and subscriptions | The same Stripe account, which stays yours throughout |
| Members | One export per [Namespace](/reference/platform.md#namespace), with their sign-in methods |
| Keys | Their digests, so the next server checks every Key without holding one |

[Leaving APIs.Dev](/lifecycle.md#leave) lists the rest, and [what Customers keep](/lifecycle/continuity.md) holds for them either way.