Public API overview
When to use the REST API directly instead of the MCP surface.
Showly's public REST API exists for the cases the MCP surface doesn't fit: CI scripts, custom dashboards, third-party integrations that aren't agents.
If you're writing agent code, use MCP instead. The MCP surface gets first-class audit, scoped tokens, and tool-schema'd inputs. The REST API exposes the same actions but with looser semantics designed for human-written automation.
Base URL
https://api.showly.ai
All requests require TLS. HTTP requests are rejected at the load balancer.
Versioning
There is no /v1/ prefix and no Api-Version header today — the API is single-track. We treat any breaking change as a notable event: it lands in CHANGELOG.md at the top, with the date, the affected route, and a migration note. Watch the repo or subscribe to the release feed if your integration is load-bearing. Additive changes (new optional fields, new endpoints) ship without ceremony.
Authentication
Every request needs an Authorization: Bearer <token> header. The credentials you'll use:
- Personal access tokens (PATs) — prefix
sk_live_/sk_test_, issued from Profile → API tokens. Carry the user's roles across all workspaces they belong to. Convenient for ad-hoc scripts. - MCP tokens — prefix
mcp_live_/mcp_test_, issued from Workspace settings → MCP clients or minted by the device flow. Scoped to a single workspace and used by the MCP surface; they also authenticate REST calls.
For agent installs that can't paste a static token, Showly supports an RFC 8628 device-authorization flow (POST /oauth/device, then poll POST /oauth/token) that mints an MCP token bound to the authorizing user. See Authentication for the full flow and token rotation.
Response envelope
Every JSON response uses the same envelope. Successful responses:
{
"ok": true,
"data": { ... }
}
Errors:
{
"ok": false,
"error": { "code": "string", "message": "string" }
}
error.code is the stable contract — branch on it, not on error.message. Codes are kebab- or snake-cased identifiers (rate_limited, idempotency_key_conflict, not_found). Messages are intended for human readers and may change between releases.
Rate limits
Default: 60 requests/min per token (anonymous and Free tier), 300/min on Pro, 1000/min on Team, 5000/min on Enterprise. The source of truth is [apps/api/src/lib/rate-limit.ts](https://github.com/showly/showly-platform/blob/main/apps/api/src/lib/rate-limit.ts) — TIER_LIMITS for the global ceiling, ROUTE_LIMITS for hot routes with stricter per-route caps (deploy creation at 5/min/user, form submission at 30/min/IP, refunds at 3/min/user, and so on).
When rate-limited the server returns HTTP 429 with error.code: "rate_limited" and a Retry-After header carrying the back-off in seconds. Treat Retry-After as the canonical wait; do not retry sooner.
Idempotency
Creating a deployment — POST /sites/:siteId/deploy — requires an Idempotency-Key header. Without it the server returns 428. The key is your choice; we recommend a UUID per logical attempt. (Production publishes go through the same route with environment: "production" and a sourceDeploymentId, so they carry the same requirement.) No other route requires the header.
Replays of the same key return the cached response from the first call. If the request body changes under the same key, the server returns 409 with idempotency_key_conflict.
Read-only GET requests are naturally idempotent and ignore the header.
Pagination
List endpoints return at most 100 items per page. Pass ?cursor=... to fetch the next page:
GET /sites?limit=50&cursor=eyJpZCI6...
The response includes data.pageInfo.nextCursor (null when exhausted). Cursors are opaque — do not parse them.
Surfaces
- Authentication — token issuance and rotation.
- Sites — list, create, inspect sites.
- Deployments — create deployments, query status and history, stream pipeline events.