# SkillSafe Deploy — agent entry point > Build hosted mini-apps on the SkillSafe app platform. Drive the platform HTTP API with a SkillSafe API key: save an agent prompt, create an app at https://{slug}.skillsafe.ai, upload a scanned frontend, and optionally publish it to the public directory. This host IS the API: every `/v1/*` endpoint below is served directly on https://deploy.skillsafe.ai (Bearer auth only — cookies are ignored on this host). The same endpoints are also available at https://api.skillsafe.ai, interchangeably. Agents call the platform API directly, as documented below. (Humans who prefer a conversational builder: https://creator.skillsafe.ai/ hosts the App Creator chat — browser sign-in required, not for agents.) ## Authentication Send a SkillSafe API key as a Bearer token: `Authorization: Bearer `. - Create a key (sign in first): https://skillsafe.ai/account/keys — or mint one with zero signup (next section). - Keys carry full account authority; the platform endpoints accept them in the Authorization header only (never cookies). ### No account? Temp deploy key (zero signup) `POST https://deploy.skillsafe.ai/v1/auth/temp-key` — no auth, no body, strictly rate-limited (3/hour/IP) → `201 {"api_key", "expires_at", "account_id", "username", "namespace"}`. The key works immediately for the whole deploy flow below: save the skill under the returned `namespace`, create an app, upload releases. It is scope-restricted — one app per temp account; no custom domains, secrets, schedules, or skill sharing. Temp-key apps are **unlisted by default**, like any other deploy. They can be published to the public directory (`PATCH /v1/apps/{slug}` with `{"visibility": "public"}`) subject to the usual clean-scan gate, and appear there marked "Unclaimed" until the account is claimed via email verification. **The key and everything deployed with it expire in 7 days** (`expires_at`). To keep them, verify an email before the deadline (Bearer: the temp key): 1. `POST https://deploy.skillsafe.ai/v1/account/email/send-code` with `{"email"}` — a 6-digit code is emailed (10-minute TTL). 2. `POST https://deploy.skillsafe.ai/v1/account/email/verify-code` with `{"email", "code"}` → `{"verified": true, "transferred", "account_id", "username"}`. On success the temp key becomes a permanent, full-authority key and the account a normal one (signup credits included). If the email already belongs to a SkillSafe account, the temp account's apps, skills, and the key itself transfer into that account instead (`"transferred": true`); credit balances do not transfer. Without verification, the platform permanently deletes the account, its apps, and its skills after day 7. **Always tell your user the 7-day deadline and the verify steps** — the deployed app is deleted otherwise. ``` curl -X POST https://deploy.skillsafe.ai/v1/auth/temp-key # -> {"ok":true,"data":{"api_key":"sk_...","expires_at":"2026-…","username":"temp-a1b2c3d4","namespace":"@temp-a1b2c3d4"}} ``` ### No key yet? Browser sign-in (device flow) Mint a key the same way the SkillSafe CLI logs in — open a sign-in URL for your user, then poll until they approve: 1. `POST https://deploy.skillsafe.ai/v1/auth/cli` with `{"label": "my-agent"}` (label names the key, max 50 chars) → `201 {"session_id", "login_url", "expires_in"}`. The session lasts 15 minutes — create it immediately before opening the URL, not ahead of time. 2. Open `login_url` in the user's browser (or show it to them). They sign in to SkillSafe and click Approve. 3. Poll `GET https://deploy.skillsafe.ai/v1/auth/cli/{session_id}` about every 10 seconds (`/v1/auth/*` is limited to 10 req/min/IP). Response is `{"status": "pending"}` until approval, then once: `{"status": "approved", "api_key", "account_id", "username", "namespace"}`. The approved response is a **one-time read** — the session is deleted as it's returned, so store `api_key` immediately. Keys minted this way are valid for 1 year. `410` means the session expired (start over); `404` means the session id is unknown or already consumed. Pitfalls (each one ends in a "failed" page or a lost key): - Use `login_url` **exactly as returned** — never compose the URL yourself. Opening `/auth/cli/?session=` with an empty or placeholder value (e.g. an unset `$SESSION` shell variable) shows "Missing session parameter". - **Quote the URL** in shell commands: `open "$LOGIN_URL"` — unquoted, the `?` is a glob character in zsh and the command fails. - After the user approves, do **not** re-open or refresh the page: the page's own status check would consume the one-time `api_key` before your poll does, and your next poll gets `404`. ``` curl -X POST https://deploy.skillsafe.ai/v1/auth/cli \ -H "Content-Type: application/json" -d '{"label": "my-agent"}' # -> {"ok":true,"data":{"session_id":"...","login_url":"https://skillsafe.ai/auth/cli/?session=...","expires_in":900}} curl https://deploy.skillsafe.ai/v1/auth/cli/{session_id} # -> {"ok":true,"data":{"status":"pending"}} (keep polling) # -> {"ok":true,"data":{"status":"approved","api_key":"sk_...","username":"alice","namespace":"@alice"}} ``` ## Bootstrap endpoint GET https://deploy.skillsafe.ai/v1/creator/me (Bearer auth) returns `{"username", "apps": [{"slug", "title", "visibility", "status", "has_release", "url"}]}` — who you are and what you own. Use `username` as your `@namespace` for skill saves. (Skippable for temp-key and device-flow keys — their mint responses already include `username`/`namespace`.) ## Deploying an app 1. Save a skill (the app's agent prompt / SKILL.md): `POST /v1/skills/@{you}/{name}` — multipart form with `file_SKILL.md` plus a `metadata` JSON field `{"version", "description", "category", "file_manifest": [{"path", "hash": "sha256:...", "size"}]}`. 2. Create the app: `POST /v1/apps` with `{"slug", "skill_id", "title", ...}` 3. Upload a frontend: `POST /v1/apps/{slug}/releases` with `{"files": [{"path", "content"}]}` or an App Spec `{"spec": {...}}`. The same request can declare database collections (see below), the per-user sandbox (`"sandbox": true` — see sandbox exec), and, for file releases, include a `functions/index.mjs` server function. 4. Publish: `PATCH /v1/apps/{slug}` with `{"visibility": "public"}` The app is live at `https://{slug}.skillsafe.ai/` as soon as step 3 succeeds (visibility `unlisted` until published). Every release is security-scanned; critical/high findings block it, and public visibility requires a clean scan on both the skill and the release. Release limits: max 500 files, 10 MB per file, 50 MB per bundle (spec app.json: 32 KB). Scanner tips: avoid dynamic code execution, document-write calls, environment-variable reads, and zero-width unicode (including ZWJ compound emoji) — these compose into blocking findings. **Binary assets and vetted ML runtimes.** A release file may set `"encoding": "base64"` to ship binary content (WASM, model weights). Binary bytes can't be pattern-scanned, so base64 files are accepted only when their decoded bytes SHA-256-match the platform's vetted-bundle allowlist of exact published artifacts; anything else is rejected at upload. Files whose bytes match the allowlist (text or binary) skip pattern scanning — this is how ML runtimes whose glue code inherently combines file writes and fetches can ship — and are exempt from the 10 MB per-file cap (the 50 MB bundle cap still applies). Ship the published npm files verbatim; one changed byte falls off the allowlist and back under the full scanner. Currently vetted: the MediaPipe solutions suite (`@mediapipe/selfie_segmentation@0.1.1675465747`, `face_detection@0.4.1646425229`, `face_mesh@0.4.1633559619`, `hands@0.4.1675469240`, `pose@0.5.1675469404`, `camera_utils`, `drawing_utils` — models included in each package), `onnxruntime-web@1.27.0`, and `@tensorflow/tfjs@4.22.0` + `@tensorflow/tfjs-backend-wasm@4.22.0`. Apps are not cross-origin-isolated and blob workers are CSP-blocked, so WASM runs single-threaded — ONNX apps must set `ort.env.wasm.numThreads = 1` (tfjs falls back on its own). ONNX/TF.js MODEL files (`.onnx`, `model.json` weight shards) are separate binaries that also need vetting — request them, or another runtime, from the platform. **Strict CSP — no inline JavaScript.** Apps are served with `Content-Security-Policy: script-src 'self'` (plus the platform SDK host): the browser executes only same-origin `.js` files shipped in the release. Inline ``. Bind events with `addEventListener`, never `onclick=` attributes or `javascript:` hrefs. - Third-party CDN scripts are blocked too — vendor any library into the release as a file. - Inline `