> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useautumn.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating from v2.3

> What changed between the v2.3 and v2.6 Lakehouse schemas, and how to move your queries.

v2.6 sits beside v2.3 in the same namespace. Your `v2_3_*` tables keep updating while you move, so you can run old and new queries side by side and compare them before you switch.

## Why move

v2.6 publishes the same objects the API returns, so its numbers match the API. Three v2.3 problems go away:

* **Balances no longer count old plans.** `v2_3_balances` added up every grant a customer ever had, including ended plans and past cycles, so you had to filter them out. `v2_6_balances` contains only what the API counts.
* **Rollovers are included.** `v2_3_balances` left rollovers out. In v2.6 they are part of the balance and listed in `v2_6_rollovers`.
* **Subscriptions keep their real status.** `v2_3_subscriptions.status` showed every non-active plan as `scheduled`. `v2_6_subscriptions.status` is the real status: `expired`, `trialing`, `paused` and so on.

## What changes in your queries

1. **Table prefix.** `v2_3_` becomes `v2_6_`. `events` is unchanged.
2. **Product is now plan.** Every `internal_product_id` becomes `internal_plan_id`, and `internal_customer_product_id` becomes `customer_plan_id`. (`events.internal_product_id` keeps its name.)
3. **Keys are named after their table.** v2.3's generic `internal_id` becomes `internal_customer_id`, `internal_plan_id`, `customer_plan_id` and so on, and references use the same name, so a join reads `s.internal_plan_id = p.internal_plan_id`.
4. **Balances have one row per view.** Add `scope = 'customer'` to customer-level balance queries. See [below](#balances-now-have-one-row-per-view).
5. **Drop your balance filters.** The active-plan, current-cycle and expiry filters you applied to v2.3 balances are already applied.
6. **Arrays are JSON text.** v2.3 had a few array columns (`plan_ids`, `product_ids`, `internal_product_ids`). In v2.6 every array is a JSON string; see [Querying → JSON columns](https://docs.useautumn.com/documentation/lakehouse/v2-6/querying.md#json-columns).

## Tables

| v2.3 | v2.6 | What changed |
| - | - | - |
| `v2_3_features` | `v2_6_features` | API shape: `type` is the API's type (`metered`, `boolean`…); `config` is split into `consumable`, `event_names`, `credit_schema` and the markup columns |
| `v2_3_plans` | `v2_6_plans` | Adds `version_slug`, `active`, `billing_controls`, `metadata`, `variant_details`, `price_id`, `price_additional_currencies` |
| `v2_3_plan_items` | `v2_6_plan_items` | `has_price` removed (check `price_amount` or `price_tiers`); adds `pooled`, `is_custom`, expiry, `price_additional_currencies` and `position` |
| `v2_3_rewards` | `v2_6_coupons` and `v2_6_feature_grants` | Split by kind, in the API's shape. Free-product rewards are no longer published |
| `v2_3_referral_programs` | `v2_6_referral_programs` | `trigger_event` → `redeem_on`, `product_ids` → `plan_ids`; `unlimited_redemptions` removed |
| `v2_3_customers` | `v2_6_customers` | Key renamed only |
| `v2_3_entities` | `v2_6_entities` | Deleted entities are left out, so `deleted` is gone; `id` → `entity_id` |
| `v2_3_subscriptions` | `v2_6_subscriptions` | Real `status`; adds `scope`, the attached trial, `customer_license_id`, `is_custom`, `position` |
| `v2_3_purchases` | `v2_6_purchases` | Adds `status`, `scope`, `is_custom`, `position` |
| `v2_3_balances` | `v2_6_balances` | Per view; includes rollovers; adds `overage_allowed`, `max_purchase`, `allocated`, `unallocated`, `as_of`; the `reset_*` columns are on breakdowns instead |
| `v2_3_breakdowns` | `v2_6_breakdowns` | Per view, only rows the API counts, ordered by `position`; adds `plan_id`, `overage` and the price columns; an unlimited balance is a single row with positive `usage` |
| `v2_3_rollovers` | `v2_6_rollovers` | Per view, unexpired only; `granted` added, `usage` and the `entities` map removed (each entity's amount is its own row) |
| `v2_3_flags` | `v2_6_flags` | One row per view, keyed on internal ids; adds `plan_id`; `subscription_ids_csv` removed |
| `v2_3_invoices` | `v2_6_invoices` | `stripe_id` → `processor_id`; `internal_product_ids` and `discounts` removed |
| `v2_3_invoice_line_items` | `v2_6_invoice_line_items` | Only the API's fields: adds `quantity` and `entities`; removes `currency`, `amount_after_discounts`, `direction`, `billing_timing`, `prorated`, `discounts` |
| (none) | `v2_6_plan_licenses`, `v2_6_licenses` | New: seats a plan includes, and each customer's seat pools |
| (none) | `v2_6_customer_plan_items` | New: the items and prices of custom plans |
| (none) | `v2_6_pooled_balance_contributions` | New: what feeds a shared pool |
| (none) | `v2_6_referrals` | New: each use of a referral code |
| `events` | `events` | Unchanged and shared |

## Renamed keys

| Table | v2.3 | v2.6 |
| - | - | - |
| features | `internal_id` | `internal_feature_id` |
| plans | `internal_id` | `internal_plan_id` |
| plan\_items | `internal_id`, `internal_product_id` | `internal_plan_item_id`, `internal_plan_id` |
| customers | `internal_id` | `internal_customer_id` |
| entities | `internal_id`, `id` | `internal_entity_id`, `entity_id` |
| subscriptions, purchases | `internal_id`, `internal_product_id` | `customer_plan_id`, `internal_plan_id` |
| breakdowns | `internal_id`, `internal_customer_product_id`, `internal_product_id` | `breakdown_id`, `customer_plan_id`, `internal_plan_id` |
| rollovers | `internal_id`, `internal_breakdown_id` | `rollover_id`, `breakdown_id` |
| flags | `internal_id`, `internal_customer_product_id`, `internal_product_id` | `flag_id`, `customer_plan_id`, `internal_plan_id` |
| referral\_programs | `internal_id`, `id` | `internal_referral_program_id`, `referral_program_id` |
| invoices | `id` | `invoice_id` |
| invoice\_line\_items | `id`, `internal_product_id` | `line_item_id`, `internal_plan_id` |

## Balances now have one row per view

In v2.3, each grant appeared once, under whichever scope it had: customer-level grants on rows with no `entity_id`, entity-level grants on rows with one. In v2.6 each balance is computed separately for the customer and for every entity, the way the API does it, and an entity's view also includes the customer-level grants it can use.

| | v2.3 | v2.6 |
| - | - | - |
| A customer-level grant | one row, `entity_id` empty | one row in the customer view, and one in each entity's view |
| An entity's own grant | one row with its `entity_id` | one row in that entity's view only |
| A customer's total | sum every row | the `scope = 'customer'` row |
| One entity's total | its rows only (missing shared grants) | the `scope = 'entity'` row for it |

So the v2.3 habit of summing every row now over-counts. Read [Working with balances](https://docs.useautumn.com/documentation/lakehouse/v2-6/balances.md) for the worked example.

## Before and after

A customer's current balance for one feature, including rollovers.

**v2.3**: filter to active plans and the current cycle, then add unexpired rollovers yourself.

```sql theme={null}
WITH live AS (
  SELECT bd.*
  FROM <namespace>.v2_3_breakdowns bd
  JOIN <namespace>.v2_3_subscriptions s ON s.internal_id = bd.internal_customer_product_id
  WHERE bd.env = 'live' AND bd.customer_id = 'cus_123' AND bd.feature_id = 'messages'
    AND s.status = 'active'
    AND (bd.expires_at IS NULL OR bd.expires_at > <now in epoch ms>)
    -- ...and pick the current reset cycle per plan
),
ro AS (
  SELECT internal_breakdown_id, sum(balance) AS rollover
  FROM <namespace>.v2_3_rollovers
  WHERE expires_at IS NULL OR expires_at > <now in epoch ms>
  GROUP BY internal_breakdown_id
)
SELECT sum(live.remaining) + coalesce(sum(ro.rollover), 0) AS remaining
FROM live LEFT JOIN ro ON ro.internal_breakdown_id = live.internal_id;
```

**v2.6**: read the row.

```sql theme={null}
SELECT b.granted, b.remaining, b.usage
FROM <namespace>.v2_6_balances b
JOIN <namespace>.v2_6_customers c
  ON c.org_id = b.org_id AND c.env = b.env AND c.internal_customer_id = b.internal_customer_id
WHERE b.env = 'live' AND c.customer_id = 'cus_123'
  AND b.feature_id = 'messages' AND b.scope = 'customer';
```

## Checklist

1. Run your v2.3 queries and their v2.6 versions side by side for a few customers and compare.
2. Change the table prefix, then rename keys using the table above.
3. Replace `internal_product_id` and `internal_customer_product_id` with `internal_plan_id` and `customer_plan_id`.
4. In balance queries, delete your active/current-cycle/rollover logic and add a `scope` filter.
5. Where you filtered `status = 'scheduled'` to mean "not active", use the real statuses.
6. Parse array columns as JSON.
7. Tell us when you've moved, so we can retire your v2.3 tables.


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