API
Everything the UI does goes through a small HTTP API, built for shell scripts and AI agents. No OAuth, no SDK: a site's edit id and its edit password are enough, and an account can issue a single API token that covers every site it owns.
app.sitebin.io,
the API is an account feature: a site created anonymously (a Drop) has
no API access at all — manage it from its edit page instead, or sign
in and create the site there to script it. Self-hosted community
instances have no accounts and no gate — every example below works
anonymously there.Authentication
There are two ways to authenticate, and which one you want depends on whether you are managing one site or many.
Edit password — one site
Every request identifies the site by its edit id (in
the URL path) and proves ownership with the edit
password — sent as an X-Edit-Password header, or
via HTTP Basic auth. Both come from the site's claim ticket. This is
the only method a self-hosted community instance has, and it needs no
account at all.
Account API token — every site you own (Enterprise)
An account can issue API tokens in the dashboard at
/account, optionally named so you can tell them apart.
Send one as a bearer token:
curl -H "Authorization: Bearer $TOKEN" \
-F "zip=@dist.zip" https://app.sitebin.io/api/sites
A token does two things an edit password cannot. It creates sites owned by your account without a browser session — the reason it exists, since publishing a build artifact from CI or an AI agent has no browser to sign in with. And it replaces the edit password on every site that account already owns, so a pipeline that manages twenty sites carries one secret instead of twenty.
A token is shown once, at creation. Sitebin stores only its hash, so a lost token is replaced rather than recovered — revoke it in the dashboard and issue another.
In the browser, a signed-in owner's edit page reaches these same
routes through the account session, without a password. That session
counts only for Sitebin's own edit page — requests must carry
X-Sitebin-Session: 1 from the same origin, which no other
site can send — so it is not a credential for scripts: use a token.
The examples below use https://app.sitebin.io as the
host (swap in your own base domain if you self-host) and assume
$EDIT_ID and $PW hold a site's edit id and
edit password. Anywhere you see -H "X-Edit-Password: $PW"
you can substitute -H "Authorization: Bearer $TOKEN", as
long as the token's account owns the site.
Create a site
POST multipart form data. Repeat the files field freely;
create folders by putting a path in the part's filename.
curl -F "files=@index.html" -F "files=@app.js;filename=js/app.js" \
https://app.sitebin.io/api/sites
Create a viewer site with settings
Settings can be supplied at creation time as ordinary form fields — here a name, the mode, a view password, an expiry, WebDAV, and a custom domain.
curl -F "name=Q3 report" -F "mode=viewer" -F "view_password=sesame" \
-F "expires_at=2026-12-31T23:59:59Z" -F "webdav=true" \
-F "domain=docs.client.com" \
-F "files=@report.pdf" \
https://app.sitebin.io/api/sites
Upload a zip
A zip field is unpacked server-side into the new site.
curl -F "zip=@site.zip" https://app.sitebin.io/api/sites
Read site settings
A GET on the site returns its settings, file list, and usage.
curl -H "X-Edit-Password: $PW" https://app.sitebin.io/api/sites/$EDIT_ID
Its locked field is null unless the
operator has locked the site as
evidence of abuse; then it is {"at": "…", "reason": "…"}
(reason only when one was given) and its
files list is empty. This GET is the one route that answers a locked site: every
other route — writes, downloads, file content, folder listings — answers
403 "This site is locked by the operator", and the site
itself answers 410 "Site suspended" to visitors. A token or
session of a suspended account is refused outright.
Every upload is scanned for known phishing and scam kits
before it goes live. When one matches on an anonymous site or an
untrusted plan (Free on the hosted service), the site is locked on the
spot and the write answers 403 "This site is locked by the
operator: held for review — …"; a creation that is held answers
403 too, without an edit password. The rest of that upload
is not written.
Update settings
PUT a JSON object with any subset of settings;
"expires_at": null clears the expiry.
name is an optional private label of up to 60
characters, so a list of sites can be told apart: it appears in your
account's site list and on the edit page, never to visitors, and
"name": "" removes it. Every response carries it.
cache is the browser-cache policy — auto,
revalidate, hour, day,
week, year or off; see
Browser caching.
curl -X PUT -H "X-Edit-Password: $PW" -H "Content-Type: application/json" \
-d '{"name":"Client docs","mode":"viewer","entry_file":"report.pdf","webdav_enabled":true}' \
https://app.sitebin.io/api/sites/$EDIT_ID
Manage files
Add files
curl -X POST -H "X-Edit-Password: $PW" -F "files=@new.html;filename=new.html" \
https://app.sitebin.io/api/sites/$EDIT_ID/files
Replace all files
?replace=true swaps out the site's entire content — here
with a zip, in one request. The old files go only once the new ones
are all in and within the site's limits, so a replace that fails, is
cut off or does not fit leaves the site exactly as it was. A second
replace of the same site while one is running is refused with
409.
curl -X POST -H "X-Edit-Password: $PW" -F "zip=@all.zip" \
"https://app.sitebin.io/api/sites/$EDIT_ID/files?replace=true"
List a folder
One folder at a time — its sub-folders first, then its files with
their sizes. path is the folder inside the site; leave it
out for the top. This is what the edit page's file browser uses.
curl -H "X-Edit-Password: $PW" \
"https://app.sitebin.io/api/sites/$EDIT_ID/dir?path=assets"
Delete a file
curl -X DELETE -H "X-Edit-Password: $PW" \
https://app.sitebin.io/api/sites/$EDIT_ID/files/js/app.js
Custom domains
403.Add a domain
A domain is attached only once its DNS proves it belongs to the site.
The call answers 202 while that proof is missing — the
claim is recorded and the body lists it under
pending_domains, each entry carrying the TXT record to
create (txt_name = txt_value) or, on a
subdomain-served instance, the cname_target the domain may
point at instead. 200 means attached, and the domain is
then in custom_domains. POST the same domain
again to re-check; the instance also re-checks pending claims itself
and drops one that never verifies after 7 days. See
Custom domains for the records and the
example response.
# 202 = claimed, pending DNS proof (pending_domains lists the record to create)
# 200 = attached. POST again to re-check.
curl -X POST -H "X-Edit-Password: $PW" -H "Content-Type: application/json" \
-d '{"domain":"docs.client.com"}' \
https://app.sitebin.io/api/sites/$EDIT_ID/domains
Both custom_domains and pending_domains are
part of the settings document every site call returns, so a
GET on the site shows where each domain stands.
Remove a domain
Works on a pending claim as well as an attached domain.
curl -X DELETE -H "X-Edit-Password: $PW" \
https://app.sitebin.io/api/sites/$EDIT_ID/domains/docs.client.com
Redirect a domain
An attached domain can redirect to another attached domain of the same
site, keeping the path and query; code is 301
(the default), 302, 307 or 308.
The target must serve the site itself — one hop, never a chain.
DELETE makes the domain serve the site again. A refused rule
answers 400 with the reason; a domain the site has not
attached, 404.
curl -X PUT -H "X-Edit-Password: $PW" -H "Content-Type: application/json" \
-d '{"to":"client.com","code":301}' \
https://app.sitebin.io/api/sites/$EDIT_ID/domains/www.client.com/redirect
curl -X DELETE -H "X-Edit-Password: $PW" \
https://app.sitebin.io/api/sites/$EDIT_ID/domains/www.client.com/redirect
The generated address
What <id>.sitebin.app does: serve (the
default), off — refused with 400 while no
custom domain serves the site — or redirect to one of its
domains.
curl -X PUT -H "X-Edit-Password: $PW" -H "Content-Type: application/json" \
-d '{"mode":"redirect","to":"client.com","code":301}' \
https://app.sitebin.io/api/sites/$EDIT_ID/view-address
Every settings document carries domain_redirects,
view_address and primary_url. See
Redirects and the generated
address.
On a site in Container mode every domain serves one of the app's HTTP
ports: POST …/domains takes an optional
"target": "app:3000" (default: the port of the site's own
address), PUT …/domains/{domain}/target
{"endpoint": "app:3001"} points it at another, and
view-address takes "endpoint" to pick which
port's generated address it sets. A port the compose file does not
declare answers 400. The document adds
addresses and domain_targets; see
Containers.
Containers
Switch a site with PUT {"mode":"container"}. Start, stop
and restart answer 202 with the settings document, whose
container object carries status
(starting · running · stopped ·
error), a message, and each service with its
state and HTTP ports. Starting past your plan's container count answers
403.
curl -X POST -H "X-Edit-Password: $PW" https://app.sitebin.io/api/sites/$EDIT_ID/containers/start curl -X POST -H "X-Edit-Password: $PW" https://app.sitebin.io/api/sites/$EDIT_ID/containers/restart curl -X POST -H "X-Edit-Password: $PW" https://app.sitebin.io/api/sites/$EDIT_ID/containers/stop # the last lines a service wrote (tail: 1-1000, default 200) curl -H "X-Edit-Password: $PW" "https://app.sitebin.io/api/sites/$EDIT_ID/containers/app/logs?tail=200"
Forms
A site's email forms (see Forms). Adding one
answers 201 and mails the recipient a confirmation; the form
is pending until they accept. Every answer is the site's form
list: enabled, limit, used, and each
form with its status (pending ·
active · stopped · paused) and
snippet. Past the plan's forms per site: 403. More
than 10 confirmation mails per site, 3 per address, or 20 from one
caller in a day — or the instance's daily ceiling — answers
429.
curl -H "X-Edit-Password: $PW" https://app.sitebin.io/api/sites/$EDIT_ID/forms
curl -H "X-Edit-Password: $PW" -H "Content-Type: application/json" \
-d '{"name":"Contact","recipient":"office@example.com","captcha":true,"files":false,"redirect":"/thanks.html"}' \
https://app.sitebin.io/api/sites/$EDIT_ID/forms
curl -X PUT -H "X-Edit-Password: $PW" -H "Content-Type: application/json" -d '{"recipient":"team@example.com"}' \
https://app.sitebin.io/api/sites/$EDIT_ID/forms/$KEY
curl -X DELETE -H "X-Edit-Password: $PW" https://app.sitebin.io/api/sites/$EDIT_ID/forms/$KEY
curl -X POST -H "X-Edit-Password: $PW" https://app.sitebin.io/api/sites/$EDIT_ID/forms/$KEY/confirmation
Read one file's content
Returns the raw content of a single file — this is what the in-browser editor uses.
curl -H "X-Edit-Password: $PW" https://app.sitebin.io/api/sites/$EDIT_ID/content/index.html
Download the site as a zip
curl -H "X-Edit-Password: $PW" -o site.zip https://app.sitebin.io/api/sites/$EDIT_ID/download
Delete the site
curl -X DELETE -H "X-Edit-Password: $PW" https://app.sitebin.io/api/sites/$EDIT_ID
Report abuse
Public endpoint, no auth required — the scripted twin of the report
page at /report (see report
abuse). target (the site's address, a custom domain or
its id) and reason are required; details and
contact — an email address to reach you at — are optional.
A report stores only the reporter's network (/24 or
/48), never the full address, and is purged after 14 days;
every stored report is mailed to the operator. The endpoint allows 20
reports an hour per address and 200 instance-wide, shared with the
report page, and a repeat report of the same target from the same
network within a day is acknowledged (202) but not
stored.
curl -X POST -H "Content-Type: application/json" \
-d '{"target":"https://abc.sitebin.app","reason":"phishing","details":"fake Microsoft sign-in","contact":"you@example.com"}' \
https://app.sitebin.io/api/report
Rate limits
Anonymous site creation is rate limited per IP, and password attempts (edit, view, and WebDAV auth) are rate limited per IP and per site. Self-hosted instances can tune both limits at startup.