# Vercel

URL: https://docs.hanzo.ai/docs/guides/migrate/vercel

> Vercel builds a repo and serves the result. Here that is two capabilities — /v1/platform (37) builds and runs an app, /v1/projects (27) serves a static export — and which one you want depends on whether your output needs a server.

Vercel takes a repository, builds it, and serves the output. Two capabilities
answer that, and picking the right one is the whole migration:

- **`/v1/platform`** (37 operations) — an app that is **built and run**. It has
  the build (`POST /v1/platform/runner`, listed at `/v1/platform/builds`), a
  deploy, per-deployment logs, preview, promote, rollback, env and domains. This
  is the like-for-like Vercel replacement.
- **`/v1/projects`** (27 operations) — a **static site**. You bring a built
  directory; it stores it as a content-addressed release and points a domain at
  one. No build, because there is nothing to build.

If your project is `next build` with route handlers, ISR or middleware, it is an
app: use `/v1/platform`. If it is `output: 'export'` — a directory of HTML — the
Sites plane is simpler and cheaper. This documentation site is the first; the
marketing site is the second.

## Start here

Register the app against your repo, then deploy it — two calls, no dashboard.

```bash
# 1. mint a key — sk- belongs on a server, pk- is safe in a browser
curl -sS -X POST https://api.hanzo.ai/v1/account/keys \
  -H "Authorization: Bearer $HANZO_SESSION" \
  -H 'Content-Type: application/json' \
  -d '{"type":"secret"}'

# 2. register the app against a git repo — it lands in draft, nothing built yet
curl -sS -X POST https://api.hanzo.ai/v1/platform/projects/default/apps \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"name":"web","source":"git","repo":{"url":"https://github.com/acme/web","branch":"main"},"port":3000}'

# 3. build it and run it
curl -sS -X POST https://api.hanzo.ai/v1/platform/projects/default/apps/web/deploy \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"commit":"main"}'
```

Step 2 answers 201 with the app in `draft` and its canonical HTTPS host already
attached; the `default` project needs no creating, because every org has one.
Step 3 answers 202 — an accepted deployment, not a live one — and comes back
`building` while BuildKit runs, which is why the logs call below exists.

### Core capabilities

| Capability     | What it does                                                                                     | Operations |
| -------------- | ------------------------------------------------------------------------------------------------ | ---------- |
| `/v1/platform` | Builds a repo and runs it: deploy, per-deployment logs, preview, promote, rollback, env, domains | 37         |
| `/v1/projects` | Serves a directory you already built, as content-addressed releases with domains and purge       | 27         |
| `/v1/function` | Publishes one handler with no app around it, and invokes it                                      | 11         |

## Nouns

Vercel is not only deploys, and neither is this. The left column is their
product surface; the right is the capability that answers it.

### Building and serving

| Vercel                     | Hanzo                                                        |
| -------------------------- | ------------------------------------------------------------ |
| Team                       | Your org, taken from the validated key                       |
| Project                    | Project — IAM owns it; apps and sites live under it          |
| Deployment (built for you) | `POST /v1/platform/projects/{project}/apps/{app}/deploy`     |
| Build logs                 | `GET .../apps/{app}/deployments/{id}/logs`                   |
| Preview deployment         | `POST .../apps/{app}/preview`                                |
| Promote to production      | `POST .../apps/{app}/promote`                                |
| Instant rollback           | `POST .../apps/{app}/rollback`                               |
| Environment variables      | `PUT .../apps/{app}/env`                                     |
| Domain, and verifying it   | `.../apps/{app}/domains`, then `/{host}/verify`              |
| Purely static output       | `/v1/projects` (27) — upload a directory, activate a release |
| Rollback, static           | `POST /v1/projects/{slug}/releases/{release}/activate`       |
| Edge cache purge           | `POST /v1/projects/{slug}/purge`                             |

### The rest of the platform

| Vercel                                   | Hanzo                                                           |
| ---------------------------------------- | --------------------------------------------------------------- |
| Serverless / Edge Functions              | `/v1/function` (11)                                             |
| Cron Jobs                                | `/v1/tasks` (5) — a durable engine, not a timer on a deployment |
| Vercel KV                                | `/v1/kv` (6)                                                    |
| Vercel Blob                              | `/v1/s3` (6)                                                    |
| Vercel Postgres / storage you provision  | `/v1/provisioning` (28)                                         |
| Edge Config                              | `/v1/flag` (8) — a definition and a deterministic decision      |
| Web Analytics                            | `/v1/event` (12)                                                |
| Speed Insights, Log Drains, traces       | `/v1/o11y` (381)                                                |
| Firewall, rate limiting, allowed origins | `/v1/gateway` (3)                                               |
| Domains bought and held                  | `/v1/domain` (7)                                                |
| AI SDK / AI Gateway                      | `/v1/ai` (272) — the models, not a proxy to somebody else's     |

The counts are the operations each capability publishes in the document this
site is built from. `/v1/ai` being the largest is the point of the whole
platform rather than an add-on to hosting.

## The call

Vercel:

```bash
vercel deploy --prod
```

Hanzo, as an app that builds — the shape that matches:

```bash
curl -sS -X POST \
  "https://api.hanzo.ai/v1/platform/projects/acme/apps/web/deploy" \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"commit":"main"}'
```

Then watch it, and promote when you are happy:

```bash
curl -sS "https://api.hanzo.ai/v1/platform/projects/acme/apps/web/deployments/$ID/logs" \
  -H "Authorization: Bearer $HANZO_API_KEY"

curl -sS -X POST \
  "https://api.hanzo.ai/v1/platform/projects/acme/apps/web/promote" \
  -H "Authorization: Bearer $HANZO_API_KEY"
```

Static instead, when the output needs no server — enqueue, upload, complete:

```bash
curl -sS -X POST https://api.hanzo.ai/v1/projects/my-site/deployments \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"commit":"'"$GIT_SHA"'"}'
```

The bytes go straight to object storage with the grant that call hands back, so
CI never holds a standing storage credential and the edge body limit never
applies to your largest asset. A release is addressed by the digest of its own
manifest, so rolling back is activating an earlier one — the bytes never move.

## What does not carry

Short, because most of Vercel's surface has an answer above. What genuinely
differs:

**Git-push-to-deploy is opt-in, not ambient.** Vercel watches every branch of a
connected repo and deploys what it finds. A push here reaches `cloud.OnGitPush`
and an app rebuilds when it tracks that repo — you say which repo an app
tracks, rather than the connection implying it for all of them.

**No `*.vercel.app` hostname per commit.** `preview` gives you a running
deployment; a memorable per-commit subdomain minted automatically is not part
of it. Point a domain at what you want to share.

**Framework detection is Pack's job, and it is the default.** Hanzo Pack is a
zero-config builder: it analyses the repository, picks the provider and writes
the image, for Node, Python, Go, PHP and more, with BuildKit layer and mount
caching underneath. So this is not the difference it looks like — what you give
up is Vercel's opinion about _their_ framework list specifically, not detection
itself.

**No image optimization in the request path.** Vercel rewrites and caches
images per request from `next/image`. Nothing here transforms an image as it is
served, so pre-size them at build.

**Cron is a durable engine, not a field on a deployment.** `vercel.json`'s
`crons` ships with the deployment and disappears with it. `/v1/tasks` outlives
any one deploy, which is what you want and is a different thing to port.

**On the static plane specifically:** `/v1/projects` does not build and has no
per-request compute, and its completion manifest is a single body subject to
the edge limit — a full prerender of very many pages is what makes that bite.
Those are properties of choosing the static plane over `/v1/platform`, not of
the platform.
