> ## Documentation Index
> Fetch the complete documentation index at: https://docs.instapods.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Imports

> Create a pod from a live URL, an AI artifact, or pasted source.

The import endpoint turns something that already exists elsewhere into a running pod. Two of the
four import routes go through it:

| Route                                                | Endpoint                                                                   |
| ---------------------------------------------------- | -------------------------------------------------------------------------- |
| **Live URL** (static snapshot) and **pasted source** | `POST /api/imports`, documented here                                       |
| **GitHub repository**                                | [`POST /api/pods/from-repo`](/api-reference/pods#create-a-pod-from-a-repo) |
| **Project ZIP**                                      | [`POST /api/pods/from-zip`](/api-reference/pods#create-a-pod-from-a-zip)   |

See [Import an existing app](/guides/import-existing-app) for which route fits what, and
[Import AI artifacts](/guides/import-artifacts) for the Claude and ChatGPT paths.

Rate limited to **20 requests per hour per IP**, because a single call can trigger a crawl of
someone else's site.

## Create an Import

```
POST /api/imports
```

Send **exactly one** of `url` or `paste`. Everything else is optional.

| Field         | Type    | Description                                                                                                          |
| ------------- | ------- | -------------------------------------------------------------------------------------------------------------------- |
| `url`         | string  | The page or artifact to import                                                                                       |
| `paste`       | object  | Source code sent inline. See [Pasting source](#pasting-source)                                                       |
| `mode`        | string  | `site` forces a static snapshot of `url` even when the host is one we'd normally answer with a guide. Omit to detect |
| `max_pages`   | integer | Cap the crawl for a site snapshot                                                                                    |
| `single_page` | boolean | Snapshot only the page you gave, following no links                                                                  |
| `name`        | string  | Pod name. Derived from the artifact's title if omitted                                                               |
| `region`      | string  | Region slug. Geo-resolved from your IP if omitted                                                                    |
| `plan_slug`   | string  | Plan. Default `launch`                                                                                               |
| `ssh_key`     | string  | Public SSH key to inject                                                                                             |
| `dry_run`     | boolean | Parse and return the artifact **without** creating a pod                                                             |

Setting any of `mode: "site"`, `max_pages` or `single_page` makes it a site snapshot, which is given
a longer fetch budget than other imports (75 seconds rather than 25).

```bash theme={null}
curl -X POST https://app.instapods.com/api/imports \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com", "mode": "site", "max_pages": 25}'
```

**Response: `202 Accepted`**

```json theme={null}
{
  "artifact": {
    "source": "site",
    "source_url": "https://example.com",
    "title": "Example",
    "slug": "example",
    "type": "html",
    "preset": "static",
    "files": [{ "path": "index.html", "size": 4096 }],
    "transfer": { "headline": "...", "transfers": [], "does_not_transfer": [], "notes": [] }
  },
  "pod": { "name": "example", "status": "creating" },
  "message": "Pod created. Files will be uploaded once the pod is running."
}
```

The pod is created immediately and the files are pushed in the background once it reaches `running`,
which is why this is a `202` and not a `201`. Poll `GET /api/pods/{name}` the same way you would
after any other create.

If you didn't pass a `name` and the derived one is taken, we suffix it rather than failing. An
explicit `name` is used as-is, so it can collide and return `409`.

### Errors

| Code  | Reason                                                                                                 |
| ----- | ------------------------------------------------------------------------------------------------------ |
| `400` | Neither or both of `url`/`paste`, an empty paste, a bad name or unknown preset, or a fetch that failed |
| `402` | No payment method, or the subscription is suspended                                                    |
| `409` | A pod with the name you gave already exists                                                            |
| `422` | The URL belongs to a platform we can't fetch from. **The response carries the guide**                  |
| `503` | No capacity in the chosen region                                                                       |

The `422` is the interesting one. It isn't a bad request - the URL is fine, the path is closed - and
it comes back with the steps for getting that platform's code out by hand:

```json theme={null}
{
  "error": "Lovable projects can't be imported from their URL - push the project to your own GitHub repository, then deploy that repository here",
  "guide": {
    "slug": "lovable",
    "name": "Lovable",
    "summary": "...",
    "route": "github",
    "automatic": false,
    "steps": [{ "text": "Open your project and connect it to GitHub" }],
    "transfer": { "...": "..." },
    "docs": [{ "text": "Lovable docs", "href": "https://..." }]
  }
}
```

Show the guide. A bare error here is a dead end for the user, which is the failure this feature
exists to avoid.

### Previewing First

Set `dry_run: true` and you get `200 OK` with just the parsed `artifact` - no pod, no charge. This
is what the dashboard's preview pane uses.

File **content** is trimmed in a preview even though paths and sizes are not: anything binary, over
64KB, or past a 128KB total budget comes back with `content_omitted` set instead of `content`, and a
site snapshot keeps the content of `index.html` only. Sizes are always accurate, so use those if you
want to show what's coming.

### Pasting Source

```json theme={null}
{
  "paste": {
    "title": "Dashboard",
    "source": "claude",
    "type": "react",
    "language": "tsx",
    "body": "export default function App() { ... }"
  }
}
```

| Field      | Description                                                                               |
| ---------- | ----------------------------------------------------------------------------------------- |
| `body`     | The source, for a single-file artifact                                                    |
| `files`    | A whole project as `[{"path": "...", "content": "..."}]`. When present, `body` is ignored |
| `title`    | Names the pod and the artifact                                                            |
| `source`   | Metadata only, e.g. `claude`, `chatgpt`, `paste`                                          |
| `type`     | Override detection: `html`, `react`, `vue`, `svg`, `mermaid`, `code`, `multi`             |
| `language` | A hint for `type: "code"`                                                                 |

Send one of `body` or `files`, not both. A `files` project containing a `package.json` is installed
and built on the pod rather than served as static files.

## The Import Catalog

```
GET /api/imports/sources
```

Every route in, every guided platform, and what each one does and does not carry across. Read-only,
no outbound fetch, and outside the import rate limit - so it's safe to call on page load.

**Response: `200 OK`**

```json theme={null}
{
  "routes": [
    {
      "route": "github",
      "name": "GitHub repository",
      "description": "Clone a repo and build it on the pod, with push-to-deploy...",
      "transfer": {
        "headline": "...",
        "transfers": [{ "label": "Your code", "detail": "..." }],
        "does_not_transfer": [{ "label": "Your database and its contents", "detail": "..." }],
        "notes": ["..."]
      }
    }
  ],
  "platforms": [
    {
      "slug": "lovable",
      "name": "Lovable",
      "summary": "...",
      "route": "github",
      "automatic": false,
      "steps": [{ "text": "..." }],
      "transfer": { "...": "..." },
      "host_examples": ["lovable.app"]
    }
  ]
}
```

| Query Param | Description                                    |
| ----------- | ---------------------------------------------- |
| `url`       | Also answer "what would you do with this URL?" |

With `?url=`, the response gains `detected_source` (`site`, `claude`, `chatgpt`, or `unknown`) and,
when the URL matches a guided platform, that platform's `guide`. This is what the dashboard calls as
you type a URL, so it can show the right next step before you submit.

`automatic` is `false` on every guided platform, stated explicitly so nothing implies a one-click
path that doesn't exist.

<Note>
  Build your own import UI against this endpoint rather than hard-coding the routes and their
  caveats. It is served from the backend precisely so the dashboard, the CLI and these docs cannot
  drift on what an import actually carries.
</Note>
