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_id for 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 SessionsAPI 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

ServiceURLPurpose
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:

  1. User signs in via Apple/Google/Facebook → gets JWT tokens
  2. App sends request with Authorization: Bearer <access_token>
  3. Service validates token with mushu-auth (or locally via JWT verification)
  4. Service processes request, scoped to the user's app

A typical request from a backend service:

  1. Server sends request with X-API-Key: msk_live_...
  2. Service validates key hash against mushu-core
  3. 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.