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:
- From any Claude chat, including Claude on your phone: add the Makeship connector once, then ask Claude to put your app on Makeship.
- By dropping its folder on the Makeship dashboard. New here? Upload your app signs you up and deploys in one go.
- From a script or Claude Code, with the deploy API or the local Makeship MCP.
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
- In Claude, open Settings, then Connectors, and choose Add custom connector.
- Name it Makeship and paste this address:
https://mcp.makeship.ai/mcp
- 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
| Tool | What it does |
|---|---|
get_makeship_guide | Tells Claude how a Makeship app is shaped: static files, box.collection, box.json and the limits. |
start_app, write_files, edit_files, publish_app | How 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_app | Takes 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_apps | Your apps, their links, live versions and active visitors. |
get_app_files | The live version's files, so Claude can change an app it made in an earlier chat. |
rollback_app | Makes an earlier version live again (by default, the one before the current one). |
app_status | Checks 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_ticket | Opens 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:
- Drop your app: a built folder, a zip of it, or a single HTML file (served as
index.html). Give it a name. - Confirm your email with the code we send.
- 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,.gitand 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.jsonwith a build step), it tells you the build command to run, such asnpm 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@4instead ofcdn.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 } });
| Call | Does |
|---|---|
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 tocreatedAt.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.
isOwneristruewhen 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 asowner-writein 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
emailso 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,createdAtandupdatedAtare 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" }
}
}
| Access | Who reads | Who changes | Good for |
|---|---|---|---|
private (default) | Only the visitor who made it | Only them | Journals, personal trackers |
shared-read | Everyone in the app | Each visitor edits and deletes their own | Recipe boards, sign-up sheets |
shared | Everyone in the app | Everyone | Shared lists, chore charts |
owner-write | Everyone in the app | Only you, signed in to the app with your Makeship email | Menus, 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:
| Setting | What it does | Default |
|---|---|---|
| Calls per visitor per hour | Each visitor's calls in one clock hour | 30 for AI, 5 for email, 60 for custom |
| Calls per month | All visitors together, reset on the 1st (UTC) | 3,000 for AI, 300 for email, 10,000 for custom |
| Max tokens per call | A call asking for more is held to this | 2,048 for AI |
| Model, or From for email | Every call uses it, whatever the app asks for | The app chooses |
| Other fixed fields | Any top-level body fields to set on every call, as JSON | None |
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 withlist(). - 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 inbox.json. Filters, ordering,insert,update,upsert,delete,single()and realtimepostgres_changeswork. Joins insideselect(),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.collectionand remove the client library. Who-sees-what moves intobox.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.
| Result | When | What happens |
|---|---|---|
| Stopped | API keys or private keys in the files; key or password files; server scripts or programs | Nothing goes live. You get the list of what to fix. |
| Held for review | A 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 sites | The current version stays live while a person reviews the upload. |
| Warning | One of those signs on its own | Goes 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.
| Limit | Free | Mid | Pro |
|---|---|---|---|
| Apps | 3 | 10 | 50 |
| Active visitors per app | 25 | 100 | 500 |
| Data and uploaded files, all apps together | 1 GB | 10 GB | 50 GB |
| One upload | 50 MB | 50 MB | 50 MB |
| App size, adding files in more uploads | 250 MB | 1 GB | 2 GB |
| Requests a month | 1M | 3M | 15M |
| Image resizes a month (images) | 500 | 800 | 2,000 |
| Notifications a month, per app (notifications) | 5,000 | 50,000 | 250,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-accountso 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
| Request | Does |
|---|---|
GET /api/v1/apps | Your apps and their links |
POST /api/v1/apps/:name/deploys | Upload 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/deploys | Version history |
POST /api/v1/apps/:name/rollback | Make an earlier version live: {"version": "v..."} |
GET /api/v1/apps/:name/holds | Held uploads with their review status and note |
GET /api/v1/me | Who the token belongs to |
DELETE /api/v1/me | Revoke 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.