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.
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; for rollbacks, Releases.
/ projects/ {name}/ deploysIts releases, newest first/ projects/ {name}/ deploysUpload a release/ projects/ {name}/ deploys/ {id}/ activateMake an earlier release live again/ deploysRecent releases across all your projectsThe release object
idstringdpl_ followed by 32 hex characters.projectstringname.account_idstringcreated_atintegerfilesintegerbytesintegercmdstringfortly static for a site served as files.statestringbuilding, ready, live, superseded (was live, replaced by a newer one), failed, or gone (cleaned up).sourcestring | nullupload or github.shastring | nullrepo and branch likewise.buildstring | nullnpm ci && npm run build. null when nothing was built.project_idstring | nullprj_….runtime_typestring | nullstatic or fortly.frameworkstring | nullNext.js.started_atinteger | nullfinished_atinteger | nullactivated_atinteger | nullfailurestring | nullspa_fallbackbooleanindex.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
/ projects/ {name}/ deploysIts releases, newest firstReturns the project's 50 most recent releases, newest first.
namestringRequiredname.curl https://api.fortly.com/projects/my-app-ada/deploys \
-H "Authorization: Bearer $FORTLY_TOKEN"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();{
"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
/ projects/ {name}/ deploysUpload a releaseThe 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.htmlat its root and nopackage.json, or a build whose output has anindex.htmlinout,.output/public,dist,buildor_site. - A Next.js or Nuxt server app, built by Fortly. Upload the source without
node_modules(or sendx-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.
namestringRequiredname.x-fortly-cmdheadercmd. 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 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-buildheaderskip 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.# 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 arrivedimport { 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. Poll List releases until this release'sstateis no longerbuilding.
{
"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:
{
"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
/ projects/ {name}/ deploys/ {id}/ activateMake an earlier release live againMakes 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.
namestringRequiredname.idstringRequiredid, from List releases.curl -X POST https://api.fortly.com/projects/my-app-ada/deploys/dpl_3f9c2a71d04e4b8c9a6e5d1f2b7c8a90/activate \
-H "Authorization: Bearer $FORTLY_TOKEN"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, now live:
{
"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
/ deploysRecent releases across all your projectsThe 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; its project field says which project it belongs to.
curl https://api.fortly.com/deploys \
-H "Authorization: Bearer $FORTLY_TOKEN"const res = await fetch("https://api.fortly.com/deploys", {
headers: { Authorization: `Bearer ${process.env.FORTLY_TOKEN}` },
});
const { deploys } = await res.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.