# Deploys API

> Upload a release as a gzipped tar, control whether it is built, watch it go live, and roll back to an earlier one.

Source: https://docs.fortly.com/api/deploys

A release is a gzipped tar of your project. Upload a static site and it goes live at once. Upload source without its `node_modules` and Fortly's build service installs and builds it first. Earlier releases are kept, so you can put one back without rebuilding. For how builds are detected and run, see [Builds](https://docs.fortly.com/deploy/builds); for rollbacks, [Releases](https://docs.fortly.com/deploy/releases).

- `GET /projects/{name}/deploys` — Its releases, newest first
- `POST /projects/{name}/deploys` — Upload a release
- `POST /projects/{name}/deploys/{id}/activate` — Make an earlier release live again
- `GET /deploys` — Recent releases across all your projects

## The release object

- `id` (string, optional) — The release id, `dpl_` followed by 32 hex characters.
- `project` (string, optional) — The project's `name`.
- `account_id` (string, optional) — The account that made the release.
- `created_at` (integer, optional) — When it was uploaded, Unix seconds.
- `files` (integer, optional) — Files in the unpacked archive.
- `bytes` (integer, optional) — Unpacked size, in bytes.
- `cmd` (string, optional) — The start command recorded on the release. `fortly static` for a site served as files.
- `state` (string, optional) — `building`, `ready`, `live`, `superseded` (was live, replaced by a newer one), `failed`, or `gone` (cleaned up).
- `source` (string | null, optional) — `upload` or `github`.
- `sha` (string | null, optional) — For a repository deploy, the commit. `repo` and `branch` likewise.
- `build` (string | null, optional) — What ran before it went live, such as `npm ci && npm run build`. `null` when nothing was built.
- `project_id` (string | null, optional) — The project's permanent id, `prj_…`.
- `runtime_type` (string | null, optional) — How it is served: `static` or `fortly`.
- `framework` (string | null, optional) — The framework detected at build time, such as `Next.js`.
- `started_at` (integer | null, optional) — When the build began.
- `finished_at` (integer | null, optional) — When the build finished, either way.
- `activated_at` (integer | null, optional) — When it became live.
- `failure` (string | null, optional) — Why a failed release failed, in one line. The full output is in the [project's log](/deploy/logs).
- `spa_fallback` (boolean, optional) — For a static site, whether unmatched paths are answered with `index.html`.

The object also carries build provenance fields — `execution_engine`, `artifact_root`, `manifest_path`, `source_digest`, `builder_image`, `builder_image_digest`, `build_digest` and `build_artifact_kind` — which are `null` when not recorded.

## List releases

- `GET /projects/{name}/deploys` — Its releases, newest first

Returns the project's 50 most recent releases, newest first.

- `name` (string, required) — Path parameter. The project's `name`.

**curl**

```bash
curl https://api.fortly.com/projects/my-app-ada/deploys \
  -H "Authorization: Bearer $FORTLY_TOKEN"
```

**JavaScript**

```js
const res = await fetch("https://api.fortly.com/projects/my-app-ada/deploys", {
  headers: { Authorization: `Bearer ${process.env.FORTLY_TOKEN}` },
});
const { deploys } = await res.json();
```

```json
{
  "deploys": [
    {
      "id": "dpl_3f9c2a71d04e4b8c9a6e5d1f2b7c8a90",
      "project": "my-app-ada",
      "account_id": "8b1e4f0c2d6a4e7f9c3b5a1d0e2f4c6b",
      "created_at": 1791820800,
      "files": 214,
      "bytes": 1893422,
      "cmd": "next start",
      "state": "live",
      "source": "upload",
      "sha": null,
      "repo": null,
      "branch": null,
      "build": "npm ci && npm run build",
      "project_id": "prj_0d4e7a9b1c2f4e6a8b0c3d5e7f9a1b2c",
      "runtime_type": "fortly",
      "execution_engine": null,
      "framework": "Next.js",
      "artifact_root": "accounts/8b1e4f0c2d6a4e7f9c3b5a1d0e2f4c6b/projects/prj_0d4e7a9b1c2f4e6a8b0c3d5e7f9a1b2c/",
      "manifest_path": "accounts/8b1e4f0c2d6a4e7f9c3b5a1d0e2f4c6b/projects/prj_0d4e7a9b1c2f4e6a8b0c3d5e7f9a1b2c/deployments/dpl_3f9c2a71d04e4b8c9a6e5d1f2b7c8a90/manifest.json",
      "started_at": 1791820801,
      "finished_at": 1791820839,
      "activated_at": 1791820840,
      "failure": null,
      "spa_fallback": false,
      "source_digest": null,
      "builder_image": null,
      "builder_image_digest": null,
      "build_digest": null,
      "build_artifact_kind": null
    }
  ]
}
```

## Upload a release

- `POST /projects/{name}/deploys` — Upload a release

The body is the archive itself — a gzipped tar of the project, sent raw rather than as a multipart form.

What can go live:

- **A static site.** A folder with an `index.html` at its root and no `package.json`, or a build whose output has an `index.html` in `out`, `.output/public`, `dist`, `build` or `_site`.
- **A Next.js or Nuxt server app, built by Fortly.** Upload the source without `node_modules` (or send `x-fortly-build: force`). The build service builds it and the result runs on the Fortly runtime.

Server output that Fortly's build service did not build, such as a server packed with its own `node_modules` or one you built yourself, is refused. So is any server framework or runtime Fortly cannot build. In both cases the live release stays as it is. See [Builds](https://docs.fortly.com/deploy/builds).

- `name` (string, required) — Path parameter. The project's `name`.
- `x-fortly-cmd` (header, optional) — A start command to record on this release, as its `cmd`. Static sites and releases on the Fortly runtime do not run it; it only matters for releases from the older process runtime. A release must still have a start command from somewhere. When this header is absent, the project's [`cmd` setting](/api/projects#update-a-project) is used, and when that is empty too, the command is read from the files: a `package.json` start script, or `fortly static` for a site. If none of these gives one, the upload is refused with `400`, so you rarely need to send this header.
- `x-fortly-build` (header, optional) — `skip` never builds: what arrived is what runs. `force` builds even when `node_modules` is already there. Absent, or any other value, builds only when dependencies are missing. `off`, `none` and `false` also mean `skip`; `always` also means `force`.

**curl**

```bash
# Source without node_modules: Fortly builds it
tar --exclude=node_modules --exclude=.git -czf release.tgz .

curl -X POST https://api.fortly.com/projects/my-app-ada/deploys \
  -H "Authorization: Bearer $FORTLY_TOKEN" \
  --data-binary @release.tgz

# x-fortly-build: force   build even if node_modules is in the archive
# x-fortly-build: skip    never build; serve what arrived
```

**JavaScript**

```js
import { readFile } from "node:fs/promises";

const res = await fetch("https://api.fortly.com/projects/my-app-ada/deploys", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.FORTLY_TOKEN}`,
    "Content-Type": "application/gzip",
  },
  body: await readFile("release.tgz"),
});
const { deploy, url, building } = await res.json();
```

What the status means:

- **`201 Created`** — nothing needed building, and the release is already live. This is the answer for a static site uploaded ready to serve.
- **`202 Accepted`** — the release is building. The response comes back straight away with `"building": true`; the build continues and narrates itself in the [project's log](https://docs.fortly.com/deploy/logs). Poll [List releases](#list-releases) until this release's `state` is no longer `building`.

```json
{
  "deploy": {
    "id": "dpl_7a1b3c5d7e9f4a2b8c6d0e1f3a5b7c9d",
    "project": "my-app-ada",
    "account_id": "8b1e4f0c2d6a4e7f9c3b5a1d0e2f4c6b",
    "created_at": 1791824400,
    "files": 38,
    "bytes": 51230,
    "cmd": "next start",
    "state": "building",
    "source": "upload",
    "sha": null,
    "repo": null,
    "branch": null,
    "build": "npm ci && npm run build",
    "project_id": "prj_0d4e7a9b1c2f4e6a8b0c3d5e7f9a1b2c",
    "runtime_type": null,
    "execution_engine": null,
    "framework": null,
    "artifact_root": null,
    "manifest_path": null,
    "started_at": null,
    "finished_at": null,
    "activated_at": null,
    "failure": null,
    "spa_fallback": false,
    "source_digest": null,
    "builder_image": null,
    "builder_image_digest": null,
    "build_digest": null,
    "build_artifact_kind": null
  },
  "url": "https://my-app-ada.fortly.com",
  "building": true
}
```

A `201` has the same shape without `building`, and the release's `state` is `live`.

A build that fails, or one whose output cannot be served, leaves the live release exactly where it was. The new release's `state` becomes `failed`, and `failure` says why.

When an upload that needed no build turns out to need a server Fortly cannot run, the refusal comes back on the request itself as `400`. The body includes `unsupported_runtime: true`, which tells it apart from a broken archive. Fix it by deploying something else, not by retrying:

```json
{
  "error": "This upload looks like it needs a server, but it wasn't built through Fortly's own build pipeline, so there's nothing safe to run it with. Deploy your project's source (upload it or link a repository) instead of a pre-built output folder, and Fortly will build and run it for you. Static sites deploy as usual; existing deployments are unaffected.",
  "unsupported_runtime": true
}
```

| Status | When |
| --- | --- |
| `400` | The archive could not be unpacked (it is not a gzipped tar, a path escapes the archive root, or it is over the limits below), or no start command could be found, or what arrived needs a server Fortly cannot run (`unsupported_runtime: true`). `error` says which. |
| `409` | Another release of this project is still building. Wait for it to finish. |
| `413` | The archive is over 256 MB. |

Limits: the upload may be 256 MB, and it may unpack to 2 GB across at most 50,000 files.

## Activate a release

- `POST /projects/{name}/deploys/{id}/activate` — Make an earlier release live again

Makes an earlier release the live one. Nothing is rebuilt: the release is served as it was built when it was made. The edge cache for the project is cleared.

- `name` (string, required) — Path parameter. The project's `name`.
- `id` (string, required) — Path parameter. The release `id`, from [List releases](#list-releases).

**curl**

```bash
curl -X POST https://api.fortly.com/projects/my-app-ada/deploys/dpl_3f9c2a71d04e4b8c9a6e5d1f2b7c8a90/activate \
  -H "Authorization: Bearer $FORTLY_TOKEN"
```

**JavaScript**

```js
const id = "dpl_3f9c2a71d04e4b8c9a6e5d1f2b7c8a90";
const res = await fetch(`https://api.fortly.com/projects/my-app-ada/deploys/${id}/activate`, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.FORTLY_TOKEN}` },
});
const { deploy } = await res.json();
```

Answers `200` with the [release](#the-release-object), now `live`:

```json
{
  "deploy": {
    "id": "dpl_3f9c2a71d04e4b8c9a6e5d1f2b7c8a90",
    "project": "my-app-ada",
    "state": "live",
    "activated_at": 1791828000,
    "cmd": "next start",
    "created_at": 1791820800
  }
}
```

The `deploy` object is complete; it is shortened here.

| Status | When |
| --- | --- |
| `404` | No release with this id among the project's 200 most recent. |
| `410` | The release is `building`, `failed` or `gone`, or its files have been cleaned up, so it cannot be made live. |

## List releases across projects

- `GET /deploys` — Recent releases across all your projects

The 20 most recent releases of each project you can access, merged and sorted newest first, up to 100 in all. Each item is a [release object](#the-release-object); its `project` field says which project it belongs to.

**curl**

```bash
curl https://api.fortly.com/deploys \
  -H "Authorization: Bearer $FORTLY_TOKEN"
```

**JavaScript**

```js
const res = await fetch("https://api.fortly.com/deploys", {
  headers: { Authorization: `Bearer ${process.env.FORTLY_TOKEN}` },
});
const { deploys } = await res.json();
```

```json
{
  "deploys": [
    { "id": "dpl_7a1b3c5d7e9f4a2b8c6d0e1f3a5b7c9d", "project": "my-app-ada", "state": "live", "created_at": 1791824400 },
    { "id": "dpl_3f9c2a71d04e4b8c9a6e5d1f2b7c8a90", "project": "my-app-ada", "state": "superseded", "created_at": 1791820800 }
  ]
}
```

Items are shortened here; each is a complete release object.
