Architecture
Mushu is a multi-tenant backend-as-a-service. This page explains the core data model, how entities relate to each other, and how authentication flows through the system.
Data Model
User (person who signs in)
└── Organization (team or company)
├── App (your software application)
│ ├── Auth Providers (Apple, Google, Facebook config)
│ ├── API Keys (server-to-server access)
│ └── End Users (people who use your app)
└── Org Connections (linked external services) Organization
The top-level container. An organization represents a team, company, or project. Every resource in Mushu belongs to an organization.
- Has members with roles: owner, admin, member
- One owner per org (creator by default)
- A user can belong to multiple organizations
App
An app represents your software application — an iOS app, a web app, a backend service, etc. Apps belong to organizations.
Clarification: app_id in Mushu always refers to your
registered application in Mushu (e.g., your iOS app or web app). It does not
refer to external service accounts like a Facebook App or Ad Account. Those are
configured as connections or
auth providers.
Each app has:
- A unique
app_id(format:app_xxxxxxxx) - A
bundle_idfor iOS apps - Auth provider configurations (which sign-in methods are enabled)
- API keys for server-to-server access
- Its own pool of end users (users are scoped per app)
End Users
End users are the people who sign into your app. They authenticate via Apple, Google, or Facebook and get JWT session tokens. End users are scoped per app — a user who signs into App A is a separate record from a user who signs into App B, even if they use the same email.
Auth Providers
Per-app configuration for identity providers. Each app can enable Apple, Google, and/or Facebook sign-in with its own credentials. See Auth Tenants for setup details.
API Keys
API keys authenticate server-to-server requests. They belong to an app and have scopes (read, write, admin). See API Keys for details.
Connections
Connections link external services (like Facebook Ad accounts) to an organization. They store OAuth tokens server-side and auto-refresh them. See Connections.
Two Types of Authentication
Mushu has two distinct auth flows for different use cases:
| User Sessions | API Keys | |
|---|---|---|
| Who | End users (people) | Backend services (servers) |
| How | Apple/Google/Facebook sign-in | Static key in header |
| Lifetime | Access: 1hr, Refresh: 30 days | Never expires (until revoked) |
| Format | Authorization: Bearer <jwt> | X-API-Key: msk_live_... |
| Use case | Mobile/web app making requests on behalf of a user | Your server calling Mushu APIs |
Services
| Service | URL | Purpose |
|---|---|---|
mushu-core | core.mushucorp.com | Orgs, apps, API keys, membership |
mushu-auth | auth.mushucorp.com | Sign-in, sessions, auth providers, connections |
mushu-notify | notify.mushucorp.com | Push notifications (APNs), email (SES) |
mushu-media | media.mushucorp.com | Image/video hosting (Cloudflare R2) |
mushu-pay | pay.mushucorp.com | Stripe payments, prepaid wallet |
All services use DynamoDB for state and AWS Secrets Manager for credentials.
Services that need to verify user identity call mushu-auth to
validate tokens.
Request Flow
A typical request from a mobile app:
- User signs in via Apple/Google/Facebook → gets JWT tokens
- App sends request with
Authorization: Bearer <access_token> - Service validates token with mushu-auth (or locally via JWT verification)
- Service processes request, scoped to the user's app
A typical request from a backend service:
- Server sends request with
X-API-Key: msk_live_... - Service validates key hash against mushu-core
- Service processes request, scoped to the key's app
Rate Limits
Mushu does not currently enforce hard rate limits. Services are deployed on
AWS Lambda with auto-scaling. For high-frequency endpoints like
/auth/validate, consider caching the JWT validation result
client-side (JWTs are self-contained — you can verify the signature and
exp claim locally without calling Mushu).
Tip: If your backend validates tokens on every request,
parse the JWT locally instead of calling /auth/validate each time.
This avoids unnecessary network round-trips and scales better. See
Sessions for the JWT format.