Deploys

A deploy is a single PUT to the project's domain, with the project packed as a gzipped tarball in the request body. The response is a live stream of newline-delimited JSON describing the publish as it happens: upload, propagation to the CDN, certificate work, and the final result.

PUT /:domain

Request

PUT /my-site.surge.sh HTTP/1.1
Host: surge.surge.sh
Authorization: Basic dG9rZW46…
Content-Type: application/gzip
Accept: application/x-ndjson
Transfer-Encoding: chunked
version: 1.0.0
file-count: 42
project-size: 123456

The body is a gzipped tar archive of the project directory. Notes on the format the official clients produce:

  • Entries are prefixed with a single top-level directory (the server unpacks with the first path segment stripped), so site/index.html becomes /index.html.
  • Files matched by the default ignore rules (.git, dotfiles, node_modules, bower_components) and by the project's .surgeignore are excluded before packing.

Deploy headers

Metadata about the deploy travels in request headers:

Header Description
version Your client's version string.
file-count Number of files in the archive—used for progress totals and limits.
project-size Total bytes—used for progress and limits.
message A deploy message, shown in the revision list (surge list <domain>).
stage Set to make this a preview deploy: uploaded and given a revision preview URL, but not cut over to production.
add / rem Comma-separated emails of collaborators to add or remove as part of the deploy.
ssl Signals the project should force HTTPS.

The response stream

The server responds 200 and begins streaming JSON objects, one per line. Each object has a type. In a typical successful deploy you'll see, in order:

{ "type": "progress", "id": "upload", "written": 51200, "total": 123456 }
{ "type": "progress", "id": "cdn", "written": 12, "total": 42, "file": "index.html" }
{ "type": "cert", "data": { "issuer": "Let's Encrypt", "altnames": ["my-site.surge.sh"], "expiresInWords": "89 days" } }
{ "type": "subscription", "data": null }
{ "type": "info", "urls": [...], "instances": [...], "metadata": {...}, "config": {...} }
Type Meaning
progress Progress through a phase. id is upload, cdn, or encrypt; the final message of a phase carries "end": true.
cert Certificate details, when certificate work happens during the deploy.
subscription The project's plan, if any.
collect Payment required—carries the plan and a checkout URL. The deploy will not complete until the account subscribes.
info The success marker. Contains the deploy result: urls (where the project is reachable, including the revision preview URL), instances (the edge nodes serving it), and metadata (revision id, message, file counts, sizes).

A stream that ends without an info line means the deploy did not succeed.

Failures

Failures before the stream starts arrive as plain HTTP errors, with the explanation in the reason response header:

Status Reason
401 Invalid or expired token.
403 No permission to publish to this domain; or rate limited (30 deploys per domain, 60 per account, 240 per IP, per 15 minutes—halved for unverified accounts); or the project exceeds the limits (10,100 files / 450 MB).
422 The domain is invalid.

Revisions and previews

Every deploy creates a revision, identified by a millisecond timestamp, and each revision gets its own permanent preview URL. A normal deploy is cut over to production immediately; a deploy with the stage header stops after upload, leaving production untouched until you cut it over.

Revisions are managed with a small family of endpoints—list, rollback, rollfore, cutover, discard—documented in the Endpoint Reference, and correspond directly to the CLI's revision commands.

Certificates over the same stream

Two more endpoints speak the same NDJSON protocol:

  • PUT /:domain/encrypt — provision a managed SSL certificate for a custom domain. No body. Streams progress (id encrypt), cert, and finally info.
  • PUT /:domain/ssl — upload a custom PEM bundle (Content-Type: application/x-pem-file, raw body). The server validates the bundle; 202 with a msg object on success, 400 if the PEM is rejected. Requires a paid plan.