Opening Final Frame
Verifying your credentialsEstablishing an encrypted sessionPreparing your workspace
Secured by WorkOS
This is taking longer than usual. Your connection may be slow.

Deal approvals

Both sides of a deal approve it in five stages, in order. A stage is approved when both sides have approved the same version of it. Approvals open once the other party has accepted the handoff; before that, GET /v1/projects/{id}/approvals answers linked: false and an approval is refused with 409. In the product it is the Deal Approvals tab under Summary.

StageKeyWhat both sides agree
Deliverables listdeliverablesThe total list of deliverables across both sides of the deal.
DeliverydeliveryWhere everything ships to: their cloud storage, Aspera, Signiant, MASV, Flomenco, Mediafier, their Final Frame organisation, or Final Frame orchestrating, with where and any notes. The route is chosen in the product.
Work orderswork_ordersEvery deliverable sits on a work order: how many orders, what is unassigned, and who does the work.
Deal costcostEach side’s Estimated per currency, as its own Costs tab shows it. An estimate is fine at this point.
Delivery acceptanceacceptanceWhat arrived. Both sides' acceptance shows on the close-out as acceptance.

Where a stage stands

Each stage is read from the caller's side, so the two organisations on a deal see mirrored states.

StateMeaning
not_startedNeither side has approved the current version.
waiting_on_themThis side approved the current version; the other side has not.
waiting_on_youThe other side approved the current version; this side has not.
approvedBoth sides approved the same version.
needs_reapprovalAn approval no longer stands: what was approved changed, or a change request reopened the stage.

Every approval records who, when and the exact version: a fingerprint of the stage and a snapshot of it. version is the first eight characters of the fingerprint. When the deliverables, the route, the work orders, the estimate or what arrived moves on, the fingerprint no longer matches and the stage needs approving again; changes lists what was added, removed and changed since. Statuses are left out of every fingerprint except acceptance, so work progressing never undoes an agreement. The cost stage follows the estimate itself, so a file that arrives and is measured, or needs converting, moves it as a rate or a work order does.

Who approves

Each side approves through its own approvers. A person's stages are set across every deal on their Users sheet, and can be overridden per deal on the Deal Approvals tab. Where nobody is named for a stage, anyone on that side who holds project.configure may approve it. A key acts for its person: that person must be an approver for the stage, and the key must also carry project.configure. Each stage says mayApprove and, when it is false, whyNot.

Project settings hold two switches per stage. Required is on by default. Gating is off by default; on, this side cannot approve a later stage, or raise work orders when the gate sits before Work orders, until both sides have approved it. A refusal is 409 naming the stage in blockedBy. The sender's handoff permissions can make approvals read-only for the receiving side.

Change requests

Once a stage is approved by both sides, a change goes as a change request. The side that did not raise it answers approved or declined; the side that did can answer withdrawn. Approving one resets both sides' approvals of its stage, so both approve it again. A delivery route both sides approved changes only this way.

API

RouteWhat it doesNeeds
GET /v1/projects/{id}/approvalsEvery stage from this side, with each side’s approval, the approvers, what changed, then the change requests and the route.project.view
POST /v1/projects/{id}/approvals/{stage}/approveApprove one stage for this side. Send expect, the version you read; a stage that changed since is 409. Optional note.project.view, project.configure
POST /v1/projects/{id}/approvals/change-requestsPropose a change: stage and description.project.view, project.configure
POST /v1/projects/{id}/approvals/change-requests/{changeRequestId}/answerdecision (approved, declined or withdrawn) and an optional note.project.view, project.configure

Every write takes an Idempotency-Key and lands on the action log with the credential named.

curl https://qa.app.final-frame.com/api/v1/projects/<projectId>/approvals \
  -H "Authorization: Bearer <your key>"
# {"linked":true,"side":"sender","partner":{"orgId":"...","name":"..."},
#  "stages":[{"stage":"deliverables","state":"waiting_on_you","version":"3f9a1c07","mayApprove":true,...},...],
#  "changeRequests":[],"route":{...}}

curl -X POST https://qa.app.final-frame.com/api/v1/projects/<projectId>/approvals/deliverables/approve \
  -H "Authorization: Bearer <your key>" \
  -H "Idempotency-Key: approve-deliverables-3f9a1c07" \
  -H "content-type: application/json" \
  -d '{"expect":"3f9a1c07"}'
# 201 {"approval":{"stage":"deliverables","side":"sender","version":"3f9a1c07",...},"approvals":{...}}

From an assistant: read_deal_approvals, approve_deal_stage, request_deal_change and answer_deal_change, listed on MCP.

Webhooks

Every approval event goes to both organisations on the deal, each under its own project, and carries the deal's handoffId.

Eventdata
approval.approvedstage, side, approvedBy, version, fingerprint, summary, and agreed: whether both sides now agree.
approval.revokedstage, side, revokedBy, note. A side withdraws its approval in the product before the other side agrees.
approval.resetstage, side, reason (changed or change_request) and changeRequestId.
approval.change_requestedThe change request: changeRequestId, stage, side, description, status, raisedBy, raisedAt.
approval.change_resolvedThe same, with decision, resolvedBy, resolvedAt and resolutionNote.

A change is noticed on the next read of the approvals (the tab, the API, an MCP tool or an approval), so approval.reset arrives then. Every event is on Webhooks.