> ## Documentation Index
> Fetch the complete documentation index at: https://help.the-meridian.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# What needs Meridian hosting

Most of the SDK works wherever your app runs. **Billing is the exception**, and it is a hard requirement rather than a degraded experience.

## Why billing is different

To create a Shopify subscription for a shop, Meridian needs that shop's Shopify **offline access token**. The only place that token exists is your app's own session table, the one your Shopify library owns, and Meridian reads it by opening your app's database directly.

So billing works only where Meridian can reach that database, which means Meridian has to be running it.

## What satisfies the requirement

Any one of these is enough:

| | What it is |
| - | - |
| A **production environment with a managed database** | The normal case: your app is [hosted on Meridian](/environments) and its production [database](/database) is provisioned and ready |
| A **production environment with a database add-on** | Same thing by another route: a ready database [add-on](/add-ons) on the production environment counts |
| An install running under **`meridian dev`** | Your local app's sessions live in your managed [developer database](/cli), which Meridian can also reach |

The dev-database path is what lets you test billing end to end **before your first deploy**. Installs made by a locally-run app are always billed as Shopify **test** charges, never real ones.

<Note>
  Both production paths run through a Meridian hosting **environment**, because that is what a managed database attaches to. Keeping your app on another host and buying a Meridian database does not work: the session table Meridian reads has to be the one your running app writes to.
</Note>

## What works anywhere

Every one of these is independent of where your app is hosted:

| Feature | Why it does not need hosting |
| - | - |
| [Feature gating](/features) and entitlements | Resolved entirely from Meridian's own data |
| Reading plans, the customer and usage | The same: pure reads |
| [Usage tracking](/events) | The usage row is written by Meridian. See the caveat below |
| [Custom field values](/custom-fields-api) | Meridian's CRM data, read and written with your own credentials |
| [Identify and contacts](/sdk-identify) | Meridian's own records |
| Automatic contact capture | Needs your Shopify **client secret** in Meridian, not your database |
| Webhook registration and [`afterAuth`](/sdk-app#afterauth) | The SDK calls the Shopify Admin API directly, with the access token your app passes in |
| Subscription and trial sync for apps billed outside Meridian | The SDK reads your subscriptions from the Shopify Admin API with that same access token and reports them. See [Free trial](/free-trial#trials-on-subscriptions-you-bill-yourself) |
| [Forwarded webhook verification](/sdk-webhooks) | An HMAC computation in your own process |
| [Referral capture](/sdk-referrals) and [attribution](/sdk-attribution) | Cookies on your domain, plus Meridian's own records |
| Install and lifecycle [automations](/automations) | Reported by your server, dispatched by Meridian |

A self-hosted app keeps gating, the CRM, segments, automations, emails, affiliates and the webhook pipeline. It cannot take money through Meridian, but Meridian still sees the subscriptions the app bills itself, trials included.

## What needs it

Three calls assert the requirement up front and refuse rather than half-work:

| Call | Surfaced by |
| - | - |
| `subscribe` | [Pricing page](/plans), `useMeridian().subscribe` |
| `cancelSubscription` | [Account page](/account) |
| `updateUsageCap` | The account page's **Raise cap** flow |

They fail with `403 PLAN_BUILDER_UNAVAILABLE`, or `401 SHOPIFY_ACCESS_TOKEN_STALE` when the store is reachable but holds no offline session for that shop, which is the merchant's to fix rather than yours. See [Errors](/sdk-errors#meridian-cannot-bill-this-app-at-all).

The **plan builder itself is gated too.** For an app that cannot bill, Meridian blocks its plan-builder routes and the dashboard names the reason, so you find out at "publish a plan", not at a merchant's first checkout.

### Two things degrade quietly

These do not fail, which makes them the ones to watch:

**Usage overage is never charged.** A `track` call still returns `200` and the usage row is still written. Meridian writes that before it attempts anything with Shopify, so the record of what your app did always survives. But the overage charge that should follow is best-effort: it is logged as a warning and swallowed. Your usage figures look correct and no money moves.

**Plan prices fall back to USD.** At `identify`, Meridian asks Shopify for the shop's billing currency. With no reachable session store that lookup fails, and the currency falls back to `USD`, so a shop that bills in another currency is shown every plan priced in dollars. The shop's contact email goes unresolved in the same call, which is how a store ends up in your CRM with no address on it.

Neither is fixable from the SDK. They are what "billing is unavailable" looks like on the calls built not to throw.

## How to check

**Before you build:** the app's plan-builder section in the Meridian dashboard is the answer. If it is gated, billing will not work, and the gate names the reason.

**In code:** reactively, with the guard the SDK exports.

```ts theme={null}
import { isPlanBuilderUnavailableError } from "@the-meridian/sdk/server"

try {
  await client.subscribe(planId, returnUrl)
} catch (err) {
  if (isPlanBuilderUnavailableError(err)) {
    // err.details.plan_builder_billing_status names the missing precondition.
  }
  throw err
}
```

`err.details.plan_builder_billing_status` distinguishes the four cases. See the [status table](/sdk-errors#meridian-cannot-bill-this-app-at-all).

<Warning>
  There is **no public API or SDK function that answers this up front.** Meridian's public API exposes no readiness field, so the SDK cannot ask "may this app bill?" before trying. If your app needs to hide its pricing page when billing is unavailable, drive that from your own configuration, or catch the error on first use and remember the answer.
</Warning>

## A note on the session table

`session_table_missing` looks like a hosting problem but is not. It means Meridian reached your database and found no `Session` table, almost always an app still pointing Prisma at the template's SQLite datasource, so its migrations ran somewhere else.

Put the managed `DB_*` [variables](/secrets) in your datasource, deploy, and the table appears.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.