Blog
Headless CMS for mobile apps: one REST API for content and user sessions
Mobile apps that show CMS-driven content often need logged-in users too: profiles, saved items, member-only articles, or in-app purchases tied to an account. The common pattern is a headless CMS for content plus Clerk, Auth0, or Firebase Auth for identity. That works, but it adds integration work on every new app.
NomaCMS is a headless CMS with auth built in. Your mobile client talks to one API base (https://app.nomacms.com/api) for both published content and project user sessions. This post explains how that fits a typical mobile stack and which credentials to use where.
What mobile apps usually need from a CMS
| Need | Typical approach | NomaCMS approach |
|---|---|---|
| Articles, settings, media | Content API | Collections, fields, assets over REST |
| User signup and login | Separate auth vendor | Project auth (/auth/* routes) |
| Member-only content | Custom backend glue | Session tokens + your app logic |
| Images and files | CDN URLs from CMS | Asset library with CDN delivery |
Flutter, Swift, Kotlin, and React Native all work the same way here: HTTP requests with JSON. You do not need the JavaScript SDK on the device. Use it on a backend if you prefer, or call REST directly from the app through a thin API layer you control.
Two kinds of credentials (do not mix them up)
This trips up a lot of teams on day one.
Personal access token (your developer account)
- Created in User Settings → API Keys in the NomaCMS dashboard
- Used for Content API routes: collections, entries, assets, webhooks
- Scoped to a workspace; create the key while that workspace is selected
- Copy the full token when created (shown once; format is assigned by the dashboard)
- Never ship this in a mobile app binary. Keep it on your server or in CI
Project user access token (your app's end users)
- Issued after signup or login on
/auth/signupand/auth/login - A JWT that identifies one user in one project
- Used for
/auth/me, logout, profile, and user API key management - Not a substitute for a personal access token on Content API routes
Same email in two projects means two separate accounts. Auth is isolated per project.
Fetch published content from a mobile app
The safest pattern: your mobile app calls your backend, and your backend calls NomaCMS with a personal access token.
Example server route (Node/Express style):
// Your API: GET /api/posts
const response = await fetch("https://app.nomacms.com/api/posts?state=published", {
headers: {
"project-id": process.env.NOMA_PROJECT_ID!,
Authorization: `Bearer ${process.env.NOMA_API_KEY!}`,
Accept: "application/json",
},
})
const posts = await response.json()Every Content API request needs:
project-id: <your-project-uuid> Authorization: Bearer <personal-access-token> Accept: application/json
Replace posts with your collection slug. Filter with query parameters your app needs (state=published, locale, paginate, and so on). See List Entries for the full list.
If you truly need the app to read public content without your server in the middle, treat that as a product decision. A read-only key still belongs in a proxy you control, not hard-coded in the app store build.
Sign up and sign in from mobile
Project auth routes live under /auth/ on the same API base.
Sign up
POST https://app.nomacms.com/api/auth/signup
project-id: <your-project-uuid>
Content-Type: application/json
{
"email": "[email protected]",
"password": "at-least-8-chars",
"display_name": "Alex"
}A successful response returns access_token, optional refresh_token, and a user object. Some projects require email verification first. In that case you get 202 Accepted with verification_required: true instead of tokens. Your app handles the verification flow.
Sign in
POST https://app.nomacms.com/api/auth/login
project-id: <your-project-uuid>
Content-Type: application/json
{
"email": "[email protected]",
"password": "at-least-8-chars"
}Store tokens in the platform secure store (Keychain on iOS, EncryptedSharedPreferences or Keystore on Android). Refresh before expiry:
POST https://app.nomacms.com/api/auth/refresh
project-id: <your-project-uuid>
Content-Type: application/json
{
"refresh_token": "<stored-refresh-token>"
}Social login
Google sign-in is supported today via OIDC id_token exchange:
POST https://app.nomacms.com/api/auth/login/social
project-id: <your-project-uuid>
Content-Type: application/json
{
"provider": "google",
"id_token": "<id-token-from-google>"
}Complete the OAuth flow in the mobile SDK, then send the id_token to NomaCMS from your backend when possible.
Load the signed-in profile
GET https://app.nomacms.com/api/auth/me
project-id: <your-project-uuid>
Authorization: Bearer <project-user-access-token>
Accept: application/jsonUse this on app launch to restore session state.
User API keys (for your own backends, not Content API)
Signed-in users can create user API keys (uak_ prefix) for integrations you build. These are not Content API tokens. Sending a uak_ key to collection or entry routes returns 401.
List and create keys while authenticated as the project user. Store plain_text_key securely when created; it is only shown once.
Assets and images
Upload and list assets through the Content API at /api/files (from your server with a personal access token). Responses include url, thumbnail_url, and original_url you can render in mobile image views. See List Assets and Upload an Asset.
When a hosted CMS + auth beats stitching vendors
Consider NomaCMS for mobile when:
- You ship content-driven apps (news, courses, community, catalogs) and need accounts
- You want one API contract for your iOS and Android teams
- You prefer a hosted backend over running Strapi or Payload yourself
- You are an indie team that cannot afford a CMS bill plus a separate auth bill on every prototype
You may still want a dedicated auth vendor if you need enterprise SSO across many products. For many indie and small product teams, project auth in the same platform is enough to ship.
What to explore next
Every plan includes a 7-day free trial.
Learn more
- REST API authentication
- User API keys
- JavaScript SDK (optional on your backend)