DropCat API
Static site hosting in one curl. Deploy a zip, get a live URL.
Quick Start
Create a directory with an index.html, zip it, deploy it:
mkdir mysite && echo "<h1>Hello!</h1>" > mysite/index.html cd mysite && zip -r ../mysite.zip . curl -F "site=@../mysite.zip" https://api.drop.cat/deploy
Response:
{
"siteId": "gentle-amber-fox",
"url": "https://gentle-amber-fox.dropcat.site",
"customDomain": null,
"screenshot": "https://preview.drop.cat/ac654c827dff3918e33056002d757e2b.jpg",
"expiresAt": "2026-02-19T12:00:00.000Z",
"files": 1,
"sizeBytes": 22,
"key": "sk_a1b2c3d4e5f6...",
"account": {
"tier": "anonymous",
"email": null,
"sites": 1,
"sitesLimit": 3,
"expiryDays": 7
},
"hint": "Your site is live at https://gentle-amber-fox.dropcat.site — ..."
} POST /account/email.Deploy a Site
POST /deploy
Upload a ZIP or TAR.GZ archive containing your static site. The archive root must contain an index.html. Only static web files are allowed (HTML, CSS, JS, images, fonts, media).
Request
# Anonymous deploy (returns a new API key)
curl -F "[email protected]" https://api.drop.cat/deploy
# Authenticated deploy (uses existing account)
curl -F "[email protected]" \
-H "Authorization: Bearer sk_yourkey" \
https://api.drop.cat/deploy
# Key as form field (alternative to header)
curl -F "[email protected]" \
-F "key=sk_yourkey" \
https://api.drop.cat/deploy
# With email — deploys + sends confirmation email
curl -F "[email protected]" \
-F "[email protected]" \
https://api.drop.cat/deploy Response 201
{
"siteId": "gentle-amber-fox",
"url": "https://gentle-amber-fox.dropcat.site",
"customDomain": null,
"screenshot": "https://preview.drop.cat/ac654c827dff3918e33056002d757e2b.jpg",
"expiresAt": "2026-02-19T12:00:00.000Z",
"files": 12,
"sizeBytes": 48210,
"key": "sk_...",
"account": {
"tier": "anonymous",
"email": null,
"sites": 1,
"sitesLimit": 3,
"expiryDays": 7
},
"hint": "Your site is live at https://gentle-amber-fox.dropcat.site — ..."
} | Field | Description |
|---|---|
siteId | Unique site identifier, used in the URL and for management |
url | Live URL where the site is accessible |
screenshot | Screenshot URL — visual preview of the deployed site |
expiresAt | When the site will be automatically deleted |
files | Number of files extracted from archive |
sizeBytes | Total size of extracted files in bytes |
key | API key — only returned on anonymous deploy, shown once |
account | Current account state: tier, email, site count, limits, expiry |
hint | Suggested next action (useful for AI agents) |
email sends a confirmation email but does not upgrade the account inline. The deploy proceeds at anonymous tier. The account upgrades when the user clicks the confirmation link.Redeploy / Update a Site
POST /deploy/:siteId
Replace a site's files with a new archive. Requires authentication. The site ID stays the same, the version increments, and expiry resets.
curl -F "[email protected]" \
-H "Authorization: Bearer sk_yourkey" \
https://api.drop.cat/deploy/gentle-amber-fox Response 200
{
"siteId": "gentle-amber-fox",
"url": "https://gentle-amber-fox.dropcat.site",
"customDomain": "cool.app",
"screenshot": "https://preview.drop.cat/ac654c827dff3918e33056002d757e2b.jpg",
"version": 2,
"files": 15,
"sizeBytes": 61440,
"account": {
"tier": "free",
"email": "[email protected]",
"sites": 2,
"sitesLimit": 5,
"expiryDays": 30
},
"hint": "Your site is live at https://gentle-amber-fox.dropcat.site — ..."
} List Sites
GET /sites
List all sites owned by your account.
curl -H "Authorization: Bearer sk_yourkey" \
https://api.drop.cat/sites Response 200
{
"sites": [
{
"siteId": "gentle-amber-fox",
"url": "https://gentle-amber-fox.dropcat.site",
"customDomain": null,
"files": 12,
"sizeBytes": 48210,
"version": 1,
"createdAt": "2026-02-12T12:00:00.000Z",
"expiresAt": "2026-02-19T12:00:00.000Z"
}
]
} Get Site Details
GET /sites/:siteId
Get details for a specific site, including its deploy history.
curl -H "Authorization: Bearer sk_yourkey" \
https://api.drop.cat/sites/gentle-amber-fox Response 200
{
"siteId": "gentle-amber-fox",
"url": "https://gentle-amber-fox.dropcat.site",
"customDomain": "cool.app",
"files": 15,
"sizeBytes": 61440,
"version": 2,
"createdAt": "2026-02-12T12:00:00.000Z",
"expiresAt": "2026-02-19T12:00:00.000Z",
"deploys": [
{ "version": 1, "files": 12, "sizeBytes": 48210, "createdAt": "2026-02-12T12:00:00.000Z" },
{ "version": 2, "files": 15, "sizeBytes": 61440, "createdAt": "2026-02-12T14:30:00.000Z" }
]
} Custom Domains
Attach a custom domain by including a CNAME file at the root of your archive containing a single hostname:
echo "cool.app" > CNAME
zip -r site.zip index.html CNAME
curl -F "[email protected]" \
-H "Authorization: Bearer sk_yourkey" \
https://api.drop.cat/deploy The CNAME file is the source of truth for the site's custom domain. Each deploy applies it as follows:
- Present →
customDomainis set or updated to that hostname. - Absent →
customDomainis cleared.
The CNAME file is read for its value and removed before upload — it is never served. The files count in the response reflects only what is actually served. The site's siteId and dropcat URL never change; only customDomain does. You still need to point your DNS at dropcat for the custom hostname to resolve.
Errors: INVALID_DOMAIN (bad hostname), DOMAIN_BLOCKED (reserved/blocked label), DOMAIN_TAKEN (already attached to another site).
Delete a Site
DELETE /sites/:siteId
Permanently delete a site and all its files.
curl -X DELETE \
-H "Authorization: Bearer sk_yourkey" \
https://api.drop.cat/sites/gentle-amber-fox Response 200
{
"message": "Site deleted",
"siteId": "gentle-amber-fox"
} Account Status
GET /account
View your current account status, tier, limits, and site count.
curl -H "Authorization: Bearer sk_yourkey" \
https://api.drop.cat/account Response 200
{
"tier": "anonymous",
"email": null,
"sites": 1,
"sitesLimit": 3,
"expiryDays": 7,
"createdAt": "2026-02-12T12:00:00.000Z"
} | Field | Description |
|---|---|
tier | anonymous or free |
email | Confirmed email, or null |
sites | Number of active sites |
sitesLimit | Max sites for your tier |
expiryDays | How long new sites last before auto-deletion |
createdAt | When the account was created |
Attach Email
POST /account/email
Start the email confirmation process. A confirmation link is sent to the provided email. Once confirmed, your account upgrades to the free tier (more sites, longer expiry, key recovery).
curl -X POST \
-H "Authorization: Bearer sk_yourkey" \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]"}' \
https://api.drop.cat/account/email Response 200
{
"message": "Confirmation email sent. Check your inbox."
} Confirm Email
GET /account/confirm?token=...
Called by the static confirmation page at /confirm after the user clicks the link in their email. Validates the token, upgrades the account to the free tier, and extends all site expiry dates.
Response 200
{ "message": "Email confirmed. Your account has been upgraded." } On invalid or expired tokens, returns a 400 error.
Recover Account
POST /account/recover
Lost your API key? If you confirmed an email, request a new key. A recovery email is sent with a fresh key. The old key is invalidated immediately.
curl -X POST \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]"}' \
https://api.drop.cat/account/recover Response 200
{
"message": "If this email exists, a new key has been sent."
} Authentication
Most endpoints require an API key. Pass it in one of two ways:
Authorization Header (recommended)
curl -H "Authorization: Bearer sk_yourkey" https://api.drop.cat/sites
Form Field
curl -F "[email protected]" -F "key=sk_yourkey" https://api.drop.cat/deploy
How Keys Work
When you deploy anonymously, the response includes a key field starting with sk_. It is shown only once and cannot be recovered without a confirmed email on file.
Keys are hashed server-side. We never store your plaintext key.
Public Endpoints (no key required)
POST /deploy | Anonymous deploy (creates account + returns key) |
POST /account/email | Attach email (key passed in body) |
GET /account/confirm | Email confirmation link |
POST /account/recover | Key recovery |
Authenticated Endpoints
POST /deploy | Deploy with existing key |
POST /deploy/:siteId | Redeploy |
GET /account | Account status |
GET /sites | List sites |
GET /sites/:siteId | Site details |
DELETE /sites/:siteId | Delete site |
Tiers
Accounts start as anonymous and upgrade to free after confirming an email.
| Anonymous | Free (confirmed email) | |
|---|---|---|
| Sites | 3 | 5 |
| Expiry | 7 days | 30 days (renewable on redeploy) |
| Key recovery | Not possible | Via email |
| Deploy rate | 5/hr per IP | 10/hr per account |
Upgrading extends all existing site expiry dates to 30 days.
Rate Limits & Quotas
| Action | Limit |
|---|---|
| Deploy (anonymous, per IP) | 5 per hour |
| Deploy (authenticated, per account) | 10 per hour |
| All API calls (per IP) | 120 per hour |
| Upload size (compressed) | 50 MB |
| Upload size (uncompressed) | 50 MB |
| Files per archive | 1,000 |
Rate-limited requests return 429 with a Retry-After header indicating seconds until the next allowed request. The response body also includes a retryAfter field:
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Wait a moment and try again.",
"retryAfter": 120
}
} Error Codes
All errors follow this structure:
{
"error": {
"code": "ERROR_CODE",
"message": "Description with next steps"
}
} | Code | Status | Meaning |
|---|---|---|
UNAUTHORIZED | 401 | Invalid or missing API key |
FORBIDDEN | 403 | API key does not own this site |
NOT_FOUND | 404 | Site not found |
ENDPOINT_NOT_FOUND | 404 | Unknown URL path |
RATE_LIMITED | 429 | Too many requests (includes Retry-After header) |
ANON_DEPLOY_LIMITED | 429 | Anonymous deploy limit hit — use your API key or wait |
TOO_LARGE | 413 | Archive exceeds 50 MB compressed size limit |
SITE_LIMIT_REACHED | 400 | Max sites for your tier — confirm email to increase |
INVALID_ARCHIVE | 400 | Bad or corrupted archive — upload a .zip or .tar.gz |
NO_INDEX | 400 | Archive missing index.html at root — if using a build tool, zip the output directory (dist/ or build/) |
TOO_MANY_FILES | 400 | Archive has more than 1,000 files |
PATH_TRAVERSAL | 400 | File paths must be relative and within the archive root |
FORBIDDEN_EXTENSION | 400 | Only static web files allowed (HTML, CSS, JS, images, fonts, media) |
INVALID_DOMAIN | 400 | CNAME file contains an invalid hostname |
DOMAIN_BLOCKED | 400 | CNAME hostname uses a reserved or blocked label |
DOMAIN_TAKEN | 409 | Custom domain is already attached to another site |
INVALID_EMAIL | 400 | A valid email address is required |
INVALID_EMAIL_FORMAT | 400 | Email format is invalid |
EMAIL_TAKEN | 409 | Email linked to another account — use that key or recover via POST /account/recover |
EMAIL_ALREADY_CONFIRMED | 409 | Email already confirmed on another account |
INVALID_TOKEN | 400 | Confirmation token invalid or expired — request a new one via POST /account/email |
INTERNAL_ERROR | 500 | Server error — try again later |