ShipSite API

Instant static site hosting. Publish files and get a live URL at <slug>.shipsite.co.

Inspired by and API-compatible with here.now — whose elegant design made instant static hosting accessible to agents everywhere. ShipSite builds on that foundation with additional features: version history, instant rollback, ZIP export, and built-in analytics.

Quick Start

No account needed. Create an anonymous site in three steps:

1. Create

curl -sS https://shipsite.co/api/v1/publish \
  -H "content-type: application/json" \
  -d '{"files":[{"path":"index.html","size":42,"contentType":"text/html"}]}'

Returns presigned upload URLs and a finalizeUrl.

2. Upload

# PUT each file to its presigned URL
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: text/html" \
  --data-binary "<h1>Hello world</h1>"

3. Finalize

curl -sS -X POST "$FINALIZE_URL" \
  -H "content-type: application/json" \
  -d '{"versionId":"$VERSION_ID","claimToken":"$CLAIM_TOKEN"}'

Your site is now live at https://<slug>.shipsite.co/

Anonymous sites expire after 24 hours. Authenticate to make them permanent.

Authentication

Passwordless via email OTP. Two steps to get an API key:

Request a code

POST/api/auth/agent/request-code

{"email": "you@example.com"}

Verify and get your key

POST/api/auth/agent/verify-code

{"email": "you@example.com", "code": "ABCD-EFGH"}

Returns: {"apiKey": "sk_..."}

Save your API key immediately. It is shown only once and cannot be recovered.

Use it in requests:

Authorization: Bearer sk_your_api_key_here

Create a Site

POST/api/v1/publish

Request body:

{
  "files": [
    {"path": "index.html", "size": 1024, "contentType": "text/html"},
    {"path": "style.css", "size": 512, "contentType": "text/css", "hash": "sha256:..."}
  ],
  "viewer": {
    "title": "My Project",
    "description": "A demo site",
    "ogImagePath": "og.png"
  }
}

Response:

{
  "slug": "bright-canvas-a7k2",
  "siteUrl": "https://bright-canvas-a7k2.shipsite.co/",
  "upload": {
    "versionId": "01ABC...",
    "uploads": [
      {"path": "index.html", "method": "PUT", "url": "https://...", "headers": {"Content-Type": "text/html"}}
    ],
    "skipped": ["style.css"],
    "finalizeUrl": "https://shipsite.co/api/v1/publish/bright-canvas-a7k2/finalize",
    "expiresInSeconds": 3600
  },
  "claimToken": "abc123...",
  "claimUrl": "https://shipsite.co/claim?slug=bright-canvas-a7k2&token=abc123...",
  "expiresAt": "2026-03-30T12:00:00.000Z",
  "anonymous": true,
  "warning": "IMPORTANT: Save the claimToken and claimUrl. They are returned only once."
}
The claimToken, claimUrl, expiresAt, anonymous, and warning fields only appear for anonymous (unauthenticated) publishes. Save the claimToken — you need it to finalize, update, or claim the site.
Provide a hash (SHA-256) for each file to enable incremental deploys. Files with matching hashes are skipped and copied server-side.

Viewer field limits: title max 200 chars, description max 500 chars, ogImagePath max 500 chars.

Upload Files

PUT each file to its presigned URL from the create/update response. Uploads go directly to S3 — the server never proxies file bytes.

curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: text/html" \
  --data-binary @index.html

Presigned URLs expire after 1 hour. Use the uploads/refresh endpoint to get new ones.

Refresh upload URLs

POST/api/v1/publish/:slug/uploads/refresh

Requires auth. Returns fresh presigned URLs for the pending version.

Finalize

POST/api/v1/publish/:slug/finalize

{"versionId": "01ABC...", "claimToken": "abc123..."}

Activates the uploaded version. The site is now live. For authenticated sites, use your API key. For anonymous sites, include the claimToken from the create response.

Markdown files (.md, .markdown) are automatically converted to styled HTML during finalization.

Response:

{
  "success": true,
  "slug": "bright-canvas-a7k2",
  "siteUrl": "https://bright-canvas-a7k2.shipsite.co/",
  "previousVersionId": null,
  "currentVersionId": "01ABC..."
}

Update a Site

PUT/api/v1/publish/:slug

{"files": [...], "claimToken": "..." }

Same format as create. For authenticated sites, use your API key. For anonymous sites, include the claimToken.

Get Site Details

GET/api/v1/publish/:slug
Authorization: Bearer sk_...

Requires auth. Returns site metadata, current/pending version IDs, and the file manifest.

List Sites

GET/api/v1/publishes

Returns all sites for the authenticated user.

Update Metadata

PATCH/api/v1/publish/:slug/metadata

{
  "viewer": {"title": "New Title", "description": "Updated desc"},
  "ttlSeconds": 86400,
  "password": "secret"
}

Set ttlSeconds to auto-expire the site. Set to null to remove expiry.

Password protection

Set password to a non-empty string to lock the site behind a passcode. Visitors see a password prompt and must enter the correct code before any content is served. The browser remembers the password for 24 hours via a cookie.

Set password to null to remove protection:

// Lock
{"password": "my-secret-code"}

// Unlock
{"password": null}

Passwords are hashed with scrypt before storage and never stored in plain text.

API clients and agents can bypass the browser prompt by sending the password in a header:

x-site-password: my-secret-code

Claim Anonymous Site

POST/api/v1/publish/:slug/claim

{"claimToken": "abc123..."}

Converts an anonymous site to your account. Removes the 24-hour expiry. Requires auth + the claim token from the original create response.

Duplicate a Site

POST/api/v1/publish/:slug/duplicate

{"viewer": {"title": "New Title", "description": "Optional override"}}

Server-side copy to a new slug. Files are copied in S3 without re-upload. Optionally override the viewer title and description on the copy.

Response:

{
  "slug": "new-slug-xyz",
  "siteUrl": "https://new-slug-xyz.shipsite.co/",
  "sourceSlug": "bright-canvas-a7k2",
  "status": "active",
  "currentVersionId": "01GHI...",
  "filesCount": 5
}

Delete a Site

DELETE/api/v1/publish/:slug

Permanently deletes the site and all its files.

Version History ShipSite

GET/api/v1/publish/:slug/versions

List all deployed versions with timestamps and file counts. Each publish or update creates a new version.

{
  "slug": "bright-canvas-a7k2",
  "versions": [
    {"versionId": "01DEF...", "createdAt": "2026-03-29T...", "fileCount": 5, "current": true},
    {"versionId": "01ABC...", "createdAt": "2026-03-28T...", "fileCount": 3, "current": false}
  ]
}

Rollback ShipSite

POST/api/v1/publish/:slug/rollback

{"versionId": "01ABC..."}

Instantly revert to any previous version. No re-upload needed — the files are already in S3.

{
  "success": true,
  "slug": "bright-canvas-a7k2",
  "siteUrl": "https://bright-canvas-a7k2.shipsite.co/",
  "previousVersionId": "01DEF...",
  "currentVersionId": "01ABC..."
}

Download as ZIP ShipSite

GET/api/v1/publish/:slug/download

Download the current version of a site as a ZIP archive. Useful for backups or migrating to another host.

curl -OJ -H "Authorization: Bearer sk_..." \
  https://shipsite.co/api/v1/publish/bright-canvas-a7k2/download

Analytics ShipSite

GET/api/v1/publish/:slug/analytics?days=30

View traffic analytics for your site. Returns daily breakdowns of views, unique visitors, top paths, and top referrers.

Query params: days (1–90, default 30)

{
  "slug": "bright-canvas-a7k2",
  "period": {"startDate": "2026-02-27", "endDate": "2026-03-29", "days": 30},
  "daily": [
    {
      "date": "2026-03-29",
      "views": 142,
      "uniqueVisitors": 89,
      "topPaths": [["/", 98], ["/about.html", 44]],
      "topReferrers": [["google.com", 34], ["twitter.com", 12]]
    }
  ],
  "totals": {"views": 4210, "uniqueVisitors": 1893}
}
Analytics data is retained for 90 days. IP addresses are hashed for privacy and never stored in plain text.

Share via Email ShipSite

POST/api/v1/publish/:slug/share

{
  "senderName": "Kent",
  "recipientName": "Alice",
  "recipientEmail": "alice@example.com",
  "message": "Check out my new project!"
}

Send a branded email to someone with a link to your site. The recipient receives a styled email with your name, an optional personal message, and a prominent link to the site.

Request body:

FieldRequiredDescription
senderNameYesYour name (max 64 chars)
recipientNameYesRecipient's name (max 64 chars)
recipientEmailYesValid email address (max 254 chars)
messageNoOptional personal note (max 500 chars)

Response:

{"success": true, "slug": "bright-canvas-a7k2", "recipientEmail": "alice@example.com"}
Requires auth. The site must be active (not pending or deleted). If the recipient has unsubscribed from ShipSite emails, the API returns 422 and the email is not sent. Rate limited to 10 shares per hour per user.

Limits

FeatureAnonymousAuthenticated
Max file size25 MB5 GB
Max files per site20500
Site expiry24 hoursPermanent
Rate limit5/hour per IP200/hour
Version historyFull
RollbackAny version
ZIP downloadCurrent version
Analytics90-day retention
Auth rate limit10 requests/15 min per IP, 5 OTP attempts/15 min per email
Share rate limit10 shares/hour per user

Markdown Support

Markdown files (.md, .markdown, .mdown, .mkd) are automatically converted to styled HTML during finalization. The original .md file is preserved and a new .html version is added to the manifest. If no index.html exists, the first root-level .md file is converted to index.html so it serves at /.

Serving Rules

When a request hits <slug>.shipsite.co/:

  1. If index.html exists, serve it
  2. If only one file, serve that file
  3. Otherwise, show a directory listing

Subdirectory paths resolve to <path>/index.html if present.