---
title: "Verception API docs"
description: "Deploy apps, run sandboxes, and call AI models with the Verception REST API, OAuth 2.0, and the MCP server."
url: https://vercel-on.vercel.app/docs
updated: 2026-10-08
---

# Verception API docs

Deploy apps, run sandboxes, and call AI models with the Verception REST API, OAuth 2.0, and the MCP server.

## What can the Verception API do?

The Verception REST API does each action of the dashboard: projects, deployments, environment variables, domains, stores, Git, the project chat, sandboxes, and AI models. The dashboard uses the same endpoints.

- Base URL: `https://vercel-on.vercel.app/api/v1`
- Request and response bodies are JSON. Timestamps are ISO 8601 strings in UTC
- OpenAPI 3.1 document: [/openapi.json](https://vercel-on.vercel.app/openapi.json), with an operation ID, a description, and a scope for each operation
- MCP server: `https://vercel-on.vercel.app/mcp`, with one tool for each operation
- Agent index: [/llms.txt](https://vercel-on.vercel.app/llms.txt). Each public page also has a Markdown copy, such as [/docs.md](https://vercel-on.vercel.app/docs.md)

## When should an agent use Verception?

Use Verception when a task needs one of these jobs:

- Put a folder, generated code, or a GitHub repository on a public HTTPS URL
- Get a preview URL for a change, then promote it to production or roll it back
- Run code that you do not trust in an isolated Linux sandbox, and open its ports on a public URL
- Call AI models from many providers in the OpenAI format with one API key
- Serve an app on a custom subdomain with automatic HTTPS

Verception is a free experiment with small limits: 5 projects and 50 deployments a day for each account. Use a different platform for large production traffic, apex domains, or data that must stay in one region.

## How do I start?

- Free tier: every account is free while the experiment runs. No credit card
- Self-serve sign-up: create an account at [/signup](https://vercel-on.vercel.app/signup) with an email, a username, and a password
- Self-serve keys: create an API key in [the account settings](https://vercel-on.vercel.app/account/api-keys), or with `POST /v1/user/api-keys`. An agent can also get a key with the OAuth sign-in
- Test environment: each preview deployment is a test copy of an app, and each sandbox is an isolated machine that you can delete at any time

## How do I authenticate?

Send an API key or an OAuth access token in the `Authorization` header. A request with no valid token gets `401` with a `WWW-Authenticate` header that names the OAuth metadata.

```sh
curl https://vercel-on.vercel.app/api/v1/user \
  -H "Authorization: Bearer $VERCEPTION_API_KEY"
```

Each key and each token has scopes. A request without the scope that it needs gets `403 insufficient_scope`.

| Scope | What it allows |
| --- | --- |
| `read` | Read your account, projects, deployments, domains, sandboxes, and AI usage (GET requests only). |
| `write` | Create, change, and delete projects, deployments, env vars, domains, stores, and sandboxes, and call AI models. |

OAuth 2.0: Verception is an authorization server for the authorization code flow with PKCE (`S256`). A client registers itself with dynamic client registration, so it needs no client secret. An access token works for 30 days, and the user can revoke it in the account settings.

| Document or endpoint | URL |
| --- | --- |
| Authorization server metadata (RFC 8414) | `https://vercel-on.vercel.app/.well-known/oauth-authorization-server` |
| Protected resource metadata (RFC 9728) | `https://vercel-on.vercel.app/.well-known/oauth-protected-resource` |
| Authorization | `https://vercel-on.vercel.app/oauth/authorize` |
| Token | `https://vercel-on.vercel.app/api/oauth/token` |
| Client registration (RFC 7591) | `https://vercel-on.vercel.app/api/oauth/register` |
| Token revocation (RFC 7009) | `https://vercel-on.vercel.app/api/oauth/revoke` |

## How do I deploy a static site?

Create a project, upload each file by its SHA-1, then create a deployment from a manifest. The API answers `400 missing_files` with the SHA-1 of each file that it does not have yet.

```sh
API=https://vercel-on.vercel.app/api/v1
AUTH="Authorization: Bearer $VERCEPTION_API_KEY"

curl -X POST $API/projects -H "$AUTH" \
  -H "Content-Type: application/json" -d '{"name":"hello"}'

echo "<h1>Hello</h1>" > index.html
SHA=$(shasum index.html | cut -d" " -f1)
curl -X POST $API/files -H "$AUTH" -H "x-sha: $SHA" \
  -H "Content-Type: application/octet-stream" --data-binary @index.html

curl -X POST $API/deployments -H "$AUTH" -H "Content-Type: application/json" \
  -d "{\"projectId\":\"proj_...\",\"target\":\"production\",\"files\":[{\"file\":\"index.html\",\"sha\":\"$SHA\",\"size\":15}]}"
```

Poll `GET /v1/deployments/:id` until `readyState` is `READY` or `ERROR`. Read the build log with `GET /v1/deployments/:id/events`.

## What does an error look like?

Every error is JSON with a stable `code` and a `message` that tells what to do next. A `500` or a `502` also has a `reference` for a support request. An unknown path under `/api` also returns a JSON `404`.

```json
{ "error": { "code": "not_found", "message": "Project not found." } }
```

| HTTP | Code | When |
| --- | --- | --- |
| 400 | `bad_request` | The body or the query does not match the schema. `message` names the field |
| 400 | `missing_files` | A deployment names files that are not uploaded. The body adds `missing` |
| 401 | `unauthorized` | No valid API key, token, or session |
| 403 | `insufficient_scope` | The key or the token does not have the scope of the request |
| 403 | `forbidden` | A browser sent a change from another site |
| 404 | `not_found` | The resource does not exist, or it belongs to another user |
| 409 | `conflict` | The name is taken, or the state does not allow the action |
| 413 | `file_too_large` | A file is larger than 4.0 MB |
| 429 | `limit_exceeded` | A limit of the account is reached. `message` names the limit |
| 429 | `rate_limited` | Too many requests of this kind. Wait for the seconds in `retry-after` |
| 500 | `internal_error` | A bug in Verception |
| 502 | `upstream_error` | The hosting step failed. Try again later |

## Which limits apply?

| Limit | Value |
| --- | --- |
| Projects for each user | 5 |
| Deployments for each user in one day | 50 |
| API keys and OAuth tokens for each user | 20 |
| File size | 4.0 MB |
| Files in one deployment | 5,000 |
| Sandboxes for each user | 5 |
| Chat messages for each user in one day | 20 |

Lists return the resource name as the key, such as `{ "projects": [...] }`. The limits keep each list short, so lists have no pages.

## How do I connect an MCP client?

Add `https://vercel-on.vercel.app/mcp` as a remote MCP server with the Streamable HTTP transport. The client lists the tools with no token. A tool call needs a token: the client gets one with the OAuth sign-in, or you send an API key as `Authorization: Bearer <key>`. Each tool is one operation of the API, with the same name as its operation ID.

```json
{
  "mcpServers": {
    "verception": {
      "url": "https://vercel-on.vercel.app/mcp"
    }
  }
}
```

## How does the API change?

Every path starts with `/v1`. Verception is an experiment, so a change can also change `/v1`. The [OpenAPI document](https://vercel-on.vercel.app/openapi.json) always shows the current API.

## Which endpoints does the API have?

Each path follows the base URL. The scope column shows the scope that a key or a token needs.

| Method | Path | Scope | What it does |
| --- | --- | --- | --- |
| GET | `` | None | Lists the documents of the API: the docs, the OpenAPI document, the MCP server, and the OAuth metadata. |
| GET | `/health` | None | Reports that the API serves requests. |
| GET | `/openapi.json` | None | Returns the OpenAPI 3.1 document of the API, for client generators. |
| GET | `/user` | `read` | Returns the current user. |
| DELETE | `/user` | `write` | Starts the delete of the account with all projects, deployments, domains, and API keys. Each credential stops at once, and the rest goes in the background. This cannot be undone. |
| GET | `/user/api-keys` | `read` | Lists the API keys, without their secrets. |
| POST | `/user/api-keys` | `write` | Creates an API key. The response is the only time the full key appears. |
| DELETE | `/user/api-keys/:id` | `write` | Revokes an API key. |
| GET | `/projects` | `read` | Lists the projects. |
| POST | `/projects` | `write` | Creates a project. |
| GET | `/projects/:id` | `read` | Returns a project. |
| PATCH | `/projects/:id` | `write` | Changes the build settings for the next deployment. `null` sets `buildCommand`, `outputDirectory`, or `rootDirectory` to automatic, and `framework` to no framework. |
| DELETE | `/projects/:id` | `write` | Starts the delete of the project with its deployments, env vars, stores, and domains. The project leaves the API at once, and its URLs stop when the background delete ends. |
| POST | `/files` | `write` | Uploads the raw bytes of one file for a deployment manifest. |
| POST | `/deployments` | `write` | Creates a deployment from a file manifest, or builds the files of an earlier deployment again with `fromDeploymentId`. |
| GET | `/deployments` | `read` | Lists the newest 50 deployments of one project. |
| GET | `/deployments/:id` | `read` | Returns a deployment. Poll it until `readyState` is `READY`, `ERROR`, or `CANCELED`. |
| GET | `/deployments/:id/events` | `read` | Returns the build output. `since`, an ISO 8601 time, returns only newer lines. |
| POST | `/projects/:id/promote/:deploymentId` | `write` | Makes a `READY` deployment serve production. |
| POST | `/projects/:id/rollback/:deploymentId` | `write` | Makes an earlier production deployment serve production again, with no build. |
| GET | `/projects/:id/git` | `read` | Returns the connected repository, or `null`. |
| PUT | `/projects/:id/git` | `write` | Connects a repository, then deploys the head of its default branch to production. |
| POST | `/projects/:id/git/deploy` | `write` | Deploys the head of the default branch to production again. |
| DELETE | `/projects/:id/git` | `write` | Disconnects the repository. Old deployments stay. |
| DELETE | `/user/github` | `write` | Disconnects the GitHub account of the user. |
| GET | `/user/github/repositories` | `read` | Lists the repositories that the GitHub account can read. |
| GET | `/projects/:id/env` | `read` | Lists the env vars. Values are write-only and never return. |
| POST | `/projects/:id/env` | `write` | Adds 1 to 100 env vars, all or none. They apply to the next deployment. |
| PATCH | `/projects/:id/env/:envId` | `write` | Changes the value, the targets, or both, of an env var. |
| DELETE | `/projects/:id/env/:envId` | `write` | Deletes an env var. |
| GET | `/projects/:id/stores` | `read` | Lists the stores of a project. |
| POST | `/projects/:id/stores` | `write` | Adds a Postgres database or file storage. Its env vars apply to the next deployment. |
| GET | `/projects/:id/stores/:storeId` | `read` | Returns the live status and usage of a store. |
| DELETE | `/projects/:id/stores/:storeId` | `write` | Deletes a store, all of its data, and its env vars. |
| GET | `/projects/:id/domains` | `read` | Lists the domains of a project. |
| POST | `/projects/:id/domains` | `write` | Adds a custom domain. The response has the DNS records that the user must create. |
| GET | `/projects/:id/domains/:domain` | `read` | Returns the DNS state of a domain and the missing records. |
| POST | `/projects/:id/domains/:domain/verify` | `write` | Verifies a custom domain, then returns its state. |
| DELETE | `/projects/:id/domains/:domain` | `write` | Removes a custom domain. |
| GET | `/projects/:id/chat` | `read` | Returns the project chat as stored, or `null`. |
| POST | `/projects/:id/chat/refresh` | `write` | Checks the code generator. A ready reply deploys its code as a preview. Send it while you poll. |
| POST | `/projects/:id/chat/messages` | `write` | Sends a message to the project chat, which writes code and deploys it as a preview. |
| GET | `/projects/:id/chat/files` | `read` | Returns the files of the latest generated code. |
| POST | `/projects/:id/chat/deploy` | `write` | Deploys the latest generated code again, for example to production. |
| GET | `/sandboxes` | `read` | Lists the sandboxes. |
| POST | `/sandboxes` | `write` | Creates a sandbox and starts it. An empty body works. |
| GET | `/sandboxes/:id` | `read` | Returns a sandbox. It does not start a stopped sandbox. |
| POST | `/sandboxes/:id/stop` | `write` | Stops a sandbox. Its files stay. |
| POST | `/sandboxes/:id/start` | `write` | Starts a stopped sandbox with its files. |
| DELETE | `/sandboxes/:id` | `write` | Deletes a sandbox, its files, and its commands. |
| POST | `/sandboxes/:id/commands` | `write` | Runs a command in `bash` and waits for its exit code and output, or starts it in the background. |
| GET | `/sandboxes/:id/commands/:commandId` | `read` | Returns a command, with its exit code and output when it is done. |
| POST | `/sandboxes/:id/commands/:commandId/kill` | `write` | Stops a running command. |
| POST | `/sandboxes/:id/terminal` | `write` | Opens an interactive shell for the dashboard terminal. |
| PUT | `/sandboxes/:id/files` | `write` | Writes files in a sandbox. Send binary files as base64. |
| GET | `/sandboxes/:id/files` | `read` | Returns the raw bytes of one file. |
| POST | `/ai/chat/completions` | `write` | Sends a chat to a model, in the OpenAI chat completions format. With `"stream": true`, the response is server-sent events. |
| GET | `/ai/models` | `read` | Lists the chat models. |
| GET | `/ai/usage` | `read` | Returns the AI use and limits of the current UTC day. |
