Docs

Putting an app on Makeship

Makeship hosts small web apps made of static files and gives each one a link, a database and sign-in for the people who use it. This page covers everything a creator needs. Makeship is in alpha, so a few pieces are marked as coming soon.

Getting started

An app on Makeship is a folder of static files with an index.html at the top: plain HTML, CSS and JavaScript, or the output of a static build (Vite, Astro, SvelteKit, Create React App). Makeship serves those files as they are. It never runs a build and never runs server code.

Each app lives at its own address, <app>--<handle>.makeship.app, where the handle is the one you pick when you sign up. Sign-up is open to anyone and only needs an email address you can confirm.

There are three ways to put an app online:

Use Makeship from Claude

The Makeship connector lets Claude build an app and put it online without a computer. Add it once and it works in Claude on iPhone, Android, the web and the desktop app.

Add the connector

  1. In Claude, open Settings, then Connectors, and choose Add custom connector.
  2. Name it Makeship and paste this address:

    https://mcp.makeship.ai/mcp

  3. Choose Connect. Claude opens a Makeship page: type the code we email you (this also creates your account if you don't have one, and asks you to pick a handle), then tap Allow.

On iPhone and Android: connectors you add in one place show up everywhere you're signed in to Claude. If your Claude app doesn't offer Add custom connector, add it at claude.ai in your phone's browser (or on a computer), then open the app. In a chat, make sure Makeship is switched on in the tools menu.

Claude's Free plan allows one custom connector. On a Team or Enterprise plan, an owner may need to add the connector for the organization first.

Just ask

  • "Make me a grocery list app and put it on Makeship."
  • "Add a quantity field to my grocery app."
  • "Which of my Makeship apps have the most visitors?"
  • "Roll my chore chart back to the last version."

What the connector can do

ToolWhat it does
get_makeship_guideTells Claude how a Makeship app is shaped: static files, box.collection, box.json and the limits.
start_app, write_files, edit_files, publish_appHow Claude builds on Makeship: building is deploying. Ask for an app and Claude opens a draft for it on Makeship, writes each file straight into it as it goes, then publishes it. Before anything goes live, Makeship tries the draft in a real browser; if something breaks, visitors never see it and Claude fixes the draft. Changing an app works the same way, writing only what changes. Drafts nobody touches for 14 days are dropped.
deploy_appTakes the app's files (up to 200 files and 5 MB per call), runs the same checks as every other upload, copies allowed CDN files into the app and deploys it. The first deploy of a name creates the app. Source that needs a build step is refused.
list_appsYour apps, their links, live versions and active visitors.
get_app_filesThe live version's files, so Claude can change an app it made in an earlier chat.
rollback_appMakes an earlier version live again (by default, the one before the current one).
app_statusChecks an app when something seems wrong: whether it's paused, full, out of this month's requests or storage, has a deploy waiting for review, or has open reports, and what to do about each.
open_ticketOpens a support ticket, or an appeal of a pause, with Makeship's team. Replies come by email and on the dashboard's Support page.

Signing in and out

The connector signs in with OAuth: Claude never sees your email code. Allowing it creates an API token called "MCP: Claude" on your dashboard's API tokens list. Revoke that token to disconnect Claude; it asks you to sign in again next time. If you stop using the connector, its sign-in expires after 90 days.

Upload a built app

First time: sign up and upload

Upload your app on the home page takes you through it in one go:

  1. Drop your app: a built folder, a zip of it, or a single HTML file (served as index.html). Give it a name.
  2. Confirm your email with the code we send.
  3. Pick your handle. Your app deploys and you get its link.

Your files stay in the browser tab until your email is confirmed, so nothing is uploaded for an address that isn't yours. If you already have an account, signing in works the same way, and Makeship asks before replacing an app that already has that name.

After that: drag and drop

The dashboard's home page and each app's page take a new version. Drop the folder your build makes (usually dist), a zip of it, or use Choose folder / Choose zip. The folder needs an index.html at its top.

  • node_modules, .git and system files are left out automatically.
  • Your browser checks the files first and lists what it finds. If you drop a project's source (a package.json with a build step), it tells you the build command to run, such as npm run build, and which folder to drop afterwards. Makeship never builds apps for you.
  • Items marked fix, such as a script from a CDN, can still be deployed with Deploy anyway, but they won't load until they're bundled (see CDNs).
  • The upload then gets the upload checks. You see the live link and any warnings, or the reason it was stopped. A refused upload never goes live and never creates an app.

Versions and rollback

Each app keeps its 5 newest versions plus the live one. An app's page lists them, and any of them can be made live again in one click.

App settings

Each app's page on the dashboard lets you:

  • Pause the app. Visitors see a "paused" page and new deploys are refused. Nothing is deleted.
  • Set the visitor cap: how many active visitors the app takes, up to your plan's limit.
  • Set the guest window: how long a guest can be away before they stop counting toward the cap (see visitors).
  • See visitors, newest first, and ban anyone. A ban blocks that visitor on every device and frees their seat. Or delete a visitor, which removes them and everything they created in the app, including shared documents. Deleting can't be undone.

Libraries and fonts from CDNs

For safety, an app on Makeship may only load files from its own address. Scripts, stylesheets, fonts, images and API calls pointing at other sites are blocked by the browser.

When you deploy with the Claude connector, Makeship handles the common case for you. Anything the app loads from these CDNs is copied into the app at deploy time and its links are rewritten:

  • fonts.googleapis.com
  • fonts.gstatic.com
  • cdnjs.cloudflare.com
  • cdn.jsdelivr.net
  • unpkg.com

Fonts a stylesheet refers to and modules a script imports are copied too, and integrity hashes are checked before they're dropped. Up to 80 copied files, 15 MB in all and 6 MB per file. Use exact versions in CDN links (react@18.3.1, not react).

  • A script or stylesheet from any other site is refused with a fix. For example, use https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4 instead of cdn.tailwindcss.com.
  • Images, API calls and embeds from other sites only get a warning, but won't load.

When you upload a folder from the dashboard, CDN files aren't copied yet: install the library with your package manager so your build bundles it, or save the file into your app's folder and link to it there.

React without a build

Claude on your phone can't run npm run build, and neither does Makeship. A single-page React app still works by loading React and Babel from unpkg and compiling JSX in the browser:

<script src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.26.4/babel.min.js"></script>

<div id="root"></div>
<script type="text/babel">
  function App() {
    const [items, setItems] = React.useState([]);
    React.useEffect(() => { box.collection("items").list().then(setItems); }, []);
    return <ul>{items.map((i) => <li key={i.id}>{i.name}</li>)}</ul>;
  }
  ReactDOM.createRoot(document.getElementById("root")).render(<App />);
</script>

Use the React and ReactDOM globals, not import, inside text/babel scripts. Deploy this through the connector so the three files get copied in.

Pictures in your app

Put your app's photos and pictures in a folder called images and Makeship sizes them for each visitor. A phone photo you drop in at full size is shrunk before it's sent, and browsers that can show AVIF or WebP get those smaller formats.

  • Without anything extra, a picture is sent at most 1920 pixels wide.
  • Add ?w= for a smaller size, for example <img src="/images/cat.jpg?w=400"> for a thumbnail. Widths round up to a set of common sizes, and a picture is never made bigger than it is.
  • PNG, JPEG, WebP and AVIF files up to 10 MB are resized. GIFs (so animations keep moving), SVGs and larger files are sent as they are.
  • Each size is made once and kept. Making one counts toward your plan's monthly image resizes (see limits). Past that, pictures are sent as uploaded until the next month, so nothing breaks.

Pictures anywhere else in your app are sent exactly as uploaded. Every file, including the originals in images, counts toward your upload size.

Saving data: the box SDK

Every page on Makeship already has box loaded; you don't add a script tag. The first time the app saves or reads data, the visitor gets a guest account automatically.

The same code runs anywhere. Away from Makeship (a Claude preview, a file opened on your laptop) box saves to the browser's localStorage instead, so you can try an app before it's online. box.mode is "platform" on Makeship and "local" everywhere else.

// a collection is a named list of documents
const items = box.collection("groceries");

const milk = await items.add({ name: "Milk", done: false });
// -> { id, createdAt, updatedAt, name: "Milk", done: false }

const todo = await items.list({
  where: { done: false },
  orderBy: "-createdAt",   // "-" for newest first
  limit: 50,
});

await items.update(milk.id, { done: true });   // shallow merge
await items.get(milk.id);                      // the document, or null
await items.remove(milk.id);

// live: called now, then whenever the list changes (anyone's change, in a shared collection)
const stop = items.subscribe((docs) => render(docs), { where: { done: false } });
CallDoes
box.collection(name)Opens a collection. Names use letters, numbers, - and _, up to 64 characters. Collections don't need creating first.
add(data)Saves a new document and returns it with id, createdAt and updatedAt added.
list(options)Returns documents, oldest first unless you set orderBy.
get(id)One document, or null.
update(id, patch)Merges the given fields into the document (top level only) and returns it.
remove(id)Deletes the document.
subscribe(callback, options)Calls callback with the documents (same options as list) right away and again whenever they change, including other visitors' changes to a shared collection. Returns a function that stops it. Add onError to the options to handle a failed refresh.

list options

  • where: up to 10 fields. A value matches exactly ({ done: false }); { in: [...] } matches any of up to 50 values; { gt, gte, lt, lte } compares numbers or strings.
  • orderBy: a field name, with - in front for descending. Defaults to createdAt.
  • limit: defaults to 100, at most 200.

Accounts

Every app shows a small account button in a corner. It opens a Makeship sheet where the visitor can add their email (to keep their account and open it on other devices), sign in, set a display name, sign out, delete their data, or report the app. The sheet is Makeship's own page, so the app can't change it.

const me = await box.account.current();
// -> { id, isGuest, email, displayName, isOwner }

box.account.open();                         // open the sheet from your own Settings button
box.account.onChange((me) => render(me));   // after a claim, sign-in, sign-out or new name; null once signed out

Styling the account button

Make the button fit your app. Each call changes only the options it names.

box.overlay.configure({
  position: "top-right",      // bottom-right (default), bottom-left, top-left, top-right
  offset: { x: 16, y: 12 },   // px from the corner, 0 to 200 (or one number for both)
  size: 40,                   // px, 32 to 72; 44 by default
  background: "#1d4ed8",       // any CSS colour
  color: "white",              // icon and label
  borderColor: "transparent",
  label: "Account",            // text next to the icon, up to 24 characters
  shadow: false,
});

Using your own button instead

Turn the floating button off and open the sheet from your own menu, header or settings page. Start with it off by adding this to your page's <head> (nothing flashes on screen first):

<meta name="box:account-button" content="off">

Then pick any of these as the way in:

<a href="/_box/account">My account</a>           <!-- opens the sheet; the full page if scripts are off -->
<button data-box-account>Settings</button>      <!-- any element with data-box-account -->
menu.onclick = () => box.account.open();         // from your own code

box.overlay.hide() and box.overlay.show() do the same from code. Visitors must always be able to reach the sheet, because that's where they delete their data and report the app. If you hide the button, keep a visible way in on every screen, such as a menu item. Makeship's deploy check warns when the button is off and nothing in the app opens the sheet.

  • isOwner is true when you, the app's creator, are signed in with the email of your Makeship account. Use it to show editing screens, and mark the collections only you may change as owner-write in box.json. Your dashboard's app page has a link that signs you in. You can always get in, even when the app is full.
  • If the app doesn't call onChange, the page reloads when the visitor switches to another account, so nobody sees the previous person's data.
  • Away from Makeship there is one local visitor, who counts as the owner, and no button.

Images visitors upload: box.files

For photos, avatars and other pictures, use box.files instead of putting images in documents:

const photos = box.files("photos");
const f = await photos.upload(input.files[0]);
// -> { id, url, name, type, size, createdAt, mine }
img.src = f.url;                              // served by Makeship from your app's address
await posts.add({ caption, photo: f.id });    // keep the id in a document
const all = await photos.list();              // newest first; { mine: true } for just the visitor's
await photos.remove(f.id);
  • Images only: PNG, JPEG, GIF, WebP or AVIF, up to 10 MB each. Makeship checks the file itself, so a renamed file is refused, and SVG isn't allowed because it can carry code.
  • Uploads count toward your plan's storage. A folder is private to each visitor unless box.json shares it under "files".
  • You can delete any image in your app when you're signed in to it. If visitors see each other's images, put a Report link next to them: /_box/report?file= plus the image's id.
  • Away from Makeship, uploads stay in the page's memory until it reloads.

Feedback from visitors: box.feedback

Give visitors a way to tell you what's broken or what they'd like. Call box.feedback from a form or button:

await box.feedback(text, { kind: "bug" });  // "feedback" (default), "bug" or "idea"
  • You read it on your app's Feedback page in the dashboard, and your AI agent can read it with the connector's read_feedback. Makeship sees it too.
  • Up to 4,000 characters and 10 messages a day per visitor. Pass email so they can hear back; a signed-in visitor's email is used if you don't.
  • When it isn't sent, it throws an error whose message says why, so show it.
  • It isn't for safety reports: those go to Makeship through the account sheet's Report.
  • Away from Makeship nothing is sent; the message shows in the browser console.

Good to know

  • Store one document per item, not one big array, so two people editing at once don't overwrite each other.
  • A document can be up to 64 KB, and an app can hold up to 10,000 documents. id, createdAt and updatedAt are set by Makeship and can't be written.
  • Every call returns a promise. A failed call throws an error whose message includes the reason, such as the app being full or the visitor saving too quickly.

Who sees what: box.json

By default each visitor only sees their own documents. To share a collection, add a box.json file next to index.html:

{
  "name": "Grocery list",
  "description": "One list for the whole house.",
  "collections": {
    "groceries": { "access": "shared" }
  }
}
AccessWho readsWho changesGood for
private (default)Only the visitor who made itOnly themJournals, personal trackers
shared-readEveryone in the appEach visitor edits and deletes their ownRecipe boards, sign-up sheets
sharedEveryone in the appEveryoneShared lists, chore charts
owner-writeEveryone in the appOnly you, signed in to the app with your Makeship emailMenus, announcements, content you publish

The same modes work for box.files folders: add "files": { "photos": { "access": "shared-read" } }. For files, "changes" means uploading and deleting, and you can delete any image.

description (up to 200 characters) shows on the label visitors see before the app opens and in link previews. Through the connector, Claude can pass the same settings as deploy_app's manifest.

Calling APIs: Connections

An app can call OpenAI, Anthropic, Google Gemini, Resend (email) or another API with your key, without the key ever being in the app. Paste the key on the app's Connections page in the dashboard (Claude's add_connection gives you a link to it), then call the API through Makeship:

const res = await box.fetch("openai", "/v1/chat/completions", {
  method: "POST",
  body: { model: "gpt-4.1-mini", messages, stream: true },
});
for await (const e of res.events()) { /* streamed as it arrives */ }

Makeship adds the key on the way out and only ever sends it to that API's own address. Only visitors with an account (guests included) can make calls.

Limits you set

Each Connection has a Limits page, so a busy app or one eager visitor can't run up your bill:

SettingWhat it doesDefault
Calls per visitor per hourEach visitor's calls in one clock hour30 for AI, 5 for email, 60 for custom
Calls per monthAll visitors together, reset on the 1st (UTC)3,000 for AI, 300 for email, 10,000 for custom
Max tokens per callA call asking for more is held to this2,048 for AI
Model, or From for emailEvery call uses it, whatever the app asks forThe app chooses
Other fixed fieldsAny top-level body fields to set on every call, as JSONNone

When a limit is reached, box.fetch throws an error with a code (visitor_limit or monthly_limit), a message you can show, and retryAfter in seconds. The API's own errors come back as a normal response. Outside Makeship, box.fetch throws not_on_boxfort, since your keys only live here.

Built-in AI: box.ai

Invite-only apps can ask an AI model a question on Makeship's own key, with no Connection or key of yours:

const { text } = await box.ai("Suggest three names for our book club");
const { data } = await box.ai({
  prompt: "Split this bill: " + bill,
  system: "You are a careful accountant.",
  schema: { type: "object", properties: { shares: { type: "array" } } },  // optional: answer parsed as data
});

It works only in invite-only apps, so only you and the people you invited can use it. Makeship picks the model and caps the length of each answer and the number of calls: a few an hour per visitor, and a monthly allowance shared by all your apps that's bigger on paid plans. When a limit is reached, or the app isn't invite-only, box.ai throws an error with a code (ai_visitor_limit, ai_monthly_limit, ai_invite_only) and a message you can show. Outside Makeship it throws not_on_boxfort. For another model or provider, use a Connection with your own key.

Notifications: box.notifications

Apps can send push notifications from one person to another: "your turn" in a game, "Sam added milk" on a shared list. They go to accounts, on every device where that person turned them on.

// From a button: browsers only ask after a tap
const state = await box.notifications.enable();  // "on", "off", "denied", "needs_install", "unsupported"
if (state === "needs_install") box.installHint.open();  // iPhone: add to the Home Screen first

// When it's someone else's move (their id from box.account.current())
await box.notifications.send(otherPlayerId, {
  title: "Your turn",
  body: "Sam played 7♦",
  url: "/game?id=42",   // the page a tap opens
  tag: "turn-42",       // replaces an older notification with the same tag
});

Reminders can wait until later and be cancelled when they're no longer needed:

// After a move: nudge the next player tomorrow unless they move first
box.notifications.schedule(next.id, { title: "Still your turn" }, { in: "24h", key: "remind-42" });
// When they move
box.notifications.cancel("remind-42");

Scheduling again with the same key replaces the waiting reminder. Reminders go off between 1 minute and 30 days later; each person can have 50 waiting.

On iPhone and iPad, notifications work only once the app is on the Home Screen and opened from there (Apple's rule); Android and desktop browsers work in a tab. Sending is best-effort: send says who was reached and who wasn't (not_enabled, limit, failed), and only throws for a malformed message. One call reaches up to 20 people; each person can send 120 an hour, and 30 an hour to the same person. Outside Makeship nothing is sent.

Converting an existing app

Most small apps made with Claude need only a few changes. Claude does these for you when you use the connector; if you're converting by hand:

  • localStorage or IndexedDB. These still work on Makeship, but the data stays in one browser. To share it or keep it across devices, save each item with box.collection(...).add() instead, and read with list().
  • Supabase. Keep your supabase.from(...) code and load Makeship's stand-in client instead: import { createClient } from "/_box/supabase.js" (links to supabase-js on a CDN are swapped for it automatically when you deploy). Each table becomes a collection; list shared tables in box.json. Filters, ordering, insert, update, upsert, delete, single() and realtime postgres_changes work. Joins inside select(), rpc(), Storage and edge functions don't, and sign-in is Makeship's own: auth.getUser() returns the visitor, and sign-in calls open Makeship's account sheet.
  • Firebase. Replace the calls with box.collection and remove the client library. Who-sees-what moves into box.json.
  • API keys in the code. Everything in an app is public, so uploads with a real key (OpenAI, Anthropic, Stripe, AWS, GitHub and others) are stopped. Remove the key, add it as a Connection, and call the API with box.fetch.
  • Server code (API routes, PHP, Python, cron jobs). Makeship doesn't run it. Move what you can into the browser and box.collection.
  • CDN scripts and fonts. See CDNs.
  • A build step. Run the build on your computer and upload the output folder, or ask Claude for a version with no build step.

Visitors and guest accounts

The first time someone opens an app, Makeship shows a label with the app's name, its description and your handle, plus a note that apps are made by individuals. The app can't change or skip that label. Link previews and search engines see the app's own page.

"Continue as guest" gives the visitor a guest account for that app, kept in a cookie in their browser. No password, no email. Each app has its own guests and its own data, so one app can't see another's.

From the account button, a guest can add their email with a 6-digit code. Their account and data stay the same, and they can open it on another device by signing in with the same email. "Continue with email" on the label does the same for a new visitor.

Invite-only apps

Turn on Invite-only on an app's dashboard page to keep it to people you choose. Only you and visitors who sign in with an email on the app's list get in. There are no guests: everyone else sees a page saying the app is invite-only, with your message if you add one, and none of the app's pages, files or data.

  • The list takes emails and groups. A group is a named list of emails on the dashboard's Groups page that you can pick on any of your apps. Turn on Automatically allowlist this group and it's on the list of every invite-only app you have, including future ones.
  • Default apps to requiring allowlists on the dashboard's home page makes new apps start invite-only. It's off unless you turn it on.
  • Sign-in codes are only sent to emails on the list, and the list is checked on every request. Removing someone stops their access on their next visit; their account and data stay, and come back if you add them again.

Who counts toward the cap

  • A guest counts toward the app's visitor cap while they're active. When the cap is reached, new visitors see that the app is full.
  • A guest who hasn't been back within the app's guest window (30 days unless you change it) stops counting, which frees their seat. Their data stays. If they come back they get everything back, and they're counted again as long as the app has room.
  • Banned visitors never count.

How long data is kept

  • Guest data is kept for at most 90 days of inactivity. A guest who hasn't opened the app for 90 days is deleted along with everything they created in it, including documents they added to shared collections. This limit is the same for every app and can't be changed.
  • Claimed accounts are permanent. A visitor who claims their account with an email address keeps it, and their data, until the app's creator deletes them from the dashboard, which also deletes everything they created in the app. Claimed accounts always count toward the cap.
  • Visitors can delete their own data. "Delete my data" in the account sheet removes the visitor and everything they created in the app straight away.

Upload checks and review

Every upload, from Claude, the dashboard or the API, is checked before anything goes live. Keys are always masked in the results.

ResultWhenWhat happens
StoppedAPI keys or private keys in the files; key or password files; server scripts or programsNothing goes live. You get the list of what to fix.
Held for reviewA password or card form together with signs of phishing (another company's name or look-alike, "your account has been suspended" wording, sending data to a chat bot, redirecting to a real sign-in page), or a page asking for a wallet recovery phrase. Also any upload whose address, or a site it links to, is on Google Safe Browsing's list of phishing or malware sitesThe current version stays live while a person reviews the upload.
WarningOne of those signs on its ownGoes live. The warnings are shown and kept with the version.

When an upload is held

A held upload is kept but not served. The Makeship team looks at it and either approves it, which makes it live like any other version, or rejects it with a note, which deletes its files. Either way you get an email. Your app's page on the dashboard shows held uploads and the reviewer's note, and the API lists them at GET /api/v1/apps/:name/holds. Uploading a newer version replaces a held one that's still waiting.

Honest apps with a sign-in form rarely get held. If yours is, write to us.

Plans and limits

Everyone is on Free during the alpha. Paid plans open with the public beta, and every plan has hard caps, so there's never a surprise bill.

LimitFreeMidPro
Apps31050
Active visitors per app25100500
Data and uploaded files, all apps together1 GB10 GB50 GB
One upload50 MB50 MB50 MB
App size, adding files in more uploads250 MB1 GB2 GB
Requests a month1M3M15M
Image resizes a month (images)5008002,000
Notifications a month, per app (notifications)5,00050,000250,000

Every upload is also limited to 2,000 files. When an app's data is full, adding or changing documents fails until something is deleted; deleting always works.

Graduated apps

Rarely, one app gets busier than a plan is built for. On Mid or Pro, an app can graduate: it stops counting toward your plan's limits and pays for what it uses instead. Most apps never need this, and nothing changes unless you choose it on the app's page in the dashboard. There's no way to do it from Claude or the API.

  • Cost. $10 a month for the app, plus $2 per million requests, $0.75 per GB-month stored, $2 per 1,000 sign-in emails and $1 per 1,000 image resizes, from the first one. It's billed on its own monthly invoice, on top of your plan.
  • Cap. You set the most its use can cost in a month. We email you at 50%, 80% and 100%. At the cap the app closes until the 1st (UTC) or until you raise it.
  • Welcome page. A graduated app with a custom domain may skip the welcome page there, and only there (invite-only apps always keep it). On its makeship.app address it always shows. Visitors then don't see your handle first, so your app should say what it is. If you also hide the account button, add a link with data-box-account so people can still sign in; otherwise a small Report link appears in the corner.
  • Same rules. Uploads are still checked and reports still come in. An app that breaks the rules is paused, however busy it is.
  • Going back. You can undo it any time if your plan has a free app slot. Ending Mid or Pro ends graduation too.

Deploy API and Claude Code

To deploy from a script, create a token under API tokens on the dashboard, zip your built folder and post it. The first deploy of a name creates the app.

# PowerShell
Compress-Archive -Path dist\* -DestinationPath app.zip -Force
Invoke-RestMethod -Method Post -Uri https://dashboard.makeship.ai/api/v1/apps/todo/deploys `
  -Headers @{ Authorization = "Bearer $env:MAKESHIP_TOKEN" } -ContentType application/zip -InFile app.zip

# macOS / Linux
(cd dist && zip -r ../app.zip .)
curl -X POST https://dashboard.makeship.ai/api/v1/apps/todo/deploys \
  -H "Authorization: Bearer $MAKESHIP_TOKEN" -H "Content-Type: application/zip" --data-binary @app.zip
RequestDoes
GET /api/v1/appsYour apps and their links
POST /api/v1/apps/:name/deploysUpload a zip and make it live after the checks. A stopped upload returns 422 with the list; a held one returns 422 with hold: { id, status }
GET /api/v1/apps/:name/deploysVersion history
POST /api/v1/apps/:name/rollbackMake an earlier version live: {"version": "v..."}
GET /api/v1/apps/:name/holdsHeld uploads with their review status and note
GET /api/v1/meWho the token belongs to
DELETE /api/v1/meRevoke the token used for the request

Claude Code and other tools that run on your computer can use the local Makeship MCP instead of the connector. It checks a project folder, runs its build, and uploads the output, signing in through a link you approve on the dashboard. Install steps will be here once it's published.

Help

Questions, a problem with your app, or something you'd like to see? Email hello@makeship.ai. To report an app that looks unsafe, use the report link at the bottom of its label, or email us.