---
title: Publish a version
description: "Points the live URL at one ready version, in seconds, with no build: a promote on Vercel, or a rollback when that version was live before, decided by the version's first_published_at."
api_method: POST
api_path: "/v1/workspaces/{workspaceId}/sites/{siteId}/publish"
canonical_url: https://wiblo.app/docs/developers/api/sites/publish-site-version
last_updated: 2026-09-28T10:47:20+02:00
md_url: https://wiblo.app/docs/developers/api/sites/publish-site-version.md
---

# Publish a version

`POST /v1/workspaces/{workspaceId}/sites/{siteId}/publish`

Points the live URL at one ready version, in seconds, with no build: a promote on Vercel, or a rollback when that version was live before, decided by the version's `first_published_at`. Writes a row to the publish log as `publishing`, makes the call, reads the project every second for up to ten seconds until the production deployment is the version's, then settles the row `published` and moves the site's published version. Answers 200 with the settled row, or 202 with the row still `publishing` when the address has not moved after ten seconds; the next read of the site checks again. An owner or admin only, a person or their agent: the caller's effective role decides, and a run's key carries the lower of its initiator's role and the run's cap, so a member's agent is refused like the member. Per-user rate-limited at 10/60s in its own bucket.

## Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `workspaceId` | `string` | Yes |  |
| `siteId` | `string` | Yes |  |

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `version_id` | `string` | Yes | The `id` of a `ready` version, from the versions list. |

## Request

**curl**

```bash
curl https://api.wiblo.app/v1/workspaces/{workspaceId}/sites/{siteId}/publish \
  -X POST \
  -H "Authorization: Bearer $WIBLO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "version_id": "b7d3e9a1-2c4f-4a8b-9e6d-5f1a3c7b2d84"
}'
```

**TypeScript**

```ts
import { createSdk, publishSiteVersion } from "@workspace/sdk"

const sdk = createSdk({ baseUrl: "https://api.wiblo.app" })

const { data, error } = await publishSiteVersion({
  client: sdk,
  path: { workspaceId: "...", siteId: "..." },
  body: {
    "version_id": "b7d3e9a1-2c4f-4a8b-9e6d-5f1a3c7b2d84"
  },
})
```

## Responses

**`200`** — The address moved: the log row, `published`. Returns `SitePublishSummary`.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | Yes |  |
| `site_id` | `string` | Yes |  |
| `version_id` | `string` | Yes |  |
| `version_number` | `integer \| null` | Yes | The number of the version published. Null only if the version lost its number, which a ready version never does. |
| `previous_version_id` | `string \| null` | Yes | What the live URL served before. Null on version one. |
| `previous_version_number` | `integer \| null` | Yes |  |
| `method` | `"promote" \| "rollback"` | Yes |  |
| `status` | `"publishing" \| "published" \| "failed"` | Yes |  |
| `source` | `"create" \| "person" \| "run" \| "key"` | Yes |  |
| `published_by` | `object \| null` | Yes | The member who pressed, or whose agent pressed. Null for version one, which the platform published at creation, and for another machine key. Also null when that person is no longer an active member of the workspace: their member row is archived, or no member row is linked to them. |
| `published_by.member_id` | `string` | Yes |  |
| `published_by.name` | `string` | Yes |  |
| `published_by_run_id` | `string \| null` | Yes | The agent run whose key pressed, when `source` is `run`. |
| `created_at` | `string` | Yes |  |
| `settled_at` | `string \| null` | Yes |  |

**`202`** — Vercel accepted the call and the address had not moved after ten seconds: the log row, still `publishing`. Returns `SitePublishSummary`.

**`400`** — Body failed validation (`VALIDATION_FAILED`). Returns `ApiErrorEnvelope`.

**`401`** — No valid session, `wbl_*` bearer, or run token was present. Returns `ApiErrorEnvelope`.

**`403`** — The caller's effective role is member, not owner or admin (`ADMIN_REQUIRED`). A run's key carries the lower of its initiator's role and the run's cap. Returns `ApiErrorEnvelope`.

**`404`** — Workspace not visible to the caller (`WORKSPACE_NOT_FOUND`), no site with that id in it (`SITE_NOT_FOUND`), or no version with that id on the site (`VERSION_NOT_FOUND`). Returns `ApiErrorEnvelope`.

**`409`** — The site's project is not ready (`SITE_NOT_READY`), the version is not `ready` (`VERSION_NOT_READY`), the version's deployment is gone from Vercel (`VERSION_GONE`), or the version is the one the live URL serves (`ALREADY_PUBLISHED`). Returns `ApiErrorEnvelope`.

**`422`** — Path param failed UUID validation (`INVALID_PARAMS`). Returns `ApiErrorEnvelope`.

**`429`** — Rate limit exceeded. The body's `error.code` is `RATE_LIMITED` and `error.details.retry_after` is the same number of seconds as the `Retry-After` header. Returns `ApiErrorEnvelope`.

**`502`** — Vercel refused the call and the live URL is unchanged (`PROVIDER_REJECTED`), Vercel did not answer (`PROVIDER_DOWN`), or the deploy provider is not configured (`UPSTREAM_UNAVAILABLE`). Returns `ApiErrorEnvelope`.

**`503`** — Vercel is rate limited or a move is already in progress; try again in a minute (`PROVIDER_BUSY`). Returns `ApiErrorEnvelope`.
