---
title: "get otb rollup"
method: GET
path: "/api/v1/otb/{mfp_id}/rollup"
tags: ["otb"]
---

# get otb rollup

`GET /api/v1/otb/{mfp_id}/rollup`

## Request

- Base URL: `https://api.app.tightly.io`
- URL: `https://api.app.tightly.io/api/v1/otb/{mfp_id}/rollup`
- Auth: HTTP bearer

Open to buy for one season, category by category: the envelope the plan declares, what is already committed, what is reserved against it, what is left, the forward recommended buy and the chase reserve split.

`cost_rows` is the additive, cost-aware row contract. Prefer it when present, including an empty array; fall back to `rows` only when absent or null. Recorded purchase costs remain visible, while headroom, variance and status are null when the selected plan's cost basis cannot be compared with those sources. `cost_comparison` names the selected and recorded bases and explains the absence. The legacy `rows` contract retains its numeric types and is empty for such an unavailable comparison; standard-plan rows keep their existing values.

A SEASON POOL RATHER THAN A WEEKLY BUDGET. The budget is category x season, so a single week cannot breach on its own; `scope` selects SS or FW and there is no fiscal-year scope, because rolling two disjoint selling windows into one running variance produced a crossing-week signal that meant nothing. `scope=FY` is refused 400. Every figure is at cost.

There is no stored budget and no build step: the envelope is read live from the plan's own MFP on every call, so this and get_mfp_table cannot disagree about the same season.

Sold with Pro: an organisation without the planning rail is refused 403 `plan_excludes`.

Scope: `planning:read`.

## Path parameters

- `mfp_id` string, required

## Query parameters

- `scope` 'SS' | 'FW'
- `filter_args` string
- `sort_args` string
- `search` string

## Headers

- `Tightly-Version` string

## Response `200`

The season pool, category by category. Every figure is at cost. `remaining` is `season_envelope - (committed + reserved)`; `variance` is `remaining - forward_recommended_buy` and is the signal a planner acts on. `tight_week` and `short_week` are fiscal weeks and are null while the pool never crosses in the window. The `notes` block says what the figures leaned on, the cost basis, the category scope, and whether a forward estimate was available at all.

- GetOTBRollupResponse
  - `data` union
    - OTBRollupData
      - `basis` string, required
      - `channel_attribution` object, nullable
      - `cost_comparison` object, nullable
      - `cost_rows` OTBCostRollupRow[], nullable
        - `active_mode` string, nullable
        - `category` string, required
        - `chase_reserve` number, required
        - `committed` number, required
        - `committed_continuity` number
        - `committed_seasonal` number
        - `continuity_consumption_ratio` number, nullable
        - `continuity_envelope_alert` boolean, nullable
        - `current_excluded_amount` number
        - `estimated` boolean
        - `exclusion_active` boolean
        - `forward_continuity_estimate` number, nullable
        - `forward_recommended_buy` number, required
        - `forward_seasonal_estimate` number, nullable
        - `max_band` string, nullable
        - `remaining` number, nullable
        - `remaining_free` number, nullable
        - `reserved` number, required
        - `reserved_continuity` number
        - `reserved_purchase_orders` OTBVendorPurchaseOrder[]
          - `created_at` string, nullable
          - `expected_delivery_date` string, nullable
          - `po_id` integer, required
          - `reference` string, required
          - `status` string, required
          - `value` number, required
        - `reserved_seasonal` number
        - `season_envelope` number, required
        - `short_week` integer, nullable
        - `status` string, nullable
        - `substate` string, nullable
        - `surplus_free` number, nullable
        - `target_amount` number, nullable
        - `tight_week` integer, nullable
        - `variance` number, nullable
      - `filters` object, required
      - `guardrail` object, required
      - `mfp` object, required
      - `notes` object, required
      - `phase` string, required
      - `rows` OTBRollupRow[], required
        - `active_mode` string, nullable
        - `category` string, required
        - `chase_reserve` number, required
        - `committed` number, required
        - `committed_continuity` number
        - `committed_seasonal` number
        - `continuity_consumption_ratio` number, nullable
        - `continuity_envelope_alert` boolean
        - `current_excluded_amount` number
        - `estimated` boolean
        - `exclusion_active` boolean
        - `forward_continuity_estimate` number, nullable
        - `forward_recommended_buy` number, required
        - `forward_seasonal_estimate` number, nullable
        - `max_band` string, nullable
        - `remaining` number, required
        - `remaining_free` number, required
        - `reserved` number, required
        - `reserved_continuity` number
        - `reserved_purchase_orders` OTBVendorPurchaseOrder[]
          - `created_at` string, nullable
          - `expected_delivery_date` string, nullable
          - `po_id` integer, required
          - `reference` string, required
          - `status` string, required
          - `value` number, required
        - `reserved_seasonal` number
        - `season_envelope` number, required
        - `short_week` integer, nullable
        - `status` string, required
        - `substate` string, nullable
        - `surplus_free` number, required
        - `target_amount` number, nullable
        - `tight_week` integer, nullable
        - `variance` number, required
      - `scope` string, required
      - `summary` object, required
      - `totals` object, required
    - object, nullable
  - `message` OTBApiMessage
    - `desc` string
    - `service` string
    - `severity` 'SUCCESS' | 'INFO' | 'ERROR' | 'WARNING'

## Other responses

- `400`: `scope=FY` is refused: the rollup is a per-season pool, and rolling two disjoint selling windows into one running variance produced a crossing week that meant nothing. An unknown sort column is refused here too.
- `401`: No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
- `403`: The key may not reach this operation. `plan_excludes` when the planning rail is sold with Pro and this organisation's plan does not include it; `scope_missing` when the key does not hold Planning; `organization_mismatch` when the path names an organisation that is not the key's; `ip_not_allowed` when the caller's address is outside the key's allowlist.
- `429`: The key has spent its allowance for this minute. 600 reads and 120 writes a minute, counted per key, refused calls included. `Retry-After` says how long to wait.
- `503`: The database is briefly out of reach, which is what a failover or a full connection pool looks like from outside. Wait the seconds `Retry-After` names and send the request again. A read is safe to repeat. A write is not, because the connection was lost in the middle of it, so read the object back before sending it a second time. Branch on the status rather than on `code`, which is `POSTGRES_UNAVAILABLE` from one of the two seams that raise this and `SERVICE_UNAVAILABLE` from the other.

---

[API](https://skmtc.dev/tightly/apis/tightly-api.md) · [All operations](https://skmtc.dev/tightly/apis/tightly-api/llms.txt) · [OpenAPI document](https://skmtc.dev/tightly/apis/tightly-api/revisions/199f7eb35afb?raw)
