---
title: "Approve a proposed plan revision"
method: POST
path: "/v1/experiments/{experiment_id}/finetune-plans/{plan_id}/approve"
tags: ["finetune-plans"]
---

# Approve a proposed plan revision

`POST /v1/experiments/{experiment_id}/finetune-plans/{plan_id}/approve`

## Request

- Base URL: `https://api.pioneer.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.pioneer.ai/v1/experiments/{experiment_id}/finetune-plans/{plan_id}/approve`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer
- TypeScript: <https://skmtc.dev/pioneer/apis/brain-api/docs/v1/experiments/:experiment_id/finetune-plans/:plan_id/approve/post.ts> (a function with typed inputs; its response is checked against the schema)

Authorise a revision the caller was shown.

Args:
    experiment_id: Experiment the plan belongs to.
    plan_id: Revision to approve.
    request: Digest of the revision as displayed, and any edited bar.
    auth: Authenticated caller.

Returns:
    The approved revision.

## Path parameters

- `experiment_id` string, uuid, required
- `plan_id` string, uuid, required

## Request body

- FinetunePlanApproveRequest: Payload for authorising a proposed revision. ``revision_hash`` is the anti-stale check: the caller echoes the hash of the revision they were shown, so a revision that changed between rendering and clicking cannot be approved on the strength of the older display. ``success_criterion`` is the one thing a caller may *change* here rather than merely consent to. Decision 17 gives the agent the proposal, because it knows what is measurable for the task type, and the caller the last word, because it is their money and their definition of done. The edit is still bound by ``revision_hash``: it applies to the revision that was read, not to whatever the plan has since become.
  - `revision_hash` string, required
  - `success_criterion` SuccessCriterionEdit: A caller's replacement for the bar the agent proposed. Whole or nothing, exactly like the envelope's own criterion: a partial edit would leave a bar the stopping rule reads as satisfiable and ends the run against, which is the failure decision 17 exists to prevent.
    - `success_metric` string, required
    - `success_comparison` 'beat_baseline' | 'absolute_threshold', required: What a plan's success threshold is measured against.
    - `success_threshold` number, required
    - `success_suite_id` string, uuid, required
    - `success_rounds_required` integer

## Response `200`

Successful Response

- FinetunePlanResponse: One plan revision as returned to a user-authenticated caller.
  - `id` string, uuid, required
  - `experiment_id` string, uuid, required
  - `revision` integer, required
  - `revision_hash` string, required
  - `envelope` FinetunePlanEnvelope, required: The machine-checked half of a plan (EDD section 6.1). Every field here maps to a check on a real route. A field that does not belongs in the intent instead -- that split is what keeps "envelope" an enforceable word rather than a decorative one. Note there is no spend limit: owner-set limits are team- and project-scoped only (ENG-6083).
    - `expires_at` string, date-time, required
    - `allow_synthetic_data` boolean
    - `allow_eval_upload` boolean
    - `allowed_base_models` string[]
    - `baseline_model_ids` string[]
    - `allow_preview_deployment` boolean
    - `allow_deployment` boolean
    - `success_metric` string, nullable
    - `success_comparison` 'beat_baseline' | 'absolute_threshold': What a plan's success threshold is measured against.
    - `success_threshold` number, nullable
    - `success_suite_id` string, uuid, nullable
    - `success_rounds_required` integer
  - `intent` FinetunePlanIntent, required: The recorded, rendered, never-checked half of a plan (EDD section 6.1). ``estimated_cost_usd`` is server-derived at propose (ENG-6414) and shown beside the work so a user can tell a rounding error from a serious bill. Spend is bounded by team and project limits (ENG-6083), not this row.
    - `objective` string, required
    - `success_criteria` string, nullable
    - `baseline` string, nullable
    - `dataset_reasoning` string, nullable
    - `prose_constraints` string, nullable
    - `extra` object
    - `estimated_cost_usd` string, nullable
    - `estimated_cost_low_usd` string, nullable
    - `estimated_cost_high_usd` string, nullable
  - `quote` FinetunePlanQuoteBasisOutput: Pricing inputs the server quotes from. Assumptions, not constraints. The stored ``estimated_cost_usd`` is derived from these fields via ``quote_cost_usd``. They are not hashed: they are inputs to a value that already is.
    - `quote_model` string, required
    - `estimated_gpu_minutes` string, required
    - `estimated_instance_type` string, required
  - `is_current` boolean, required
  - `proposed_by` string, uuid, required
  - `proposed_at` string, date-time, required
  - `approved_by` string, uuid, nullable, required
  - `approved_at` string, date-time, nullable, required
  - `rejected_by` string, uuid, nullable, required
  - `rejected_at` string, date-time, nullable, required

## Other responses

- `422`: Validation Error

## Changes

- **2026-09-24** `0e5e86e2bae5`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/pioneer/apis/brain-api/changes/v1/experiments/:experiment_id/finetune-plans/:plan_id/approve/post.md)

---

[API](https://skmtc.dev/pioneer/apis/brain-api.md) - [All operations](https://skmtc.dev/pioneer/apis/brain-api/llms.txt) - [OpenAPI document](https://skmtc.dev/pioneer/apis/brain-api/revisions/0e5e86e2bae5?raw)
