Publish with shiply (platform integration)
Embed a one-button "Publish with shiply" flow so your users publish into their own shiply account — and you earn 30% partner commission.
Platform Connect lets any tool — a page builder, an app generator, an agent IDE — embed a single "Publish with shiply" button. Clicking it sends the user through shiply's own signup/sign-in and consent screen, and hands your platform back an OAuth token scoped to only the sites it creates. The user owns the shiply account and pays shiply directly; you earn a recurring partner commission on every account you bring.
This is different from the MCP integration (that's for an agent acting on ITS OWN developer's shiply account) — Platform Connect is for a platform that wants to publish on behalf of its own end users, each into their own separate shiply account.
The one-line embed
<a href="https://shiply.now/connect/<slug>?response_type=code&redirect_uri=<your-callback>&code_challenge=<pkce-challenge>&code_challenge_method=S256&state=<state>">
Publish with shiply
</a>Replace <slug> with the slug shiply assigned your integration at approval
(see "Getting registered" below), <your-callback> with your app's OAuth
redirect URI (URL-encoded), <pkce-challenge> with a fresh RFC 7636 S256
code challenge, and <state> with an opaque value you'll verify on return.
client_id and scope are NOT query params here — the connect flow
resolves your registered client_id from <slug> and always grants the
platform:sites scope.
Styled button
<a
href="https://shiply.now/connect/<slug>?response_type=code&redirect_uri=<your-callback>&code_challenge=<pkce-challenge>&code_challenge_method=S256&state=<state>"
style="display:inline-flex;align-items:center;gap:8px;padding:10px 18px;
background:#111;color:#fff;border-radius:8px;font:600 14px system-ui;
text-decoration:none;"
>
Publish with shiply
</a>For agents integrating this: the whole flow in one paragraph
Send the user to the /connect/<slug> URL above. They sign up or sign in to
shiply (their own account, not yours) and approve a consent screen scoped
to "create sites in your shiply account and manage the sites it creates —
it cannot access your other sites, email, or billing." shiply redirects back
to your redirect_uri with ?code=...&state=.... Exchange that code at
POST /api/v1/oauth/token with your PKCE code_verifier (standard OAuth
2.1 authorization_code + PKCE — same endpoint the MCP OAuth
flow uses). You get back an access_token (shp_at_…) and a
90-day rotating refresh_token. From then on, send
Authorization: Bearer <access_token> on:
POST /api/v1/publish— create or update a site. A brand-new site is automatically stamped as "created by your platform"; updates to that same site keep working with the same token.GET /api/v1/sites/<slug>/versions— deploy history for a site your token created.GET /api/v1/me/entitlements— the connected user's plan (free/hobby/developer/studio) and feature flags, so you can gate your own UX (e.g. hide "add a custom domain" if they're onfree) without guessing from a 403.
The token can only touch sites it created — a call against any other site
in the user's account (or any account-level action: billing, email, other
API keys) returns 403 { "error": { "code": "platform_scope_forbidden" } }.
That's enforced server-side; there's nothing extra your integration needs
to do to respect it.
Getting registered
The fastest path is the self-serve Apply for a client_id
flow below — one POST, no email required. The steps here are the manual
equivalent (useful if you're coordinating a custom requiredPlan or other
detail directly):
- Call
POST /api/v1/oauth/register(DCR — the same endpoint MCP clients use) with yourredirect_uristo mint aclient_id. Noclient_secretis issued or needed; auth is PKCE-only. - Send Steve (stevejford1@gmail.com) your
client_id, display name, logo URL, and (optionally) a minimum required plan — he registers the integration and approves it. Once approved,/connect/<slug>goes live. - You earn 30% recurring partner commission on every account that converts to a paid plan through your button, for the standard commission window — the same partner program every shiply affiliate runs on.
Suspending an integration (e.g. for abuse) invalidates every outstanding token for it immediately — there's no stale-token window to worry about on either side.
Users can revoke you any time
Every connected user sees your integration at
shiply.now/dashboard/connected-apps and can revoke it with one click.
A revoked token stops working on its very next call; sites it already
published stay live and stay the user's.
See also MCP clients and Authentication for the user's app if your platform separately needs end-user auth on the sites it publishes.
Apply for a client_id (self-serve, no shiply account needed)
You don't need to email anyone to get started — apply directly:
POST https://shiply.now/api/v1/integrate/apply
Content-Type: application/json
{
"name": "<your platform name>",
"slug": "<your-slug>",
"redirect_uris": ["<your callback URL>"],
"logoUrl": "<optional logo URL>",
"use_case": "<optional: one line on what you're building>"
}This is public and rate-limited (no auth required). It mints an OAuth client via Dynamic Client Registration under the hood, then creates a pending integration and alerts an admin — you don't need to call the DCR endpoint separately. The response is:
201 { "client_id": "mcpc_...", "slug": "<your-slug>", "status": "pending" }slug must be 3-32 chars: lowercase letters, numbers, hyphens (not
leading/trailing) — it becomes your public shiply.now/connect/<slug> path.
A taken slug returns 409; pick another and retry. An admin reviews by
hand at /admin/platforms — you'll get an email on approval, and only then
does /connect/<slug> start resolving. Nothing above grants any privilege
by itself: the pending row can't authorize a token until it's approved.
If you'd rather skip the form, /integrate has a self-contained copy-paste brief for your coding agent that covers this whole flow — apply, embed, exchange, publish — in one block.
Embed the button
Once approved, drop the hosted button asset instead of building your own —
it's the exact button shiply's own docs and /integrate page use, matched
to shiply's brand mark and colors, and cached so it loads instantly:
<a href="https://shiply.now/connect/<slug>?response_type=code&redirect_uri=<your-callback>&code_challenge=<pkce-challenge>&code_challenge_method=S256&state=<state>">
<img
src="https://shiply.now/button/publish-with-shiply.svg"
alt="Publish with shiply"
height="40"
/>
</a>GET /button/publish-with-shiply.svg always returns 200,
content-type: image/svg+xml. Two query params customize it, and can be
combined:
?theme=dark— dark fill for dark-mode UIs (default is light).?label=integrate— swaps the copy to "Integrate with shiply" (default "Publish with shiply").
As always, build the href server-side with a fresh PKCE code_challenge
and state per render — the <img> src never changes, only the link
around it does.
Domain packs (for your users on a paid plan)
Each shiply plan ships with a base custom-domain allowance. If a connected
user needs more than their plan's base cap, they can add +25-domain
packs at $40/mo (or $400/yr on an annual plan) from their own
shiply.now/dashboard/money/plan page — this is a plan add-on the user
buys directly from shiply, not something your integration needs to expose
or bill for. Packs aren't available on the Free plan. Nothing on your side
changes: your token's custom-domain calls just start succeeding again once
the user's effective cap (plan base + packs × 25) covers their count.
The create-or-connect flow, from the user's side
/connect/<slug> is the same screen whether the person clicking your
button already has a shiply account or not — there's no separate
"new user" vs. "existing user" link for you to choose between:
- No shiply account yet → they sign up on the spot (email or OAuth), land straight on the consent screen, and approve. One flow, no detour through a generic signup page first.
- Already have a shiply account → they sign in and go straight to the same consent screen.
Either way you get the same ?code=...&state=... redirect back to your
redirect_uri, and the same token-exchange call at
POST /api/v1/oauth/token — your callback code doesn't need to know or
care which path the user took.