---
title: "The buy plan's grid, by category and month"
method: GET
path: "/api/v1/mfp/{mfp_id}/table"
tags: ["mfp"]
---

# The buy plan's grid, by category and month

`GET /api/v1/mfp/{mfp_id}/table`

## Request

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

One buy plan's grid: the planned figure for every category and month of the fiscal year, with actuals filled in for months that have closed. `metric=revenue` reads the sales plan and `metric=margin` the margin plan.

`view` decides which plan is read: `working` is the planner's tip, `live` is the published version in force, and a version id reads that frozen version. A variance you want to reproduce next quarter is read against a version id, not against `live`, because `live` moves when the plan is published again.

A month still open carries a null actual rather than a partial one, and a category nobody has planned carries a null figure rather than a zero. Narrow with `filter_args` on category and sales_channel_id.

Scope: `planning:read`.

## Path parameters

- `mfp_id` string, required

## Query parameters

- `filter_args` string
- `metric` 'revenue' | 'margin' | 'units'
- `view` string

## Headers

- `Tightly-Version` string

## Response `200`

The plan's grid: one row per category, one column per month of the fiscal year, planned figures throughout and actuals only for months that have closed. `months[].is_completed` says which those are, and an in-progress month's actual is null rather than a part-month figure, "the month is not over" and "we are behind" are different readings and only one of them is actionable. `view` echoes the version actually read.

- GetMFPTableResponse
  - `data` union
    - MFPTableData
      - `buy_plan` union
        - BuyPlanData
          - `cost_basis` object, nullable
          - `cost_comparison` object, nullable
          - `identity` BuyPlanIdentityOut, required
            - `converged` boolean, required
            - `cover_wrapped_months` string[], required
            - `cover_wrapped_note` string, nullable
            - `open_to_buy_note` string, required
            - `overstocked_months` string[], required
            - `overstocked_note` string, required
            - `profile` string, required
          - `months` BuyPlanMonthOut[], required
            - `end_exclusive` string, required
            - `fiscal_month` integer, required
            - `key` string, required
            - `label` string, required
            - `month` integer, required
            - `start` string, required
            - `weeks` integer, required
            - `year` integer, required
          - `profile` string, required
          - `rows` BuyPlanRowOut[], required
            - `absence` string, nullable
            - `aggregation` string, required
            - `basis` string, required
            - `cell_basis` string[], required
            - `cells` number[], required
            - `definition` string, required
            - `label` string, required
            - `measure` string, required
            - `unit` string, required
            - `year` number, nullable
            - `year_rule` string, required
          - `terms` BuyPlanTermsOut, required
            - `cover_target_weeks` BuyPlanTermOut, required
              - …
            - `markdown_rate` BuyPlanTermOut, required
              - …
            - `opening_stock` BuyPlanTermOut, required
              - …
        - object, nullable
      - `cost_basis` object, nullable
      - `cost_comparison` object, nullable
      - `cost_outlook` object, nullable
      - `filters` object, required
      - `metric` string, required
      - `mfp` MFPSummary, required
        - `baseline_source` string, required
        - `cell_count` integer, nullable
        - `cost_basis` object, nullable
        - `created_at` string, nullable
        - `fiscal_year` integer, required
        - `fiscal_year_end_month` integer, required
        - `fiscal_year_start_month` integer, required
        - `mfp_id` string, required
        - `name` string, required
        - `status` string, required
        - `total_planned_revenue` number, nullable
      - `months` MFPMonth[], required
        - `is_completed` boolean, nullable
        - `label` string, required
        - `month` integer, required
        - `year` integer, required
      - `revenue_outlook` object, nullable
      - `rows` MFPTableRow[], required
        - `actual` object, required
        - `actual_total` number, nullable
        - `category` string, required
        - `planned` object, required
        - `planned_total` number, nullable
      - `totals` MFPTableTotals, required
        - `actual` object, required
        - `actual_total` number, nullable
        - `planned` object, required
        - `planned_total` number, nullable
      - `units_outlook` object, nullable
      - `view` object, nullable
    - object, nullable
  - `message` MFPApiMessage
    - `desc` string
    - `service` string
    - `severity` 'SUCCESS' | 'INFO' | 'ERROR' | 'WARNING'

## Other responses

- `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)
