Step 15: Deploy to run402
Phase: deploy
Context
You have verified app_files and project_id. Time to put the app online.
What to do
Deploy the static site
POST https://api.run402.com/deployments/v1
Content-Type: application/json
{
"name": "app-name",
"project": "{project_id}",
"files": [
{
"file": "index.html",
"data": "<!DOCTYPE html>...",
"encoding": "utf-8"
},
{
"file": "style.css",
"data": "body { ... }",
"encoding": "utf-8"
},
{
"file": "app.js",
"data": "const CONFIG = ...",
"encoding": "utf-8"
}
]
}
This endpoint is x402-gated ($0.05). No API key or auth header is needed — the x402 payment header IS the authorization. Same flow as project creation: first request returns 402, sign payment, retry with x-402-payment header.
Response (201):
{
"id": "dpl-1709337600000-a1b2c3",
"name": "app-name",
"url": "https://dpl-1709337600000-a1b2c3.sites.run402.com",
"status": "READY",
"files_count": 3,
"total_size": 15234
}
Auth model reference
Different run402 endpoints use different authentication methods. Here is the complete reference:
| Endpoint | Auth Method |
|---|---|
POST /projects/v1 | x-402-payment header (x402 signed payment) |
POST /deployments/v1 | x-402-payment header (x402 signed payment) |
POST /projects/v1/admin/{id}/sql | Authorization: Bearer {service_key} |
POST /subdomains/v1 | Authorization: Bearer {service_key} |
GET /rest/v1/... | apikey header (anon_key) |
Note: POST /subdomains/v1 is idempotent — it upserts. If the subdomain already belongs to your project, it updates the deployment_id instead of returning a 409. This means you can safely call it on every deploy without checking whether the subdomain already exists.
Important notes
- Max 50 MB per deployment. If files exceed this, reduce image sizes or split assets.
- Immutable deployments — each deploy creates a new URL. Previous deployments stay live.
- SPA support — paths without file extensions automatically serve
index.html. - For binary files (images), use
"encoding": "base64"and base64-encode the data.
Verify deployment
Check deployment status (free, no auth):
GET https://api.run402.com/deployments/v1/{deployment_id}
Wait for "status": "READY" before sharing the URL.
Claim a subdomain
By default, claim a subdomain so the app has a memorable URL like https://myapp.run402.com instead of the raw deployment URL.
Derive the subdomain automatically: take the app name, lowercase it, replace spaces and underscores with hyphens, strip non-alphanumeric characters (except hyphens), collapse consecutive hyphens, and truncate to 63 characters. Only skip the subdomain if the user explicitly declines.
POST https://api.run402.com/subdomains/v1
Content-Type: application/json
Authorization: Bearer {service_key}
{
"name": "myapp",
"deployment_id": "{deployment_id}"
}
Response (201):
{
"name": "myapp",
"deployment_id": "dpl-1709337600000-a1b2c3",
"url": "https://myapp.run402.com",
"deployment_url": "https://dpl-1709337600000-a1b2c3.sites.run402.com",
"project_id": "prj-abc123",
"created_at": "2026-03-05T12:00:00Z",
"updated_at": "2026-03-05T12:00:00Z"
}
Subdomain rules
- 3-63 characters, lowercase letters, numbers, and hyphens only
- Must start and end with a letter or number (no leading/trailing hyphens)
- No consecutive hyphens (
--) - Reserved words are blocked: api, www, admin, app, dashboard, docs, help, support, cdn, static, dev, staging, test, demo, run402, and others
- Each subdomain is owned by the project that created it — other projects cannot claim it
- Free — no x402 payment required, but needs
service_keyauth
If subdomain claiming fails
The subdomain request may fail for several reasons:
- 409 Conflict — the name is already taken. Suggest a different name (e.g., add a number or prefix).
- 400 Bad Request — the name violates the rules above (too short, reserved word, invalid characters).
- 429 Rate Limit — too many requests. Wait a moment and retry once.
- 5xx Server Error — run402 issue. Retry once; if it persists, skip the subdomain.
Fallback: If subdomain claiming fails after one retry, fall back to the raw deployment URL (https://dpl-{id}.sites.run402.com). Tell the user their app is live at the deployment URL and that you can try claiming a memorable subdomain later.
Do not retry in a loop or stall — the deployment itself already succeeded. The subdomain is optional polish.
If the user explicitly declines a subdomain, skip it — the raw deployment URL works fine.
Smoke-test gate
After deployment (and optional subdomain), you MUST verify the app is actually live before proceeding.
- Fetch the live URL (prefer
subdomain_urlif claimed, otherwisedeployment_url). - Confirm the response is HTTP 200 and the HTML page loads (check for
<!DOCTYPE html>or your app's<title>). - If the fetch fails or returns non-200, wait 5 seconds and retry once.
- If it still fails, tell the user: "The deploy succeeded but the site isn't responding yet. Let's give it a moment." Retry up to 3 times total.
- Do NOT proceed to Step 16 until the smoke test passes.
Expected output
deployment_id— The deployment identifierdeployment_url— The live URL (e.g., https://dpl-xxx.sites.run402.com)subdomain— The claimed subdomain name (if any)subdomain_url— The memorable URL (e.g., https://myapp.run402.com)