---
topic: deploy
connector: deploy_app
api: POST https://ship.embarko.ai/apps
requires: [prerequisites]
---

[← Guide home](https://embarko.ai/docs)

# Requirements, options and errors

**On this page:** What each route accepts, Requirements, How it starts, App name, With the connector, Over HTTP, Examples, Large apps, Limits, Errors, What to tell the person

> Plain words unless the person is a developer. Never invent a link, name or code. [Rules](https://embarko.ai/docs/prerequisites) Needs first: [prerequisites](https://embarko.ai/docs/prerequisites).

Send the app's files; Embarko detects the language, builds it and serves it at https://{app-name}.embarko.app. No Dockerfile. Redeploying the same name updates the same app.

**Connector:** deploy_app

**Done when:** The app opens at https://{app-name}.embarko.app and its main feature works when you try it.

**What each route accepts**

- Connector: deploy_app takes the files themselves: [{ path, content, encoding? }]. Text as-is, binaries as base64.
- HTTP multipart: A .tar.gz of the app folder in the "source" form field. Not .zip.
- HTTP JSON: The same files list as the connector, as a JSON body.
- Upload page: A folder or a .zip (a .tar.gz also works). The browser packs it.

**Requirements**

- The app reads its port from PORT and binds 0.0.0.0 (not a fixed port, not localhost).
- No Dockerfile: delete it or leave it out.
- The manifest (package.json, requirements.txt, go.mod, ...) or index.html sits at the top level of what you send.
- One upload is one app with one process. A frontend and a backend are two apps.
- Anything that must survive a redeploy is written under DATA_DIR (see the data topic).
- Leave out node_modules, build output, .git and .env files. Secrets go in env vars, not in the upload.

**How it starts**

- Node: Embarko installs dependencies, runs the build script if there is one, then npm start. The start script must listen on process.env.PORT and 0.0.0.0.
- Next.js: "next start" reads PORT by itself. Do not add -H localhost or -H 127.0.0.1.
- Vite, Create React App and other single-page apps (including Lovable and Bolt exports): send the project with its package.json and build script, not a built dist folder. Embarko builds it and serves the output as a website; no start script or server is needed, and links to inner pages (/products) load the app.
- Values a frontend build reads (VITE_..., REACT_APP_...) cannot come from .env (left out of the upload), and do not rely on env vars for them. Put them in a committed config file, for example src/config.ts. Public values are fine there: an API address, a Supabase URL and anon key, a Stripe publishable key. Never put a secret key in frontend code; secrets belong on a backend (see two-apps).
- Python: give it a main.py that starts the server itself on PORT and 0.0.0.0, with debug off. FastAPI: uvicorn.run(app, host="0.0.0.0", port=int(os.environ["PORT"])). Flask: from app import app, then app.run(host="0.0.0.0", port=int(os.environ["PORT"])). Dependencies go in requirements.txt.
- Plain HTML: nothing to start (see static-sites).

**App name**

- Pattern: `^[a-z0-9-]+$, no leading or trailing dash`
- Length: 3-63 characters
- Unique: Across all of Embarko. It becomes the address, so slugify: "My Landing Page" → my-landing-page.
- Reserved: Some names are reserved for Embarko and never free, for example www, api, app, admin, docs, help. A reserved name returns 409 app_name_reserved: pick another.

**With the connector**

1. Call deploy_app with { appName, files: [{ path, content, encoding? }], appType?, version? }.
2. The reply has status and links.app. Poll read_app { action: "status", appName } as described in polling.

**Over HTTP**

1. Package the folder as .tar.gz (first example).
2. POST https://ship.embarko.ai/apps as multipart with the field source and the headers X-App-Name and X-Agent-Name (your own name, e.g. codex).
3. Optional headers: Authorization: Bearer {token} (permanent at once), X-App-Version (a git SHA), X-App-Type (what the app is for, e.g. portfolio).
4. Or POST https://ship.embarko.ai/apps with Content-Type: application/json and { appName, agentName, files } (second example).
5. A server app replies 202 "building". A plain static site usually replies 200 "live" at once. Then poll as described in polling.

**Example: Multipart upload (server app) → 202 building**

```bash
tar -czf /tmp/app.tar.gz --exclude=node_modules --exclude=.git --exclude='.env*' --exclude=dist --exclude=.next --exclude=venv --exclude=.venv --exclude=__pycache__ --exclude='*.db' -C /path/to/app .
curl -sS -X POST 'https://ship.embarko.ai/apps' -H 'X-App-Name: my-app' -H 'X-Agent-Name: codex' -H 'X-App-Type: portfolio' -F 'source=@/tmp/app.tar.gz'
```

Reply 202:

```json
{"success":true,"status":"building","app":{"name":"my-app","kind":"service","claimed":false,"expiresAt":"2026-10-10T06:50:46.326Z"},"next":"Building: poll links.status every 10 seconds until it says \"live\", \"failed\" or \"crashed\".","links":{"status":"https://ship.embarko.ai/apps/my-app/status","feedback":"https://ship.embarko.ai/apps/my-app/customer-query"}}
```

**Example: JSON upload (static site) → 200 live**

body.json:

```json
{"appName":"my-site","agentName":"codex","files":[{"path":"index.html","content":"<h1>Hello</h1>"}]}
```

```bash
curl -sS -X POST 'https://ship.embarko.ai/apps' -H 'Content-Type: application/json' --data @body.json
```

Write body to body.json first, then run the command.

Reply 200:

```json
{"success":true,"status":"live","app":{"name":"my-site","kind":"static","claimed":false,"expiresAt":"2026-10-10T06:50:46.326Z"},"next":"Live: give the user links.app and tell them it will be deleted at 2026-10-10T06:50:46.326Z unless claimed. To claim it, follow links.help.","links":{"app":"https://my-site.embarko.app","feedback":"https://ship.embarko.ai/apps/my-site/customer-query","help":"https://embarko.ai/docs/claim"}}
```

**Example: Name taken → 409**

Reply 409:

```json
{"error":"App name \"my-app\" is already in use by a different workspace","code":"app_name_taken","next":"That name belongs to someone else: pick a different one.","links":{"feedback":"https://ship.embarko.ai/api/public/customer-query"}}
```

**Polling:** GET https://ship.embarko.ai/apps/{app-name}/status every 10s (2s for a static site) until it is final. Statuses and limits: [status](https://embarko.ai/docs/status).

**Large apps**

A connector call carries the whole app inside one tool call, so it is limited by how much you can write in one reply, well before the 25MB cap.

- Send source files only: no node_modules, build output or videos. Images the app shows are fine to send; only very large media (over about 5MB each) should move to an outside host.
- There is no way to split one deploy across several calls.
- Still too big and you can run commands: use the HTTP .tar.gz upload instead.
- Still too big and you cannot: give the person a .zip and send them to the upload page (route 4).

**Limits**

- Memory: 256MB per app; it cannot be raised.
- JSON and connector uploads: at most 25MB and 2,000 files. Multipart .tar.gz has no such file count.

**Errors**

| Code | Status | Retry | What to do |
| --- | --- | --- | --- |
| `host_not_allowed (from your own sandbox)` | 403 | No | Not Embarko: your environment blocks ship.embarko.ai. Switch route: connector, or ask the person to connect it. |
| `app_name_taken` | 409 | No | Another account owns the name. On a first deploy, pick a close variant yourself: add the person's or business name if you know it, otherwise a short word (portfolio → portfolio-site, then portfolio-app). Mention the name with the link. Ask only if the exact name matters to them. |
| `no_manifest_at_root` | 422 | No | Read foundNestedAt and package from that folder. |
| `app_nested_in_subdirectory` | 422 | No | Deploy the folder named in deployThisInstead. |

Every other code, and the retry rules: see [errors](https://embarko.ai/docs/errors).

**What to tell the person**

- deploying: "Your app is deploying now. I'll tell you when it's live."
- failed: "It didn't go live because of a problem in the code. I'm fixing it now."

**Next:** Deploy, then poll until the app is live.

**Related:** [status](https://embarko.ai/docs/status) · [logs](https://embarko.ai/docs/logs) · [static-sites](https://embarko.ai/docs/static-sites) · [data](https://embarko.ai/docs/data) · [claim](https://embarko.ai/docs/claim) · [feedback](https://embarko.ai/docs/feedback)

---

[Guide home](https://embarko.ai/docs) · JSON: add .json