# Shipbase: deploy a frontend

Discover `https://launchbase.remotely.works/.well-known/shipbase` and follow its `agentInstructionsUrl`, `openapiUrl`, native `mcpEndpoint`, and authorization endpoints. Shipbase RC1 shares Launchbase users, workspace grants and projects with Setbase and serves Sites at operator-configured origins. Sites are private by default.

## Authority and credentials

Read `https://launchbase.remotely.works/skill.md` for the shared credential-location, authorization and safe recovery protocol. MCP-capable clients should use the discovered native endpoint and browser OAuth; Shipbase management operations appear there under their OpenAPI operation IDs and retain normal authority checks. Use `publishSiteFiles` for small inline deployments. Raw signed-upload creation and refresh are HTTP-only, intentionally excluded from MCP so storage credentials do not enter tool transcripts. Bulk HTTP clients send bytes directly to storage without Launchbase bearer credentials or browser cookies.

For the raw-HTTP fallback, reuse a verified token when its explicit scopes and workspace grants cover the task; never ask the user to paste a token. Otherwise POST the discovered device endpoint with `clientName` and a nonempty scope list, show the returned `verificationUriComplete` and `userCode`, then wait for human approval. Do not approve or impersonate a human yourself. After approval, exchange `deviceCode` once at the discovered token endpoint and verify the returned token with `/v1/me`.

- Navigate with `workspaces:read`, `projects:read` and `sites:read`.
- Create/delete Sites with `sites:write`; create, refresh and finalize deployments with `deployments:write`.
- Add `projects:write` only to create a project. Add Setbase collection/record/Guidance scopes only for that separate work.
- Writes require a live owner/editor membership. Scopes do not override workspace grants, roles or project restrictions.

Keep agent tokens outside generated projects in the protected per-user credential location described by the shared skill. Never put tokens, browser cookies, signed upload URLs or upload headers in source, site assets, project-local state, logs or Git. HTTP examples below use `{agent-token}` as a placeholder, not a credential to persist.

## Browser folder publishing (no installation)

For local or binary-heavy built assets, give the user the management-origin URL `/shipbase/workspaces/{workspaceId}/sites/{siteId}/publish` after resolving the Site. No secret is in this URL; the user signs in personally as owner/editor. Do not ask for their session cookie, mint an agent token for the browser, or claim remote MCP can read local paths. The selected output folder must contain `index.html`; limits are 500 files, 10 MiB/file, 100 MiB total. There is no ZIP or remote-URL import. The page preserves visibility, including existing public visibility, and replaces the full file set. The exact-hash script uses same-origin, one-use CSRF protected requests. A stale session needs reauthentication in another tab; keep the original page for identical-key retries after uncertainty. Reload/reselect only after inspecting stale-base or expired-deployment errors.

## Choose or create a Site

Prefer MCP `resolveWebsite` or POST `/v1/workspaces/{workspaceId}/websites/resolve` with `{"project":{"slug":"landing","name":"Landing"},"site":{"slug":"event-demo","name":"Event demo","spaFallback":false}}`. Select the workspace explicitly; ask if ambiguous. Both `projects:write` and `sites:write` are required with live owner/editor membership and no project restriction. Matching active resources are reused without new audit events or visibility changes. Existing project kind must be `website`; mismatched names/configuration and reserved Site slugs conflict. This operation does not publish content. Feed returned `site.id` and `site.currentDeploymentId` (or null if absent) into `publishSiteFiles`.

Resolution is retryable but not a cross-module transaction: the project can remain if Site creation fails. Retry the identical request; do not create duplicates or choose another workspace automatically. If the token is project-restricted or an existing generic project is intended, use the individual project/Site APIs below instead.

List the authorized workspace's projects, then GET `/v1/projects/{projectId}/sites` and follow `nextCursor` until the intended Site is found. Reuse its stable ID. A missing local state file is not a reason to create a duplicate.

```http
POST https://launchbase.remotely.works/v1/projects/{projectId}/sites
Authorization: Bearer {agent-token}
Content-Type: application/json

{"slug":"event-demo","name":"Event demo","spaFallback":false}
```

The response contains `site`, including `siteUrl` and `visibility: "private"`. Use that exact URL, including any configured port. With default local Docker settings it is `http://event-demo.sites.localhost:18080/`. The slug is a globally unique, reserved-on-delete DNS label of at most 63 lowercase ASCII characters, beginning with a letter. Some platform names are reserved. Site creation is not replayable: after an unknown result, list the same project and check the exact slug before trying again.

Change visibility with `PATCH /v1/sites/{siteId}` and exactly `{"visibility":"private"}` or `{"visibility":"public"}`. This operation is idempotent and is available through MCP as `updateSiteVisibility`. Do not make a Site public unless the user explicitly requests anonymous publication and understands that anyone with its URL can view every deployed file. Switching back to private blocks anonymous GET, HEAD, conditional, SPA-fallback and asset requests immediately.

Move a Site and all deployment metadata atomically with `POST /v1/sites/{siteId}/move` and `{"destinationProjectId":"..."}`. The destination may be in another workspace. The token needs `sites:write`, grants in both workspaces, and its user needs active editor/owner membership in both. IDs, public URL slug, storage keys, and private object bytes remain unchanged; no upload or object copy is required. On `409`, inspect the destination rather than choosing a new slug automatically.

## Publish small sites inline (preferred MCP path)

Call `publishSiteFiles` or POST `/v1/sites/{siteId}/deployments/inline` with the current `baseDeploymentId` (explicit null before first publication), a stable `idempotencyKey`, and the complete `files` array. Each file supplies `path`, supported `contentType` and exactly one of `contentText` (actual UTF-8 text, including empty text) or `contentBase64` (padded base64). Do not supply hashes or sizes: the server derives them, writes temporary objects and finalizes using the ordinary live-authority checks. The response contains only `site` and `deployment`, never signed upload instructions.

Limits are 50 files, 2 MiB decoded per file, 5 MiB decoded total and independently 8 MiB JSON including escaping. Include root `index.html`. These are transport ceilings, not recommendations to generate megabytes of base64 through a language model. Remote MCP cannot read local paths. Use inline text for small agent-generated pages and bulk HTTP for large or binary-heavy local sites. Package dependencies as same-origin assets; the server does not fetch external URLs or convert fonts.

```json
{"baseDeploymentId":null,"idempotencyKey":"landing-v1","files":[{"path":"index.html","contentType":"text/html; charset=utf-8","contentText":"<!doctype html><title>Hello</title><h1>Hello</h1>"}]}
```

Visibility is unchanged: new Sites stay private and already public Sites stay public. Change visibility separately only with explicit user intent. On an uncertain failure retry identical key, base and bytes, never silently adopt a new base. Finalized A replay after B returns A and current Site B without rewriting blobs or republishing A. Pending requests retain the original 30-minute deadline. Publication can fail after private temporary/promoted bytes have been written; retries converge but do not remove those bytes. Verify the returned Site URL before reporting success.

## Prepare a complete manifest (bulk HTTP)

GET `/v1/sites/{siteId}` immediately before planning a publication. Use its `currentDeploymentId` as the base, or explicit `null` if it has never published. Choose a fresh body `idempotencyKey` for this publication intent; preserve it and the exact manifest for retries. There is no idempotency header and no client-supplied manifest hash.

For each actual output file, calculate its exact byte length and lowercase SHA-256. Send `size`, not `bytes`. Every manifest is a complete replacement, not a patch. Limits: 1-500 files, 0-10 MiB per file, 100 MiB total. Include root `index.html`. Paths are relative printable ASCII, at most 240 characters and 32 segments, cannot begin with a dot, and have no encoded segments, traversal, query, fragment, backslash, empty segments or reserved storage prefixes. Spaces are permitted; browsers percent-encode them when fetching. The contract lists accepted MIME types; use the returned upload headers exactly.

```http
POST https://launchbase.remotely.works/v1/sites/{siteId}/deployments
Authorization: Bearer {agent-token}
Content-Type: application/json

{
  "baseDeploymentId": null,
  "idempotencyKey": "a-new-publication-id",
  "files": [
    {"path":"index.html","contentType":"text/html; charset=utf-8","size":123,"sha256":"{actual-64-character-lowercase-sha256}"},
    {"path":"app.js","contentType":"text/javascript; charset=utf-8","size":456,"sha256":"{actual-64-character-lowercase-sha256}"}
  ]
}
```

Replace the illustrative sizes and hashes with values from the actual bytes. New deployments return 201; identical retries return 200 with the original complete `deployment`. Both the canonical files and base must match the original key. Pending deployments have a fixed 30-minute deadline. GET metadata may still say `pending` after that deadline until an authorized mutation attempt records expiry; the deadline is never extended.

## Upload and publish

The response contains `uploads`. Each instruction has the manifest `path`, `method`, `url`, required `headers`, and `expiresAt`. Match by `path`, not array order or opaque object key. Send that file's bytes to the exact returned URL with the exact method/headers. Never add Launchbase Authorization or cookies to a storage request. Upload signatures expire within 15 minutes and never outlive the deployment deadline.

Only missing or mismatched files receive instructions. After a partial upload, request fresh instructions with an empty-body POST to `/v1/sites/{siteId}/deployments/{deploymentId}/uploads/refresh`. Already matching uploads are omitted. Retrying the original create request also resumes missing uploads. Preserve the original key and body after timeouts; do not mint duplicates. These responses are `no-store`.

Once every indicated upload succeeds, POST `/v1/sites/{siteId}/deployments/{deploymentId}/finalize` with the agent token and **no body**. The server verifies the complete manifest, promotes private immutable objects, rechecks live authority and atomically publishes the Site. The response contains complete `site` and `deployment` objects.

A finalized retry needs no temporary objects. If A was finalized and B is now current, retrying A succeeds with A and the current Site B; it never republishes A. Create replay of finalized A returns no upload instructions. A stale pending base returns `stale_base`: read the current Site, compare the concurrent change and decide whether to create a new publication with a new key and base. Do not force-publish or blindly change the old request. `deployment_expired` requires a new deployment/key; `upload_incomplete` requires refreshing and completing uploads. After 429, wait at least `Retry-After`; after an uncertain 5xx, inspect state and retry the same intent.

Keep only non-secret Site/deployment IDs, Site URL, idempotency key and manifest/file hashes in optional project-local state. Never store signed instructions there.

## Browser behavior and verification

Sites use a separate hostname from management. By default, opening a deployed Site redirects through Launchbase login and a short-lived one-use POST bridge, then establishes an HttpOnly credential scoped to that exact Site hostname. Any currently signed-in Launchbase user may view a private Site; workspace membership is not required. Logging out or revoking the management session immediately invalidates Site access. Private responses are `no-store`. Public Sites are anonymous and use mandatory cache revalidation. The host-only management session cookie is never widened or sent to user-controlled Site content.

Never upload agent tokens or unrelated secrets. A `public` Site must contain only data suitable for anonymous viewers. Inline scripts, external dependencies, workers (including service workers) and embedded objects are blocked by the default content-security policy. Put JavaScript in separate same-origin files; CSS may be inline or in same-origin files. The composed application permits browser connections to the fixed Setbase `/public/records` URL only, in addition to the Site's own origin.

For an explicitly requested Setbase public-intake frontend, the insert-only public capability is deliberately browser-visible. Use only that capability in `X-Setbase-Capability`, with `credentials: "omit"`; never an agent token. It cannot read records. Follow the Setbase skill for schema validation, CORS and capability revocation. Do not copy private Guidance or record responses into public assets automatically.

After finalize, open the returned `site.siteUrl` in a browser signed into Launchbase, without rewriting Host or dropping its port. Verify the entry document and an asset, their expected bytes/MIME types, private `no-store` caching, and absence of the management cookie or bearer credentials. For an explicitly public Site, repeat anonymously and expect mandatory revalidation. Test space-containing paths with ordinary URL encoding. Stable URLs require cache revalidation, so later publication/deletion is checked on subsequent requests. SPA fallback is opt-in and applies only to extensionless missing paths; missing assets remain 404. GET and HEAD are supported; ranges are not.

DELETE `/v1/sites/{siteId}` with no body immediately removes all Site access and reserves the slug; there is no undelete. Workspace archival and project disabling also block serving. Do not declare success from metadata alone. Hosted environment status, custom domains, operational metrics and storage cleanup are operator concerns not exposed by this API; background cleanup is not part of RC1.
