Mobile app backend

A backend for a mobile app: sign-in, push notifications with action buttons, Apple in-app purchases and file uploads, all from one data model.

Last updated

A mobile app needs the same few things from its backend: a way to sign people in, somewhere to keep each person's data, a way to reach them when the app is closed, a way to charge them, and somewhere to put their photos. Anythink gives you each of these in one project, so the app talks to a single REST API with a single session.

This page builds a small iOS and Android fitness app end to end: native Apple and Google sign-in, a workouts entity that each user sees only their own records of, a push notification with action buttons sent from a workflow, an Apple in-app subscription, and photo uploads. Each step shows the Anythink dashboard, the CLI and an AI assistant (MCP). Every step is short and links to the guide that covers it in full.

If you haven't used Anythink before, read Core concepts first.

Who it's for and what you'll build#

This is for a team building a native or cross-platform app (Swift, Kotlin, Flutter, React Native) that wants a managed backend rather than writing and hosting its own. You write the app. Anythink holds the data, the users, the sessions, the push delivery and the purchase verification.

App needs Anythink provides Guide
Sign in Native Apple and Google token exchange, plus email and password. One session for every method. Social sign-in
Per-user data An entity per record type, with row-level security so each user reaches only their own records. Groups and row-level security
Push Firebase Cloud Messaging delivery, device registration, and a workflow step that sends. Action buttons can start another workflow. Push notifications
Paid features Apple in-app purchase verification and one entitlement call. Apple in-app purchases
Files A file field on any entity, uploaded straight from the app. Upload and manage files

The architecture is plain request and response:

  1. The app signs in with Apple or Google on the device and posts the identity token to your project. It gets back an access token and a refresh token.
  2. Every later call carries Authorization: Bearer <access token>. Role permissions and row-level security are applied to that user on each request.
  3. After sign-in, the app registers its push token. A workflow sends notifications to a user's devices, and a button tap is reported back to start another workflow.
  4. After a purchase, the app sends the signed StoreKit 2 transaction to your project, then asks one entitlement question to decide what to unlock.
  5. Photos upload to the project's file storage and attach to the user's record.

Step 1: Model your data#

Start with the records your app keeps. The fitness app has one entity, workouts.

Entity Field Type Notes
workouts title Small text Required. The name field.
workouts notes Large text Optional.
workouts completed_at Timestamp When the user finished it.
workouts photo File Upload A progress photo. Used in step 6.

Switch on row-level security when you create the entity. Step 3 explains why it matters, and it's easiest to set before any data exists.

In the Anythink dashboard

  1. Go to Settings › Data Model and select Add Entity. Name it workouts.
  2. Tick Enable Row-Level Security and select Create Entity.
  3. Select Add Field and add title (Small text, tick Required and Name field), notes (Large text), completed_at (Timestamp) and photo (File Upload).

With the CLI

bash
anythink entities create workouts --rls
anythink fields add workouts title --type varchar --required --name-field
anythink fields add workouts notes --type text
anythink fields add workouts completed_at --type timestamp
anythink fields add workouts photo --type file

With an AI assistant (MCP)

text
Create an entity called workouts with row-level security on. Add a required title (name field), a large text notes field, a timestamp called completed_at and a file field called photo.

The assistant runs entities create and one fields add per field through the cli tool. Connect an AI assistant sets up the MCP server. For every field type and option, see Model your data.

Step 2: Sign in with Apple and Google natively#

On the device, the provider's own SDK shows its sign-in sheet and hands your app a signed identity token. Your app posts that token to Anythink, which checks it against the provider's published keys and signs the user in. There's no web view, no redirect and no client secret in the app.

Anythink needs to know which apps to trust. Native Apple sign-in checks the token against your app's bundle ID. Native Google sign-in checks it against your iOS client ID and, for Android, your web client ID.

In the Anythink dashboard

  1. Go to Settings › Social Sign-In and select Configure on Apple.
  2. Under Native sign-in, enter your app's bundle identifier in Bundle IDs and select Save.
  3. Select Configure on Google. Under Native sign-in, enter the iOS client ID in iOS Client IDs. Under Web sign-in, enter the Web Client ID and Client secret, which Android sign-in uses, then select Save.
  4. Go to Settings › Users and choose a Default role for new users. A first-time social sign-in creates the user with that role, and fails with 400 if none is set.

With the CLI

bash
anythink fetch /integrations/definitions/apple/oauth --method PUT --body '{
  "native_audiences": ["com.example.fitness"],
  "is_enabled": true,
  "use_social_sign_in": false
}'

anythink fetch /integrations/definitions/google/oauth --method PUT --body '{
  "web_client_id": "1234567890-web.apps.googleusercontent.com",
  "web_client_secret": "'"$GOOGLE_CLIENT_SECRET"'",
  "native_audiences": ["1234567890-ios.apps.googleusercontent.com"],
  "is_enabled": true,
  "use_social_sign_in": false
}'

The default role is a dashboard setting.

With an AI assistant (MCP)

text
Turn on native Sign in with Apple for this project for the bundle ID com.example.fitness.

The assistant saves the Apple settings through the cli tool. Put client secrets in the dashboard or the CLI rather than typing them into a chat.

In the app, one request turns the identity token into a session:

http
POST /org/{project_id}/auth/v1/native/apple
Content-Type: application/json

{
  "identity_token": "<credential.identityToken as a string>",
  "user_identifier": "<credential.user>",
  "given_name": "Ada",
  "family_name": "Lovelace"
}
json
{
  "access_token": "eyJhbGciOi…",
  "refresh_token": "Yt7Qm1x…",
  "expires_in": 1800
}

Apple sends the user's name only on the first sign-in, so pass given_name and family_name then. Google uses the same shape at /auth/v1/native/google. Anythink matches the sign-in to a user by the provider's subject ID, then by email, so someone who signs in with Google and later with Apple using the same email is one user.

Send the access token on every call, refresh it with POST /auth/v1/refresh before expires_in runs out, and sign out with POST /auth/v1/logout. Refresh tokens are single-use, so store the new pair each time, in the Keychain on iOS or encrypted storage on Android. Social sign-in has the Apple and Google console setup, a full Swift example and every error. Sign in users covers email and password.

Step 3: Scope each user's records with row-level security#

With row-level security on, a standard user reaches only the records that carry a grant for them. When a signed-in user creates a record, Anythink grants them read-write access to it automatically. So the app calls the normal endpoints with the user's token, and each person sees only their own workouts. You don't write per-user queries or filter on the client.

The user's role still has to allow the entity at all. Give the role the permissions on workouts, and make sure its API Access is on.

In the Anythink dashboard

  1. Open Settings › Roles and Permissions and select the role your app users get, for example Standard User.
  2. Make sure API Access is ticked.
  3. Give the role Read, Create, Update and Delete on workouts, and save.

To let a coach read one user's workout, open the record, select its Row-Level Security tab and select Add User Access.

With the CLI

bash
anythink roles list
anythink data rls workouts 42 --user 72 --readonly

Grant the role its permissions in the Anythink dashboard, under Roles. roles list shows the role ids, and the last command grants user 72 read-only access to workout 42.

With an AI assistant (MCP)

text
List the roles on this project. Then give user 72 read-only access to workout 42 and show me who can see it.

The assistant runs roles list and data rls through the cli tool. Grant role permissions in the dashboard.

Administrators bypass row-level security, so test as a standard user, not as yourself. Grants on existing records can be changed only by administrators. For teams, where one grant covers every member, see the groups section of Groups and row-level security.

Step 4: Register for push and send from a workflow#

Push goes through Firebase Cloud Messaging (FCM). iOS devices receive pushes through FCM too: your iOS app registers an FCM token, and you add your APNs key to the Firebase project in the Firebase console. The pieces are a provider credential, a device registration from the app and a workflow step.

Connect FCM. In the Firebase console, generate a service account key (a JSON file) under Project settings › Service accounts.

In the Anythink dashboard

  1. Open Settings and select Push Notifications.
  2. On the Providers tab, find Firebase Cloud Messaging and select Upload service-account JSON.
  3. Choose the file and select Save. The badge changes to Configured.

With the CLI

bash
anythink fetch /push/credentials/fcm --method PUT \
  --body "$(jq -n --rawfile sa service-account.json '{service_account_json: $sa}')"

With an AI assistant (MCP)

text
Configure FCM push for this project with the service account JSON in ./service-account.json, then show me which providers are configured.

The key file stays on your machine and goes straight to your project.

Register the device. After sign-in, and again whenever the push token changes, the app registers its token as the signed-in user. The Standard User role can do this by default. A custom role needs the push device-registration permission, which you tick under Settings › Roles and Permissions.

http
POST /org/{project_id}/push/devices
Authorization: Bearer <access token>
Content-Type: application/json

{ "provider": "fcm", "token": "<the device's FCM token>", "metadata": { "platform": "ios" } }

The same call registers iOS and Android devices, both with provider: "fcm".

Send from a workflow. Add a Send a Push Notification step. Here a workflow sends a workout reminder with two action buttons to the user named in the trigger data. If your app syncs its button groups (below), the app's role needs the push button-group sync permission, which isn't on the Standard User role by default. Tick it under Settings › Roles and Permissions.

In the Anythink dashboard

  1. Open Workflows, select New Workflow and create workout-reminder.
  2. Select Add Step and choose Send a Push Notification. Enter a Title and Body.
  3. Under Send to, choose A single user and enter {{ $anythink.trigger.data.user_id }} as the User id.
  4. Pick an iOS button group such as workout_reminder, then set the tap target of its buttons, for example start and snooze.
  5. Save the step and select Enable Workflow.
  6. Create a second workflow with an Event trigger, Event Type Push Action Taken, the workout_reminder group and the snooze action. Its steps run when someone taps Snooze.

With the CLI

bash
anythink workflows create workout-reminder --trigger Api --api-route workout-reminder --enabled
anythink workflows step-add <workflow_id> notify --action SendPushNotification --start --enabled \
  --params '{
    "title": "Time to train",
    "body": "Your session starts soon",
    "category": "workout_reminder",
    "buttons": [
      { "id": "start", "label": "Start", "click_action": "fitness://workouts/today" },
      { "id": "snooze", "label": "Remind me later" }
    ],
    "audience": { "kind": "user", "user_id": "{{ $anythink.trigger.data.user_id }}" }
  }'

anythink workflows create workout-snoozed --trigger Event --event PushActionTaken \
  --entity workout_reminder \
  --filter '{"field":"action_id","op":"eq","value":"snooze"}' --enabled

Until the role has the button-group sync permission, the app's sync call returns 403.

With an AI assistant (MCP)

text
Create a workflow called workout-reminder with an API trigger on the route workout-reminder, and a second workflow called workout-snoozed that runs when the snooze button in the workout_reminder group is tapped. Enable both.

The assistant creates the workflows through the cli tool. Add the Send a Push Notification step in the dashboard or with the CLI command above, because step parameters that contain {{ $anythink... }} expressions aren't accepted by the cli tool.

On launch the app syncs its button groups to POST /push/categories/sync, and when a button is tapped it reports the tap to POST /push/action-events with the notification_id from the push. A tap starts every enabled workflow listening for that group and action. Push notifications covers Android and web delivery, templates, audiences, delivery history and the tap flow in full.

Step 5: Sell a subscription with Apple in-app purchases#

Your app sells a subscription through StoreKit 2. Anythink verifies the purchase on the server, links it to the signed-in user and answers one question: does this user have access? You don't write receipt verification or a separate Apple access check.

Create the auto-renewing subscription in App Store Connect, then connect your app, add a plan that carries the product's id, and have the app verify purchases.

In the Anythink dashboard

  1. Open Settings › Payments and choose the Apple tab. Enter the App bundle ID, Default environment, App Store Connect Issuer ID and App Store Connect Key ID, paste the .p8 file into Private key (.p8), and select Save Apple settings.
  2. Copy the URL under App Store Server Notifications (required) and set it for both Production and Sandbox in App Store Connect.
  3. Open AnythinkPay, choose the Plans tab and select New plan. Set Type to Mobile and paste the product's identifier into Apple product ID.
  4. Open Settings › Roles and Permissions and give the role your app users have Read on anythink_subscription_plans. Verify and entitlement calls need it.

With the CLI

bash
anythink fetch /integrations/anythinkpay/apple-iap/credentials --method PUT \
  --body "$(jq -n --rawfile key AuthKey_ABC123DEF4.p8 '{
    bundle_id: "com.example.fitness",
    environment: "sandbox",
    asc_issuer_id: "<issuer id>",
    asc_key_id: "ABC123DEF4",
    asc_private_key_pem: $key
  }')"

anythink fetch /integrations/anythinkpay/subscription-plans

Give your app users' role read access to anythink_subscription_plans in the dashboard, under Roles.

Create the plan in the dashboard, where the form carries the Apple product ID. Create plans and manage subscriptions lists every plan field.

With an AI assistant (MCP)

text
List the active subscription plans on this project.

The assistant runs a fetch against the plans endpoint through the cli tool. Don't type the .p8 key into a chat: set Apple's credentials in the dashboard or the CLI.

After a purchase or a restore, the app sends the transaction as the signed-in user, then reads access:

http
POST /org/{project_id}/integrations/anythinkpay/subscriptions/apple/verify
Authorization: Bearer <access token>
Content-Type: application/json

{ "signed_transaction": "<JWS from StoreKit 2>" }
http
GET /org/{project_id}/integrations/anythinkpay/subscriptions/me/entitlement
Authorization: Bearer <access token>

has_access is the only field you need to gate a feature. Verifying the same transaction twice returns the same subscription, so it's safe to call on every launch. If matched_known_plan comes back false, the product id isn't on any plan, so don't unlock a feature on has_access alone. Apple in-app purchases covers ownership, restores and server notifications, and Offer codes, free trials and entitlements covers the entitlement call. To go further with plans, Stripe, trials and offer codes, see Subscriptions and paywalls.

Step 6: Upload files from the app#

A File Upload field, like workouts.photo, holds the files you attach to a record. The app uploads as the signed-in user, naming the entity and field so the field's rules apply, then links the file to the record.

The Standard User role already includes the anythink_files permissions, so a standard user can upload. A custom role needs anythink_files:create to upload, and the entity's own create or update permission to set the file field.

In the Anythink dashboard

  1. Open Settings › Data Model, select the workouts entity and open the photo field.
  2. Set Allowed file types to images, set Number of files allowed for upload, and leave Public off so only signed-in users can read the files.
  3. Select Save. Editors and administrators can also upload from a record's form in the dashboard.

With the CLI

bash
anythink entities get workouts
anythink files upload progress.jpg
anythink data create workouts --data '{"title": "Leg day", "photo": [4230]}'

entities get shows the entity's numeric id. A file field takes an array of file ids.

With an AI assistant (MCP)

text
Upload ./progress.jpg and attach it to the photo field of workout 42.

The assistant uploads the file and updates the record through the cli tool.

From the app, send the file as multipart/form-data in a part named file, with the entity's id and the field name. One request uploads one file, up to 50 MB.

bash
curl -X POST "https://api.my.anythink.cloud/org/{project_id}/files?entityId=12&fieldName=photo" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -F "file=@progress.jpg"

The response includes the new file's id. Send that id in the photo array when you create or update the workout. Files that aren't public download with GET /files/{id}/get, which redirects to a short-lived signed address. Upload and manage files covers allowed types, public and private files and how files appear in record responses.

What you get, and the limits#

What you get

  • Native Apple and Google sign-in alongside email and password, with one session, one user per person and automatic account linking.
  • A REST API for every entity, with per-user records enforced on the server by row-level security.
  • Push to iOS, Android and the web from a workflow step, with action buttons that start another workflow and a delivery history per send.
  • Apple in-app purchases verified on the server, folded into the same subscription and entitlement model as Stripe.
  • File uploads attached to records, with media metadata read in the background.

Limits to plan around

  • No mobile SDK. The Anythink SDK is for React web apps and handles sign-in and sessions only. A native or Flutter app calls the REST endpoints directly, as the examples above and in the guides do.
  • Native sign-in is Apple and Google. Apple is iOS only, Google covers iOS and Android, and GitHub is web only. A first sign-in needs a verified email, and Apple users who hide their email need Allow users without an email switched on. Your project must have a default role for new users.
  • Session lifetimes are fixed. Access tokens last 30 minutes and refresh tokens 30 days, and refresh tokens are single-use.
  • Push is FCM and web push. There's no direct Apple push provider, so iOS needs your APNs key in Firebase. Push credentials, the device list, templates, button groups and history are project administrator features. Showing an image on iOS needs a Notification Service Extension in your app.
  • Button taps are time-limited. The tap token is valid for one hour after the send, and a notification sent without a category can't report taps. data values are strings only.
  • Apple purchases are iOS only. Android and web go through Stripe checkout. The first account to verify a transaction keeps it, and moving it to another user is an administrator task.
  • Apple purchases update from notifications. An Apple subscription changes only when App Store Server Notifications arrive, and Apple purchases don't start workflows.
  • Row-level security has edges. Administrators bypass it, records created by a workflow have no owner until the step grants one, and grants on existing records can be changed only by administrators. Plan for one grant per user per record.
  • Files. Each upload is one file of up to 50 MB, SVG, HTML, XML, JavaScript and executable files are refused, and storage is capped by your plan.
  • No realtime channel. The app learns something changed by asking the API again, or from a push.

Next steps#