Deploy to Crayy
Get your app live in under a minute. Works with Node.js, Flask, FastAPI, Streamlit, Gradio, and static HTML.
1. Quick Start
Fastest way: Open your project in Claude Code and say: "deploy this to crayy.com"
Claude Code (ADA) will read these docs, walk you through naming your app, and handle the deploy automatically. Or follow the manual steps below.
2. App Requirements
- Health endpoint: Your app must expose
GET /healthreturning HTTP 200. This is how the platform confirms your app is running. Streamlit and Gradio have built-in health endpoints, so no action needed for those. - Single port: Your app must listen on a single port. Use the
PORTenvironment variable if available, or your framework's default port. The platform setsPORTfor you inside the container. - No auth required: The platform handles framing and access. Your app doesn't need its own auth.
- Optional:
openapi.jsonat project root — recommended for API discoverability but not required.
3. Deploy Steps
These steps are written for both humans and AI assistants (like Claude Code) to follow programmatically.
Step 1: Check for existing deploy config
Look for .crayy/config.json in the project root. If it exists, this is an update — skip to the Update section below.
Step 2: Choose an app name
Ask the user for their desired app name. Rules:
- Lowercase letters, numbers, and hyphens only
- Cannot start or end with a hyphen
- 1–40 characters
- Must be unique on the platform
Step 3: Verify name availability
GET https://mvp.crayy.com/api/check-name/{name}
Response:
{ "name": "my-app", "available": true }
If available is false, ask the user for a different name.
Step 4: Get user's email
Ask the user for their email address. This is used for deploy notifications and account association.
Step 5: Ensure /health endpoint exists
Check the app's source code for a /health endpoint. If missing, add one based on the framework. The server-side review agent will also attempt to auto-fix this, but adding it client-side gives faster deploys.
Step 6: Create the tarball
tar -czf app.tar.gz --exclude=node_modules --exclude=__pycache__ --exclude=.git --exclude=.env --exclude=.crayy -C <appDir> .
Step 7: Deploy
POST https://mvp.crayy.com/api/deploy
Content-Type: multipart/form-data
Fields:
tarball: (file) app.tar.gz
name: my-app
displayName: My Cool App
description: One-line description of what the app does
email: user@example.com
Step 8: Handle the response
Check the status field in the JSON response:
"deployed" — Success! Save the deploy token and report the URL:
{
"status": "deployed",
"app": { "name": "my-app", "url": "https://mvp.crayy.com/my-app", "displayName": "My Cool App" },
"deployToken": "abc123...",
"oaChanges": [],
"message": "App deployed successfully!"
}
Save deployToken, name, and url to .crayy/config.json in the project root for future updates:
{
"appName": "my-app",
"deployToken": "abc123...",
"url": "https://mvp.crayy.com/my-app"
}
"rejected" — The review agent rejected the app:
{
"status": "rejected",
"reason": "App contains potentially malicious code: shell injection via user input.",
"securityNotes": "...",
"contentNotes": "..."
}
Report the rejection reason to the user. They need to fix the issues and try again.
"failed" — Build or health check failed:
{
"status": "failed",
"error": "Health check failed after all retries",
"logs": "container stderr output..."
}
Report the error and container logs to the user so they can debug.
4. Update / Redeploy
When .crayy/config.json exists, the app has been deployed before. To update:
Step 1: Read the config
// .crayy/config.json
{
"appName": "my-app",
"deployToken": "abc123...",
"url": "https://mvp.crayy.com/my-app"
}
Step 2: Create the tarball
tar -czf app.tar.gz --exclude=node_modules --exclude=__pycache__ --exclude=.git --exclude=.env --exclude=.crayy -C <appDir> .
Step 3: Deploy with token
POST https://mvp.crayy.com/api/deploy
Content-Type: multipart/form-data
Fields:
tarball: (file) app.tar.gz
name: my-app
displayName: My Cool App
description: Updated description
email: user@example.com
deployToken: abc123...
Step 4: Handle the response
Same response shapes as new deploy, except status will be "updated" on success:
{
"status": "updated",
"app": { "name": "my-app", "url": "https://mvp.crayy.com/my-app", "displayName": "My Cool App" },
"oaChanges": [],
"message": "App updated successfully!"
}
5. Adding /health — Framework Examples
Express (Node.js)
app.get('/health', (req, res) => res.status(200).json({ status: 'ok' }));
Flask
@app.route('/health')
def health():
return {'status': 'ok'}, 200
FastAPI
@app.get('/health')
def health():
return {'status': 'ok'}
Streamlit
Built-in health at /_stcore/health. No action needed.
Gradio
Built-in health at /. No action needed.
Static HTML
Create a health file (no extension) or health/index.html with any content. Or just ensure your index.html exists — the platform nginx config handles it.
6. API Reference
Check Name Availability
GET /api/check-name/:name
Response:
{ "name": "my-app", "available": true }
Deploy / Update App
POST /api/deploy
Content-Type: multipart/form-data
Fields:
tarball: file (required) — .tar.gz of your app
name: string (required) — app name, lowercase alphanumeric + hyphens
displayName: string (optional) — human-readable name
description: string (optional) — one-line description
email: string (optional) — creator email
deployToken: string (optional) — include for updates
Response (success - new deploy):
{
"status": "deployed",
"app": { "name": "...", "url": "https://mvp.crayy.com/...", "displayName": "..." },
"deployToken": "...",
"oaChanges": [...],
"message": "App deployed successfully!"
}
Response (success - update):
{
"status": "updated",
"app": { "name": "...", "url": "https://mvp.crayy.com/...", "displayName": "..." },
"oaChanges": [...],
"message": "App updated successfully!"
}
Response (rejected by review):
{
"status": "rejected",
"reason": "...",
"securityNotes": "...",
"contentNotes": "..."
}
Response (build/health failure):
{
"status": "failed",
"error": "...",
"logs": "..."
}
Discover Apps
GET /api/discover
Response:
{
"platform": "crayy",
"version": "0.1.0",
"apps": [
{
"name": "my-app",
"displayName": "My App",
"description": "...",
"type": "node",
"url": "/my-app/",
"status": "live",
"profile": {
"category": "...",
"summary": "...",
"capabilities": ["..."],
"tags": ["..."]
}
}
]
}