API reference
REST surface under https://flow.appmate.cloud/api/v1/*. Bearer token auth — issue a token at flow.appmate.cloud/admin/settings/api-tokens. The MCP server in fil-technology/appmate-mcp is a typed wrapper around this surface — under the hood it calls these same endpoints.
Authentication
Every request needs an Authorization: Bearer amk_... header. Tokens look like amk_<8-char prefix>_<32-char secret> and are shown once on creation — we store only the bcrypt hash. Revoke from the dashboard.
curl https://flow.appmate.cloud/api/v1/apps \ -H "Authorization: Bearer amk_abc12345_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
id (cuid) or its slug. Use whichever you have on hand — the resolver accepts both.Apps
List apps
GET /api/v1/apps
→ 200 OK
{ "apps": [ { "id", "name", "slug", "bundleId", "deepLinkScheme", "logoUrl", "createdAt", "updatedAt" } ] }Get one app
GET /api/v1/apps/{idOrSlug}
→ 200 OK
{ "app": { ... } }Create an app
POST /api/v1/apps
Content-Type: application/json
{
"name": "QuakeMate",
"slug": "quakemate", // optional; auto-slugged from name if omitted
"bundleId": "com.example.quakemate", // optional
"deepLinkScheme": "quakemate", // optional
"logoUrl": "https://example.com/logo.png" // optional
}
→ 201 Created
{ "app": { ... } }Cancel flow
Get the flow
GET /api/v1/apps/{idOrSlug}/flows/cancel
→ 200 OK
{
"flow": { "id", "type", "slug", "status", "currentVersionId" },
"draft": { "versionNumber", "config": { ... } },
"published": { "versionNumber", "publishedAt", "config": { ... } } | null
}Update the draft
Body is the full config JSON (not wrapped in a config key). Validates with the same Zod schema the editor uses; rejects with 422 on any issue. See the deep-link contract for the action shapes.
PUT /api/v1/apps/{idOrSlug}/flows/cancel
Content-Type: application/json
{
"type": "cancel",
"intro": { "title": "Before you go", "subtitle": "…", "primaryButton": "Continue", "secondaryButton": "Skip" },
"reasonScreen": { "title": "Why?", "subtitle": "", "reasons": [ { "id": "too_expensive", "label": "Too expensive", "iconName": "DollarSign" } ] },
"responses": { "too_expensive": { "title": "20% off?", "body": "…", "primaryButton": { "label": "Claim", "action": "open_offer", "offerId": "save_20" } } }
}
→ 200 OK { "ok": true }
→ 422 { "error": "<paths>: <messages>" }Publish the draft
POST /api/v1/apps/{idOrSlug}/flows/cancel/publish
→ 200 OK { "ok": true, "version": 4 }
→ 422 { "error": "draft is invalid: ..." }Waitlist flow
Same shape conventions as cancel — GET / PUT / POST publish at /flows/waitlist. The PUT body is the full waitlist config; the optional heroblock drives the public landing page's visual treatment.
GET /api/v1/apps/{idOrSlug}/flows/waitlist→ flow, draft, publishedPUT /api/v1/apps/{idOrSlug}/flows/waitlist→ full waitlist config (see schema below)POST /api/v1/apps/{idOrSlug}/flows/waitlist/publish→ promotes the draft
Waitlist config schema
{
"type": "waitlist",
"intro": {
"title": "Get AppMate Pro early",
"subtitle": "Unlimited apps, custom domains, webhooks…",
"emailPlaceholder": "you@company.com",
"submitLabel": "Notify me",
"legal": "One email when Pro launches." // optional
},
"success": {
"title": "You're on the list.",
"body": "We'll email you the moment Pro is ready.",
"ctaLabel": "Back to the dashboard", // optional, paired with ctaUrl
"ctaUrl": "https://flow.appmate.cloud/login" // both-or-neither
},
// EVERYTHING below is optional — omit `hero` for the minimal look.
"hero": {
"theme": "gradient", // minimal | gradient | dark | side_by_side
"eyebrow": "Coming soon", // chip above title (max 40 chars)
"accentColor": "#7c3aed", // hex; tints button + eyebrow + gradient blob
"showCount": true, // shows "{N} on the waitlist" pill (hides if <3)
"heroImage": "https://…", // optional hero image URL
"bullets": [ // 0–5 value-prop cards
{ "icon": "📈", "title": "Unlimited apps", "body": "Across your portfolio." },
{ "icon": "🪝", "title": "Webhooks + API" }
]
},
"templateId": "feature_tease" // optional analytics tag
}Theme picker guide: minimal = clean white card (default, good for B2B). gradient = pastel blobs + accent (marketing launch). dark = dark hero + accent glow (product reveal). side_by_side = 2-column desktop layout (story left, form right), collapses to minimal on phone.
Starter templates that pre-fill the whole hero block: minimal_email_only, feature_tease, launching_soon, pro_upsell, private_beta, early_access_referral. Browse them at /examples?kind=waitlist.
Waitlist signups
List signups (paginated)
GET /api/v1/apps/{idOrSlug}/waitlist/signups?limit=50&cursor=<id>
→ 200 OK
{
"signups": [ { "id", "email", "source", "country", "userAgent", "createdAt" } ],
"nextCursor": "<id>" | null
}Pass nextCursor back as cursor on the next call. Page size is capped at 200. country is a 2-letter ISO code (when the edge sent one) and userAgent is the raw UA string — both nullable. Captured at submit time for analytics; the raw client IP is never stored.
Export as CSV
GET /api/v1/apps/{idOrSlug}/waitlist/signups.csv
→ 200 OK
Content-Type: text/csv
id,email,source,country,user_agent,createdAt
…Feedback flow
Hosted feedback form at appmate.cloud/feedback/{appSlug}. Optional 1–5 star rating + free-text message + optional reply email.
GET /api/v1/apps/{idOrSlug}/flows/feedback→ flow, draft, publishedPUT /api/v1/apps/{idOrSlug}/flows/feedback→ full feedback config (see schema below)POST /api/v1/apps/{idOrSlug}/flows/feedback/publish→ promotes the draftGET /api/v1/apps/{idOrSlug}/feedback/submissions→ paginated submissions (same cursor shape as waitlist signups)
Feedback config schema
{
"type": "feedback",
"intro": {
"title": "Send us feedback",
"subtitle": "We read every message.",
"messagePlaceholder": "What's on your mind?",
"submitLabel": "Send feedback",
"legal": "Optional small print" // optional
},
"rating": { // optional
"enabled": true,
"prompt": "How would you rate your experience?", // optional
"required": false // optional
},
"emailField": { // optional
"enabled": true,
"placeholder": "you@example.com (optional)", // optional
"required": false // optional
},
"success": {
"title": "Thanks — we got it.",
"body": "Your feedback is in front of the team."
},
"hero": { "theme": "minimal", "accentColor": "#7c3aed" } // optional
}Report flow
Hosted bug/abuse/spam report form at appmate.cloud/report/{appSlug}. Required category picker (1–10 entries) + free-text message + optional reply email.
GET /api/v1/apps/{idOrSlug}/flows/report→ flow, draft, publishedPUT /api/v1/apps/{idOrSlug}/flows/report→ full report configPOST /api/v1/apps/{idOrSlug}/flows/report/publish→ promotes the draftGET /api/v1/apps/{idOrSlug}/report/submissions→ paginated submissions. Optional?category={id}filter to scope to one bucket.
Report config schema
{
"type": "report",
"intro": {
"title": "Report an issue",
"subtitle": "Tell us what went wrong.",
"messagePlaceholder": "What happened?",
"submitLabel": "Submit report",
"legal": "Optional small print" // optional
},
"categories": [ // 1–10 entries
{ "id": "bug", "label": "Bug or crash", "emoji": "🐞" },
{ "id": "abuse", "label": "Harassment or abuse","emoji": "🚫" },
{ "id": "spam", "label": "Spam", "emoji": "🧹" },
{ "id": "privacy", "label": "Privacy concern", "emoji": "🔒" },
{ "id": "other", "label": "Something else", "emoji": "💬" }
],
"emailField": { // optional
"enabled": true,
"placeholder": "you@example.com (optional)",
"required": false
},
"success": {
"title": "Report received.",
"body": "Thanks for letting us know."
},
"hero": { "theme": "minimal", "accentColor": "#dc2626" } // optional
}categories; the public submit endpoint rejects unknown categories with 422.Errors
401— missing or invalid Bearer token. Header includesWWW-Authenticate: Bearer realm="AppMate API".404— unknown{appId}.409— slug collision on create.422— config failed validation. Body{ error: "..." }describes the failing paths.
Versioning
This is /api/v1/*. Breaking changes will land at /api/v2/*; v1 stays put. Tokens work against either version when v2 ships.