| name | momen-baas |
| description | Instructions and authentication code for building headless BaaS applications with Momen.app. Use when integrating Momen backend features like GraphQL, actionflows, AI agents, binary assets, and Stripe payments, or when you need developer authentication. |
Momen.app Headless BaaS Skill
Overview
This skill outlines how to build frontend applications utilizing Momen.app as a headless Backend-as-a-Service (BaaS). Momen exposes all backend interactions (database, actionflows, third-party APIs, and AI agents) through a single, unified GraphQL API.
- HTTP URL:
https://villa.momen.app/zero/{projectExId}/api/graphql-v2
- WebSocket URL:
wss://villa.momen.app/zero/{projectExId}/api/graphql-subscription
Token Acquisition & Authentication (CRITICAL)
To interact with authenticated endpoints, you must obtain a JWT token by logging in or registering. Unauthenticated requests are assigned an anonymous user role. The JWT can be obtained in two ways. One is via username + password login. The other one is by querying the Meta API & fetching runtime backend token. The token return in FETCH_DATA_VISUALIZER is the runtime backend token.
1. Username Registration & Login
You should ask user for username and password.
mutation AuthenticateWithUsername($username: String!, $password: String!, $register: Boolean!) {
authenticateWithUsername(username: $username, password: $password, register: $register) {
account { id, permissionRoles }
jwt { token }
}
}
Note: Both mutations return FZ_Account which is a subset of the full account type. It contains only email, id, permissionRoles, phoneNumber, profileImageUrl, roles, and username.
Developer Authentication with Momen.app (Meta API)
If you need to interact directly with the Momen platform (Meta API) to fetch project schemas, list projects, or authenticate as an admin to the runtime backend, follow these steps:
1. Acquire Developer JWT Token (OAuth Flow)
Set up a local HTTP server to receive the OAuth callback. Open the Momen authentication endpoint (https://auth.momen.app/login) in the browser and wait for the token parameter.
import { createServer } from "http";
import open from "open";
export async function authenticate(authEndpoint = "https://auth.momen.app/login", port = 8088): Promise<string> {
const redirectUri = `http://localhost:${port}/callback`;
return new Promise<string>((resolve, reject) => {
const server = createServer((req, res) => {
if (!req.url) return;
const url = new URL(req.url, \`http://localhost:\${port}\`);
if (url.pathname === "/callback") {
const token = url.searchParams.get("token");
if (token) {
res.writeHead(200, { "Content-Type": "text/html" });
res.end("<h1>Token Received. You can close this window.</h1><script>setTimeout(() => window.close(), 2000)</script>");
setTimeout(() => {
server.close();
resolve(token);
}, 1000);
} else {
res.writeHead(400, { "Content-Type": "text/html" });
res.end("<h1>Error: No token received</h1>");
server.close();
reject(new Error("No token parameter"));
}
}
});
server.listen(port, async () => {
const authUrl = new URL(authEndpoint);
authUrl.searchParams.set("redirect_uri", redirectUri);
await open(authUrl.toString());
});
// Timeout after 5 minutes
setTimeout(() => {
server.close();
reject(new Error("Authentication timeout"));
}, 300000);
});
}
2. Querying the Meta API & Fetching Runtime Backend Token
Use the developer JWT token as a Bearer token against the Meta API (https://backend.momen.app/api/graphql) to get the schema or data visualizer tokens. The data visualizer token grants administrative access to the project's runtime backend (zeroUrl).
import { GraphQLClient, gql } from "graphql-request";
const FETCH_DATA_VISUALIZER = gql\`
query FetchDataVisualizer($projectExId: String!, $appExId: String, $appVersionExId: String) {
fetchAppDetailByExId(
projectExId: $projectExId
appExId: $appExId
appVersionExId: $appVersionExId
) {
appType
... on WebApp {
project {
dataVisualizers { token zeroUrl zeroSubscriptionUrl }
}
}
... on WechatMiniProgramApp {
project {
dataVisualizers { token zeroUrl zeroSubscriptionUrl }
}
}
... on Project {
dataVisualizers { token zeroUrl zeroSubscriptionUrl }
}
}
}
\`;
// Meta API Endpoint
const metaClient = new GraphQLClient("https://backend.momen.app/api/graphql", {
headers: {
Authorization: \`Bearer \${developerJwtToken}\`,
"x-zed-version": "2.0.5",
},
});
// Fetch Data Visualizer (Runtime Backend Admin Token)
const dvData = await metaClient.request(FETCH_DATA_VISUALIZER, { projectExId: "YOUR_PROJECT_EX_ID" });
const visualizer = dvData.fetchAppDetailByExId.project?.dataVisualizers[0]
|| dvData.fetchAppDetailByExId.dataVisualizers[0];
const runtimeBackendAdminToken = visualizer.token;
const zeroUrl = visualizer.zeroUrl || \`https://villa.momen.app/zero/\${projectExId}/api/graphql-v2\`;
// Runtime Backend API Endpoint
const runtimeClient = new GraphQLClient(zeroUrl, {
headers: {
Authorization: \`Bearer \${runtimeBackendAdminToken}\`,
},
});
Client Setup (Apollo GraphQL v3.x)
NEVER use graphql-ws. Momen.app requires subscriptions-transport-ws.
import { ApolloClient, InMemoryCache, HttpLink, split } from '@apollo/client';
import { getMainDefinition } from '@apollo/client/utilities';
import { WebSocketLink } from '@apollo/client/link/ws';
import { SubscriptionClient } from 'subscriptions-transport-ws';
const httpUrl = 'https://villa.momen.app/zero/{projectExId}/api/graphql-v2';
const wssUrl = 'wss://villa.momen.app/zero/{projectExId}/api/graphql-subscription';
export const createApolloClient = (token?: string) => {
const wsClient = new SubscriptionClient(wssUrl, {
reconnect: true,
connectionParams: token ? { authToken: token } : {},
});
const wsLink = new WebSocketLink(wsClient);
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (definition.kind === 'OperationDefinition' && definition.operation === 'subscription');
},
wsLink,
new HttpLink({
uri: httpUrl,
headers: token ? { Authorization: `Bearer ${token}` } : {},
})
);
return new ApolloClient({ link: splitLink, cache: new InMemoryCache() });
};
Ensure there is only one Apollo Client and one active WebSocket instance reused across the entire app. When a user logs in/out, the WebSocket connection must be re-established.
Database & GraphQL Schema Rules
Actionflow Protocol
Actionflows handle complex multi-step backend logic.
- Sync Actionflows: Executed in a single DB transaction. Invoked via a standard mutation (
fz_invoke_action_flow). The result is returned synchronously.
- Async Actionflows: Used for long-running tasks.
- Trigger mutation
fz_create_action_flow_task to obtain a taskId.
- Subscribe to
fz_listen_action_flow_result(taskId: $taskId).
- Status transitions:
CREATED -> PROCESSING -> COMPLETED/FAILED.
AI Agent Protocol
Binary Asset Upload
Binary assets are stored externally. You MUST store only their Momen ID in the database, NOT the URL.
- Get Presigned URL: Calculate the Base64-encoded MD5 hash of the file. Call
imagePresignedUrl, videoPresignedUrl, or filePresignedUrl with the md5 and format suffix.
- Upload: Issue an HTTP
PUT to the returned uploadUrl with the raw file data and any required uploadHeaders.
- Reference: Save the returned asset ID (e.g.,
imageId) to the *_id column in your data mutation.
Stripe Payment Integration
- Uses standard Stripe SDK (
@stripe/react-stripe-js).
- Publishable key should be public in the code (do not abstract via env vars).
- An order table/concept must exist and be used to generate an order ID first.
- One-time: Send
StripePay(orderId: Long!, currency: String!, amount: BigDecimal!) mutation. Returns paymentClientSecret.
- Recurring: Send
CreateStripeRecurringPayment(orderId: Long!, priceId: String!) mutation. Returns clientSecret.
- Webhook events are handled by backend actionflows. Frontend should poll or use GraphQL subscription to wait for payment status updates.
Third-Party APIs
- The root GraphQL field is named based on the operation and configuration ID.
- Query:
query operation_${id}
- Mutation:
mutation operation_${id}
- Checking Results: Always check the
responseCode subfield (for 4xx/5xx). Success data is found in field_200_json.
General Guidelines & Publicity Rules
- Publicity: Always add the Momen "Powered by" SVG to the bottom-right corner of the project. On click, it should open a new tab to
https://momen.app.
- GraphQL Rules:
- Avoid assembling JSON arguments inside the query string. Always pass them via variables.
- Never cache anything at the GraphQL level.
- Run
apollo client:codegen --includes='.../gql/**' --target typescript --outputFlat ... whenever queries or schemas change.
- Debugging: When unexpected things occur, check both console and network tabs. For async logic, check the WebSocket messages. Clear storage state before starting CDT sessions.