---
title: "Create deployment"
method: POST
path: "/api/v1/deployments"
tags: ["Deployments"]
---

# Create deployment

`POST /api/v1/deployments`

## Request

- Base URL: `https://previewdrop.dev`
- URL: `https://previewdrop.dev/api/v1/deployments`
- Auth: HTTP bearer

Triggers a new preview build. The deployment record is created as `queued`, then immediately dispatched to the build worker — the response reflects the worker's initial status (typically `building`).

**In-flight cancellation**

If a deployment for the same project and branch is already `queued`, `building`, or `deploying`, it is **automatically cancelled** (status set to `stopped`) before the new build starts. This matches the push webhook behavior: the last deploy always wins, orphaned containers are not left running.

**Specifying the repository**

You must provide either `projectId` or `repoUrl`:

- `projectId` — uses the GitHub repo and env vars configured on the project. Request-level `envVars` are merged on top. Private repos are accessed automatically via the GitHub App.
- `repoUrl` — ad-hoc build from any **public** GitHub HTTPS URL (credentials embedded in the URL are not supported; use `projectId` for private repos).

**Environment variables**

Keys are automatically normalised to UPPERCASE. Each key must match `[A-Z_][A-Z0-9_]*` (up to 100 chars) and each value may be up to 8 KiB.

**TTL**

If omitted, the preview lives for your plan's default TTL (1 h / 4 h / 8 h / 7 d depending on plan). Pass `ttlMs` to override, e.g. `3600000` for 1 hour.

**Polling for completion**

After creating a deployment, poll `GET /api/v1/deployments/{id}` every 5–15 seconds until `status` is `ready` or `failed`. A typical build takes 60–180 seconds.

**Idempotency**

Supply an `Idempotency-Key` request header (e.g. a UUID) to safely retry failed requests without triggering duplicate deploys. If the key matches a request processed within the last 5 minutes, the cached successful response is returned with an `Idempotency-Replayed: true` header instead of firing a new build. Keys must be 8–128 alphanumeric/hyphen/underscore characters.

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `projectId` string, uuid: ID of a project configured in your dashboard. The project's linked GitHub repo and env vars are used as the base. Mutually exclusive with `repoUrl` — provide one or the other.
  - `repoUrl` string, uri: Public HTTPS clone URL on github.com, gitlab.com or bitbucket.org — credentials embedded in the URL (e.g. `x-access-token:TOKEN@`) are rejected. For private repos, use `projectId` instead (PreviewDrop uses the workspace's GitHub App installation or GitLab/Bitbucket OAuth token automatically). Mutually exclusive with `projectId`.
  - `branch` string: Git branch to build. Must match `[a-zA-Z0-9/_\-.]{1,200}`. Defaults to the project's default branch (when using `projectId`) or `main`.
  - `envVars` object: Runtime environment variables injected into the container. Keys are normalised to UPPERCASE. When using `projectId`, these are merged on top of the project's stored env vars (request values take precedence on key conflicts).
  - `ttlMs` integer: Preview lifetime in milliseconds. The container is automatically destroyed after this duration. Defaults to your plan TTL (free: 3 600 000 / starter: 14 400 000 / pro: 28 800 000 / enterprise: 604 800 000).
  - `githubToken` string: A GitHub personal access token or installation token for cloning private repos when using `repoUrl` directly (without `projectId`). The token is embedded in the clone URL sent to the build worker and is never stored. Must match `[A-Za-z0-9_.\-]{4,255}`.

## Response `201`

Deployment queued successfully.

- DeploymentWrapper
  - `deployment` Deployment, required: An ephemeral preview deployment.
    - `id` string, uuid, required: Unique deployment identifier.
    - `tenant_id` string, uuid, required: Workspace (tenant) that owns this deployment.
    - `project_id` string, uuid, nullable: ID of the linked project, or `null` for ad-hoc deployments created directly with `repoUrl`.
    - `pd_projects` object, nullable: Nested project record. Only present in **list** responses (`GET /api/v1/deployments`). `null` for ad-hoc deployments with no linked project.
      - `id` string, uuid
      - `name` string
      - `github_repo` string
    - `worker_deploy_id` string, nullable: Internal ID used by the build worker. Use this to correlate with worker logs.
    - `url` string, uri, nullable: Publicly accessible preview URL. Available once status is `ready`. `null` while building.
    - `status` 'queued' | 'building' | 'deploying' | 'ready' | 'failed' | 'expired' | 'stopped', required: Current state of the deployment: - `queued` — accepted, waiting for a build slot - `building` — Docker image is being built - `deploying` — container is starting - `ready` — container is live and reachable via `url` - `failed` — build or startup error (check `build_log`) - `expired` — TTL elapsed, container destroyed automatically - `stopped` — manually stopped via the API or dashboard
    - `framework` string, nullable: Auto-detected framework (e.g. `nextjs`, `vite`, `create-react-app`). `null` until detected.
    - `branch` string, required: Git branch that was built.
    - `detected_env_vars` string[], nullable: Environment variable names detected in the build output (e.g. from a missing `.env` warning). Useful for debugging misconfigured previews. Populated after first successful worker poll.
    - `build_log` string, nullable: Raw stdout/stderr from the build process. Updated in real time while building. Use `GET /api/v1/deployments/{id}/logs` for a dedicated logs endpoint.
    - `expires_at` string, date-time, nullable: ISO 8601 timestamp when the preview will be automatically destroyed. Determined by your plan TTL at the time of creation.
    - `created_at` string, date-time, required: ISO 8601 timestamp when the deployment was created.
    - `updated_at` string, date-time, required: ISO 8601 timestamp of the last status update.

## Other responses

- `400`: Invalid request body. Common causes: neither `projectId` nor `repoUrl` provided; invalid branch name; `repoUrl` not a GitHub HTTPS URL; env var key format invalid.
- `401`: Missing or invalid API key.
- `403`: API access is not available on your current plan. Upgrade to Starter or higher.
- `404`: The specified `projectId` does not exist or does not belong to your workspace.
- `409`: The workspace has reached its concurrent preview limit (or per-project limit). Destroy an existing preview or upgrade your plan — retrying without changing state will keep returning 409.
- `429`: Rate limited. Too many requests in the current window — back off and retry after the `Retry-After` seconds.
- `500`: Internal server error (database failure).
- `502`: The build worker returned an error. The deployment is marked `failed`.

---

[API](https://skmtc.dev/previewdrop/apis/previewdrop-api.md) · [All operations](https://skmtc.dev/previewdrop/apis/previewdrop-api/llms.txt) · [OpenAPI document](https://skmtc.dev/previewdrop/apis/previewdrop-api/revisions/0742e253496b?raw)
