Auth
The user layer is an account; the credential is an api_key. The first POST /create (no auth) creates your account and returns the key. Send it on every other call:
Authorization: Bearer vibe_xxxxxxxx
api_key is still your credential. Phone verification just attaches a phone to the same account; the key doesn't change. Auth-required endpoints return 401 without a token; only /create and /feedback work anonymously.Deploy your app
Your subdomain routes to port 80 in the container. To publish there:
1 — Static: drop files in /root/www (served immediately, nothing to kill). 2 — Your own server: point our supervisor at it with an executable /root/run.sh bound to 0.0.0.0:80 — it gets auto-restarted and survives restarts. Then free :80 from the default placeholder:
# optional — only if you want YOUR long-running server on the public :80 cat > /root/run.sh <<'SH' #!/usr/bin/env bash cd /root/myapp exec python3 server.py # must listen on 0.0.0.0:80 SH chmod +x /root/run.sh pkill -f busybox # drop the default placeholder; container stays up
Errors & conventions
| Code | Meaning |
|---|---|
| 400 | bad input |
| 401 | missing/invalid api_key |
| 402 | always-on plan needs credit |
| 403 | limit (anon IP server cap, always_on, domain) |
| 404 | server not found / not yours |
| 409 | subdomain taken |
| 429 | rate limit |
| 503 | at capacity / not configured |
When the free grant is exhausted and there's no credit, read endpoints return an upgrade envelope:
{ "upgrade_required": true, "reason": "free_tier_exceeded",
"upgrade_url": "https://thevibehosting.com/account/recharge",
"message": "Server will be paused in 48h. Top up credit, or verify a phone…" }
Objects
// Credits { "tier":"anonymous", "ram_gb_hours_left":100.0, "egress_gb_left":5.0, "disk_mb_left":512.0, "balance_usd":0.0 } // Server { "id":"srv_…", "subdomain":"abc123.thevibehosting.com", "state":"running", "always_on":false, "plan":null, "suspended":false, "flag":null, "exposed_ports":[{"public":"46.225.188.115:40860","container_port":5432,"protocol":"tcp"}], "usage":{"disk_mb_used":0.0,"domains":[]}, "credits":{…} }
Endpoints
POST /create no auth → creates account
Create a server. Optional body: { region?, image?, subdomains?, subdomain?, always_on?, plan? }. plan (standard|pro|max = 1|2|4 GB; $3/$5/$9 mo) makes it always-on and needs credit. image is restricted to vetted base images (currently the default Ubuntu base); an unsupported value returns 400.
Pick a nice URL. Pass subdomains — a preference-ordered list — and the first available one is taken (include fallbacks). Slugs are lowercased; letters, digits, hyphens. If all are taken a random slug is assigned and subdomain_note says so (no error). A lone subdomain string still works but a clash returns 409.
# request — choose a name, first free wins curl -sX POST https://thevibehosting.com/create -H 'content-type: application/json' \ -d '{"subdomains":["caffeine-tracker","caffeine-app","caffeine"]}' # 200 { "id":"srv_ab12cd34ef", "ssh":"ssh root@46.225.188.115 -p 28210", "ssh_private_key":"-----BEGIN OPENSSH PRIVATE KEY-----\n…", "subdomain":"caffeine-tracker.thevibehosting.com", "subdomain_note":null, "api_key":"vibe_0123…", "credits":{…}, "ONBOARDING":"…" }
subdomain to see which choice you got. subdomain_note is non-null only when all your names were taken and a random one was assigned (rename later via …/subdomain). Save ssh_private_key (chmod 600) and api_key. SSH is ready within ~1–3 s. SSH is IP:port (the subdomain is HTTP only).GET /account
Who this api_key is.
{ "account_id":"acc_…","tier":"anonymous","phone_verified":false,"projects":1,"credits":{…} }GET /servers
Array of your Server objects. curl -s …/servers -H "Authorization: Bearer vibe_…"
GET /servers/{id}
One server's status / usage / credits. 404 if not yours.
GET /servers/{id}/usage
{ "server":"srv_…", "credits":{…}, "disk_mb_used":0.0 }
Returns the upgrade envelope instead if the grant is exhausted.
POST /servers/{id}/subdomain
{ "subdomain":"newslug" } → { "subdomain":"newslug.thevibehosting.com", "restarted":true, "warning":"…" }. 409 if taken. ⚠️ Recreates the server (re-points routing): only /root persists — data written outside /root (e.g. /var/lib/mysql) is wiped, so keep DBs/uploads under /root. /root/run.sh + SSH keys kept; open SSH drops — reconnect (host key unchanged).
POST /servers/{id}/domain verified tier
{ "domain":"example.com" } → CNAME instructions + verification status.
POST /servers/{id}/expose
Publish a raw TCP/UDP port (for non-HTTP services; HTTP on :80 is already your subdomain); max 5 ports/server. ⚠️ Recreates the server — same as rename: only /root persists (data outside /root, e.g. /var/lib/mysql, is wiped) and open SSH drops. Expose ports before loading data, or keep data under /root.
# request { "container_port":5432, "protocol":"tcp" } # 200 { "public":"46.225.188.115:40860", "container_port":5432, "protocol":"tcp" }
POST /servers/{id}/ssh-key
{ "public_key":"ssh-ed25519 AAAA…" } → { "ok":true }
DELETE /servers/{id}
Delete the server and its data. → { "ok":true }
POST /account/verify-phone
Attach a verified phone → upgrades to the verified tier (unlimited servers on one shared balance, custom domains, always-on, bigger grant). Agent-driven, two steps.
# step 1 — show the returned disclosure first, then send the code (needs consent) { "phone":"+420600000000", "consent":true } → { "sent":true, … } # step 2 — confirm the SMS code { "phone":"+420600000000", "code":"123456" } → { "verified":true, "tier":"verified", … }
POST /account/recover
Lost your api_key? Recover it by re-verifying the phone on the account. No auth needed; two steps.
# step 1 — send a code to the phone on the account { "phone":"+420600000000" } → { "sent":true, … } # step 2 — confirm; returns your api_key { "phone":"+420600000000", "code":"123456" } → { "recovered":true, "api_key":"vibe_…", … }
POST /account/recharge verified phone alias: /account/topup
Add credit in advance, any time, via Stripe Checkout. { "amount_usd":5 } → { "checkout_url":"https://checkout.stripe.com/…" }. Open the URL to pay; balance is credited automatically (min $5).
403 — verify your phone first with POST /account/verify-phone. This keeps anonymous accounts from adding credit.POST /feedback no auth ok
Tell us what to add or fix — we read every one. Add email for a reply.
{ "kind":"feature", "message":"add a /logs endpoint", "email":"you@x.com" }
→ { "ticket_id":"fb_…", "thanks":"Recorded, thank you!" }
# kind: feature | bug | friction | limit | image_request | other