> ## 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.

# Data types

The shapes the SDK hands you. All of them are generated from and type-checked against Meridian's public OpenAPI schema, so the types in your editor match the JSON on the wire.

## Entitlements

What the shop's plan grants right now. This is the snapshot every gate reads from.

| Field | Type | Notes |
| - | - | - |
| `planId` | `string \| null` | `null` when the shop has no subscription |
| `planName` | `string \| null` | |
| `paused` | `boolean` | `true` when Shopify froze the subscription over a failed payment |
| `currencyCode` | `string` | The shop's currency |
| `fxRateFromUsd` | `number` | The rate the shop's prices were converted at |
| `features` | `Record<string, EntitlementFeature>` | Keyed by feature key |
| `events` | `Record<string, EntitlementEvent>` | Keyed by event key. Covers your whole event catalog, not only the events this plan prices |
| `views` | `Record<string, EntitlementView>?` | Keyed by view key. Absent for an app with no [views](/usage-views) |
| `periodStart` | `string?` | Start of the window `usedThisPeriod` is measured in, ISO 8601 |
| `periodEnd` | `string?` | End of that window, exclusive: the instant usage resets |

All three maps are keyed by the app-local **key** you set in the dashboard. Event keys and view keys share one namespace per app, which is what lets `getUsage(key)` resolve either kind.

**`EntitlementFeature`**: `{ enabled: boolean, limit?: number }`.

**`EntitlementEvent`**: `{ usedThisPeriod: number, includedQuantity?: number, cappedAmount?: number }`. Present for every event in your catalog; `includedQuantity` and `cappedAmount` only where the shop's plan meters it. `usedThisPeriod` counts the current period only. See [Usage tracking](/events#which-period).

**`EntitlementView`**: `{ eventKey, value, eventCount, window, windowStart?, windowEnd? }`. The shop's current reading of one [view](/usage-views), measured over the view's **own** window rather than the entitlements' `periodStart`/`periodEnd`. `value` is fractional when the view sums an event property; `eventCount` is how many events contributed to it. `window` is the window id (`billing_period`, `calendar_month`, `last_7_days`, `last_30_days` or `all_time`), and the ISO 8601 bounds follow it, `windowEnd` exclusive. Both bounds are absent on an `all_time` view, which is unbounded.

## Plan

One of your active plans, priced in the shop's currency.

| Field | Type | Notes |
| - | - | - |
| `id`, `name` | `string` | |
| `description` | `string?` | |
| `priceMonthly` | `number` | Always present. `0` means the plan never bills |
| `priceYearly` | `number?` | Present only when the plan also offers annual billing |
| `currencyCode` | `string` | |
| `trialDays` | `number?` | Ignored on a free plan, since a plan that never bills cannot have a trial |
| `features` | `PlanFeatureRef[]` | |
| `events` | `PlanEventRef[]` | |
| `views` | `PlanViewRef[]?` | Present only when the plan meters a [view](/usage-views) |

**`PlanFeatureRef`**: `{ featureId, name?, value? }`, where `value` is the numeric limit this plan sets.

**`PlanEventRef`**: `{ key, name?, unit?, includedQuantity, overageRate, overageUnit, cappedAmount }`. An `overageRate` of `0` means the plan does not bill overage, so the included quantity is a hard allowance rather than a threshold. `overageRate` is the price of one block of `overageUnit` units in the shop's currency, converted exactly and never rounded to the cent (0.10 USD at an FX rate of 0.92 is `0.092`): the figure Shopify's approval screen states, before it restates a rate under one cent per ten times the units (`0.005` per `1` is approved as 0.05 per 10). `cappedAmount` is in cents, the spending limit sent to Shopify. `unit` is the singular word set on the event in the catalog (`"translation"`), or `null` when it has none. Older API versions omit it, so treat a missing `unit` as `null`. A view has no `unit`: it can total money, a weight or a count of something else.

**`PlanViewRef`**: `{ key, name?, eventKey?, includedQuantity, overageRate, overageUnit, cappedAmount }`. The same four terms as `PlanEventRef`, with two shapes a view forces: `includedQuantity` is fractional, because a view can total money ("the first 1,000 of GMV"), and `overageRate` is exact here too, because a percentage-of-value term is a rate like `0.02` and rounding it to cents would turn `0.005` into nothing. A plan carries at most one usage-priced subject across `events` and `views`, since a Shopify subscription has a single usage line item.

**`PlanInterval`**: `"EVERY_30_DAYS"` or `"ANNUAL"`.

## MeridianCustomer

The shop's CRM record.

| Field | Type | Notes |
| - | - | - |
| `shopDomain` | `string` | Lower-cased |
| `appId` | `string` | |
| `name`, `email` | `string?` | The shop-level contact |
| `customFields` | `Record<string, CustomFieldValue>` | This app's [custom field values](/custom-fields-api) for the store |

`customFields` is keyed by each field's immutable handle. A field the store has no value for is **absent**, so a store with no values reads `{}`. Values keep their type: a number reads as a number, a JSON field as the parsed structure, a date as its `YYYY-MM-DD` string.

**`MeridianMe`**: `{ customer, entitlements, plans }`. One read gives you all three.

<Note>
  `identify` returns identity only: its `customer` carries no `customFields`.
</Note>

## Results

| Type | Shape | Notes |
| - | - | - |
| `SubscribeResult` | `{ confirmationUrl?, simulated? }` | No `confirmationUrl` means the plan activated with no billing step |
| `CancelResult` | `{ cancelled: number }` | |
| `UpdateUsageCapResult` | `{ confirmationUrl }` | The merchant must approve the new cap |
| `TrackEventResult` | `{ id, eventKey, quantity, timestamp }` | |
| `TrackedEventResult` | `TrackEventResult` plus `created` | One item of a batch. `created: false` means the item's idempotency key was already recorded, and the entry is the stored event |
| `TrackManyResult` | `{ events: TrackedEventResult[] }` | One entry per event sent, in the same order |
| `FeatureCheck` | `{ enabled, limit? }` | |
| `IdentifyResult` | `{ shopToken, shopTokenExpiresAt?, customer }` | |
| `RefreshShopTokenResult` | `{ shopToken, shopTokenExpiresAt? }` | |
| `MeridianShopTokenDetails` | `{ shopToken, shopTokenExpiresAt? }` | What [`mintShopTokenDetailed`](/sdk-app#persisting-the-token-yourself) returns. The expiry is verbatim from the API and absent when it reported none |
| `SetCustomFieldsResult` | `{ shopDomain, values }` | The store's full value map after the write. A [de-duplicated write](/custom-fields-api#using-the-sdk-instead) replays its twin's map, which can be a few seconds stale |

## Tracking inputs

| Type | Shape | Notes |
| - | - | - |
| `TrackEventInput` | `{ eventKey, quantity?, properties?, idempotencyKey? }` | One event, dated the moment Meridian receives it. It carries no `timestamp` (the type says `timestamp?: never`) |
| `BackdatedTrackEventInput` | `{ eventKey, quantity?, properties?, idempotencyKey, timestamp }` | One event that happened earlier. `timestamp` is a `Date` or an RFC 3339 string with an offset, and `idempotencyKey` is required. See [Dated events](/events#dated-events) |
| `TrackManyInput` | `{ events }` | A batch. Each event is a `TrackEventInput` or a `BackdatedTrackEventInput` |

`track` and `trackMany` on the [server client](/sdk-server-client) and on [`createMeridianApp`](/sdk-app) take either input. The [`useMeridian()`](/sdk-use-meridian) hook's `track` takes a `TrackEventInput` only. `TrackEventInput` is still a plain object type, so a type of your own can extend it.

**`DiscountPreview`** carries `valid` plus, when it applies, the discount's `type` (`percentage` or `fixed_amount`), its `value`, a human-readable `durationLabel`, and the original and discounted prices for both intervals. `appliedToCurrentSubscription` distinguishes a discount the shop's subscription already carries from an offer only a new subscription could take. When nothing applies, `valid` is `false` with a generic message that never reveals whether a code exists.

## Custom field values

**`CustomFieldValue`**: `string | number | boolean | unknown[] | Record<string, unknown> | null`.

On the **write** side, `null` clears a field, and so does `""`, so a stored text value is always non-empty. A read never yields `null`: a cleared field is absent from the map.

## Server types

| Type | Notes |
| - | - |
| `MeridianGates` | `{ entitlements, planId, planName, can, limitOf, usage, view }` |
| `MeridianApp` | What [`createMeridianApp`](/sdk-app) returns |
| `MeridianServerClient`, `MeridianAdminClient` | The two client shapes |
| `MeridianAfterAuthResult` | `{ registered, updated, unchanged, errors, lifecycleNotified, subscriptionsSynced, referralCode, referralForwarded }` |
| `MeridianSyncSubscriptionsResult` | `{ synced, triggered }`: what [`syncSubscriptions`](/sdk-app#syncing-subscriptions-on-demand) reports back |
| `ShopSubscription`, `ShopSubscriptionLineItem` | One Shopify app subscription as the SDK reads it from the Admin API: `{ gid, name, status, test, trial_days, created_at, current_period_end, line_items }` |
| `MeridianCaptureReferralResult` | See [Referral capture](/sdk-referrals#what-the-result-tells-you) |
| `MeridianWebhookResult` | `{ registered, updated, unchanged, errors }`: topics created, rewritten, or already correct and left alone |
| `MeridianMintShopTokenInput` | What both mint methods take: `{ shop, name?, email?, contacts?, mref?, mrefClickedAt? }` |
| `IdentifyContact` | `{ email, firstName?, lastName?, phone?, role? }` |
| `CapturedReferral` | `{ code, clickedAt }` |

## React types

`MeridianContextValue` is everything [`useMeridian`](/sdk-use-meridian) returns, and `MeridianProviderProps` is the [provider's](/sdk-provider) prop shape. Both are exported, for a wrapper component of your own.


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