API reference
Deploy a drop
One endpoint for your HTML, files, or static build. Send what you made. Get a live URL back.
On this page
https://ship.page/deployAuth optional
Request body
The Content-Type header selects the format. All three return
the same response envelope.
HTML
Send the file itself as the body. It becomes index.html at
the root of your drop.
curl -X POST https://ship.page/deploy \
-H "Content-Type: text/html" \
--data-binary @index.html
Content-Type text/html
JSON files map
Each key in files becomes a path. Text files are plain
strings; binary assets use an object with base64 encoding.
Body formats covers
binary assets.
curl -X POST https://ship.page/deploy \
-H "Content-Type: application/json" \
-d '{
"files": {
"index.html": "<!doctype html><h1>It works</h1>",
"styles.css": "h1 { color: #264fc5; }"
}
}'
Content-Type application/json
Zip upload
Send a ZIP of your static build. A single enclosing folder, such as
dist/, is stripped automatically — your
index.html still ends up at /.
curl -X POST https://ship.page/deploy \
-H "Content-Type: application/zip" \
--data-binary @dist.zip
Content-Type application/zip
Headers
Content-TypeBody format-
text/html,application/json, orapplication/zip. AuthorizationOptional-
Use
Bearerwith ansp_API key or Firebase ID token to attach the drop to your account. Authentication guide X-Ship-PasswordPro / Team-
Send
encodeURIComponent(password)with your Bearer credential. Passwords must be 8–128 Unicode characters, at most 512 UTF-8 bytes. Don’t put the password in the URL or files map.
Password protection details
Omitting the header on a live named redeploy preserves its password. An empty value is invalid; it does not remove protection. When recreating an expired or purged named drop, send the password again.
The password is never returned. See the full password protection reference below, or password management to rotate or remove it later.
For a shorter lifetime or stable alias, see
query parameters: ttl and name.
Response
A successful request returns 200 OK and a JSON body. The URL is
live when the response arrives.
{
"slug": "vast-juice-c2dse",
"url": "https://vast-juice-c2dse.shipped.run/",
"files": ["index.html"],
"plan": "free",
"password_protected": false,
"expires_at": "2026-06-17T18:00:00.000Z",
"claim_token": "spc_7xk3m9q2vj8n4p1w6z5y0r2t"
}
urlstring- The shareable URL, on the drop’s own subdomain.
-
slug/filesstring / string[] - The drop identifier and the file paths deployed.
planstring- The plan used for this deployment.
password_protectedboolean- Whether the drop requires a password to view.
expires_attimestamp | null- The expiry time, or
nullfor a drop without expiry. claim_tokenAnonymous only- Returned once. Anyone holding it can attach the drop to an account via POST /drops/<slug>/claim. Authenticated deploys are already owned and don’t return one.
Errors & retries
Errors return an error message and a stable code.
Use the HTTP status to decide what to do next.
400Bad request-
The body didn’t parse or breaks a rule — malformed JSON
filesmap, an invalid zip, a bad path, an out-of-rangettl, or more files than your plan’s per-request cap. 401Unauthorized-
?name=without a credential — named drops and password protection need an API key. Plain anonymous deploys never see this. 402Payment required-
?name=or password protection with a valid key but no active subscription — attlpast your plan’s cap is a 400 instead. See Limits & plans. 413Payload too large- The zip exceeds your plan’s cap (25 MB free, 100 MB subscribed) or decompresses past 4× that — or a non-zip body tops 10 MB on any plan.
429Rate limited- Too many deploys in a short window — 10/min per IP anonymous and free, 120/min per account on paid plans. Every 429 carries Retry-After: 60 — back off and retry.
Every error answers with
{ "error": "<message>", "code": "<stable-code>" } —
the full catalogue lives in Errors.
Password protection (Pro / Team)
Send X-Ship-Password with
encodeURIComponent(password) and your Bearer credential to
protect a new drop or rotate a named drop’s password. Use 8–128 Unicode
characters, at most 512 UTF-8 bytes. The header works with all three body
formats; do not put the password in the URL or JSON files map.
await fetch("https://ship.page/deploy", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "text/html",
"X-Ship-Password": encodeURIComponent(password),
},
body: html,
});
Setting or rotating a password requires an active paid Pro/Team
subscription: missing auth returns 401, an ineligible plan 402, and
unavailable billing 503. An empty password is invalid, not a request to
remove protection. The response includes password_protected;
the password is never returned. Omitting the header on a live named redeploy
preserves existing protection, as does appending files. Recreating an
expired or purged named drop is a new deploy: send the password again. A
top-level JSON password field is rejected, and append rejects password
input; use the owner endpoints to rotate it. The
/_ship/ namespace is reserved and cannot be uploaded.
Visitors see a 401 password gate until they unlock on the drop’s own origin; the host-only session lasts 12 hours. All protected files bypass caching. Changing a password, locking a formerly public drop, or reuploading content cannot recall copies already downloaded or cached. See password management and browser unlock.
Last updated Jun 12, 2026