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.

On the hosted instance at 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.

What a token cannot do. It acts on sites, never on the account that owns them: the dashboard refuses it, so it cannot change your plan, reset passwords or delete your account. It reaches no other account's sites, and no anonymous site — those belong to nobody. A token that has been revoked, or one that never existed, is refused outright rather than falling back to something weaker.

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

Custom domains are an Enterprise feature — in the community edition these endpoints return 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

Container sites are an Enterprise feature (Pro and Studio on the hosted service). See Containers for the compose file.

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.