---
title: "Every published version of a buy plan, and the working tip"
method: GET
path: "/api/v1/mfp/{mfp_id}/versions"
tags: ["mfp"]
---

# Every published version of a buy plan, and the working tip

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

## Request

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

Every published version of one buy plan, newest first, each with its totals and whether it is the version the plan currently reads as live, plus the working tip and whether it holds changes nobody has published.

This is what answers "what did the plan say in January". A version is frozen: its totals do not move when the live plan does, which is what makes a variance reproducible six months later. Read one version's grid with `get_mfp_table` and `view=<version_id>`.

Scope: `planning:read`.

## Path parameters

- `mfp_id` string, required

## Headers

- `Tightly-Version` string

## Response `200`

Version history

- GetMFPVersionsResponse
  - `data` union
    - MFPVersionsData
      - `live_version_id` integer, nullable
      - `mfp` object, required
      - `versions` MFPVersionSummary[]
        - `budget_state` string, nullable
        - `budget_stated_at` string, nullable
        - `cost_basis` object, nullable
        - `gross_margin_pct` number, nullable
        - `is_live` boolean, required
        - `name` string, required
        - `published_at` string, nullable
        - `published_by` string, nullable
        - `total_planned_cogs` number, nullable
        - `total_planned_revenue` number, nullable
        - `version_id` integer, required
        - `version_number` integer, required
      - `working` object, required
    - 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.
- `404`: Plan not found
- `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)
