---
title: Sign-off
kind: resource
slug: signoff
summary: "Where a decision stands and what you can do about it: who needs to weigh in, how each person has responded, the recorded call — and, scoped to you, the actions you can take right now."
---

# Sign-off

Where a decision stands and what you can do about it: who needs to weigh in, how each person has responded, the recorded call — and, scoped to you, the actions you can take right now.

A **decision** is a Droplet wearing the `decision` Type (see the [Decisions](/docs/decisions) concept). Its **sign-off** is the live state of that decision: the RAPID roster, each approver's response, and the decider's recorded call. There is no separate "decision" object — the Droplet holds the content, the Type makes it a decision, and this resource is how you read and drive its sign-off.

**The read (`get`) is affordance-bearing.** It returns the current state *and* the actions available to you, each with a reason when it isn't (e.g. "only the decider can record the call"). A person — or an agent — can act on what's `available` without having to know the rules.

**What you can do:**

- **`cast`** — record a response as an approver: agree, agree with a condition, or disagree (a comment is required for the latter two). Pass `onBehalfOf` to record someone else's position — see **Recording for someone else** below.
- **`clear`** — remove a recorded response while sign-off is still open. Yours, or (as a facilitator) one you recorded for someone.
- **`finalize`** — the decider records the call: approve → decided, block → blocked. (In practice the decider's own `cast` records it.)
- **`reopen`** — clear a recorded call and return the decision to open.
- **`open` / `close`** — an editor or facilitator turns sign-off on (draft → in flight) or back off (→ draft); responses are retained either way. To take the decision off the Droplet altogether, clear its Type ([`droplet.clearType`](/docs/resources/droplet)) — that discards the roster, the responses and the call, and reports what it took.
- **`set-proposition`** — an editor or facilitator sets "what you're signing off on", the statement shown above the roster.
- **`mine`** — across every decision you can reach, the ones waiting on you.
- **`list`** — every decision you can see, each with its sign-off state; filter by `phase`, your `role`, or `tag`. (The query behind the [Decisions](/docs/decisions) dashboard.)

**Recording for someone else.** Decisions get made in rooms, and the person running the room is often not the person holding the role. Members of the `@drop-facilitators` group (and editors on the Droplet) can pass `onBehalfOf` to `cast` and `clear`, and can drive the lifecycle — `open`, `close`, `set-proposition`, and the roster — on any decision **they can already read**. Group membership grants no access of its own, and never unlocks editing the Droplet itself.

A proxied response carries `castBy` on the roster row, and the event log records both who acted and who it was for. The subject overwrites it by casting themselves, which clears `castBy` and makes the row self-affirmed again. Recording the **decider's** response records the call, exactly as their own cast would — attributed to them, and reopenable by them.

Everything else is scoped to the calling identity: you only see and act on decisions you can access, in the roles you actually hold. The behavior is generic — a Type declares which of its `user-list` fields carry the approver and decider roles and which `status` values gate the flow — so the same surface works for any sign-off-shaped Type. A RAPID decision is the first one.

## The Sign-off object

### Attributes

- **`configured`** `bool` — Whether the Droplet's Type is sign-off-shaped. When false, there is no sign-off — the rest of the object is empty.
- **`phase`** `string` — Lifecycle phase: `draft` (not yet open), `open` (collecting sign-offs), `decided`, or `blocked`.
- **`status`** `string · nullable` — The raw value of the Type's status field that the phase is derived from.
- **`proposition`** `string · nullable` — The editor-authored statement of what people are signing off on, shown above the roster.
- **`roster`** `json` — Every RAPID role on the Type (Recommend / Agree / Perform / Input / Decide) with its members. `isApprover` marks the roles that cast sign-offs. Members are people or teams; a team in an approver role carries a `tally` of how its members voted.
- **`approvers`** `json` — The flat approver list with each response: `{ email, name, vote (agree|condition|disagree|null), comment, castBy }`. `vote: null` = hasn't weighed in. `castBy` names the facilitator who recorded it for them, and is null when they recorded it themselves.
- **`signedCount`** `int` — Approvers who have responded (agree or condition).
- **`totalApprovers`** `int` — Total approvers across the approver roles (teams expanded to members).
- **`verdict`** `json · nullable` — The recorded call once decided: `{ outcome (approved|blocked), summary, decidedBy, decidedAt }`. Null while open.
- **`openedAt`** `string · nullable` — When sign-off opened (ISO). Powers the "in flight · 3d" age. Null while draft.
- **`viewer`** `json` — The caller's standing: `{ email, isApprover, isDecider, vote, comment, canManage, canFacilitate, castBy }`. `canManage` = editor on the Droplet. `canFacilitate` = may drive this decision (record for others, open/close, proposition, roster) — true for editors and for `@drop-facilitators` members who can read it. `castBy` is set when your own response was recorded by someone else.
- **`canClose`** `bool` — Whether the caller can un-enable (return to draft) right now — editor or facilitator, open phase, Type has a draft status.
- **`actions`** `json` — Affordances for the caller: `{ name (cast|clear|finalize|reopen|cast-for-others), available, reason? }`. Act on `available`; surface `reason` when blocked. `cast-for-others` answers "may I record someone else's position", which `cast` (your own) does not.

### JSON example

```json
{
  "configured": true,
  "phase": "open",
  "status": "in-flight",
  "proposition": "Adopt RDS Proxy in front of the SSR fleet to end burst-time connection storms.",
  "roster": [
    { "key": "recommend", "label": "Recommend", "isApprover": false, "members": [{ "kind": "user", "email": "kate@himarley.com", "name": "Kate Horning" }] },
    { "key": "agree", "label": "Agree", "isApprover": true, "members": [
      { "kind": "group", "slug": "platform-team", "name": "Platform Team", "memberCount": 3, "hasViewer": true, "tally": { "agree": 1, "condition": 1, "disagree": 0, "pending": 1 } }
    ] },
    { "key": "decide", "label": "Decide", "isApprover": false, "members": [{ "kind": "user", "email": "will@himarley.com", "name": "Will Sherlin" }] }
  ],
  "approvers": [
    { "email": "you@himarley.com", "name": "You", "vote": null, "comment": "" },
    { "email": "sarah@himarley.com", "name": "Sarah Lee", "vote": "condition", "comment": "Confirm the rollback plan first", "castBy": "kate@himarley.com" }
  ],
  "signedCount": 1,
  "totalApprovers": 3,
  "verdict": null,
  "openedAt": "2026-08-05T14:00:00.000Z",
  "viewer": { "email": "you@himarley.com", "isApprover": true, "isDecider": false, "vote": null, "canManage": false, "canFacilitate": false, "castBy": null },
  "canClose": false,
  "actions": [
    { "name": "cast", "available": true },
    { "name": "clear", "available": false, "reason": "you have not signed off yet" },
    { "name": "finalize", "available": false, "reason": "only the decider can record the call" },
    { "name": "reopen", "available": false, "reason": "nothing to reopen — no verdict recorded" },
    { "name": "cast-for-others", "available": false, "reason": "you are not a facilitator or editor on this decision" }
  ]
}
```

## Methods

### Get sign-off

`GET /api/apps/{slug}/signoff`

Read a Droplet's sign-off state — roles, responses, verdict, status — **plus the actions available to you** (an affordance-bearing read). Blocked actions carry a reason. Everything is scoped to the calling identity: you only see what you can act on.

**CLI**

```bash
drop signoff get migrate-drop-to-ai
```

**MCP**

```json
{
  "tool": "drop_signoff_get",
  "arguments": {
    "slug": "migrate-drop-to-ai"
  }
}
```

**HTTP**

```bash
curl -X GET 'https://drop.himarley.ai/api/apps/migrate-drop-to-ai/signoff' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "configured": false,
  "status": "example",
  "phase": "draft",
  "proposition": "example",
  "roster": [],
  "approvers": [],
  "signedCount": 0,
  "totalApprovers": 0,
  "responseCount": 0,
  "verdict": {
    "outcome": "approved",
    "summary": "example",
    "decidedBy": "example",
    "decidedAt": "example"
  },
  "openedAt": "example",
  "viewer": {
    "email": "example",
    "isApprover": false,
    "isDecider": false,
    "vote": "agree",
    "comment": "example",
    "canManage": false,
    "canFacilitate": false,
    "castBy": "example",
    "castByName": "example"
  },
  "canClose": false,
  "blockedApprovers": [],
  "actions": []
}
```

### Cast sign-off

`POST /api/apps/{slug}/signoff`

Record a response as an approver: `agree`, `condition` (agree with a condition), or `disagree`. A comment is required for `condition` and `disagree`. Approver-gated — the subject must hold an approver or decider role on the Droplet's Type. Casting again updates the response; use `clear` to remove it.

Pass `onBehalfOf` to record someone else's position, which is what a facilitator does live in a meeting. The response is attributed (`castBy`), the event log records both who acted and who it was for, and the subject can overwrite it themselves at any time — which clears the attribution.

**CLI**

```bash
drop signoff cast migrate-drop-to-ai --vote agree
```

**MCP**

```json
{
  "tool": "drop_signoff_cast",
  "arguments": {
    "slug": "migrate-drop-to-ai",
    "vote": "agree"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/apps/migrate-drop-to-ai/signoff' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"vote":"agree"}'
```

**Response** (200)

```json
{
  "configured": false,
  "status": "example",
  "phase": "draft",
  "proposition": "example",
  "roster": [],
  "approvers": [],
  "signedCount": 0,
  "totalApprovers": 0,
  "responseCount": 0,
  "verdict": {
    "outcome": "approved",
    "summary": "example",
    "decidedBy": "example",
    "decidedAt": "example"
  },
  "openedAt": "example",
  "viewer": {
    "email": "example",
    "isApprover": false,
    "isDecider": false,
    "vote": "agree",
    "comment": "example",
    "canManage": false,
    "canFacilitate": false,
    "castBy": "example",
    "castByName": "example"
  },
  "canClose": false,
  "blockedApprovers": [],
  "actions": []
}
```

### Clear sign-off

`POST /api/apps/{slug}/signoff/clear`

Remove a recorded sign-off response while the decision is still open. Clears your own by default; a facilitator can pass `onBehalfOf` to remove a response they recorded for someone. Finalized decisions must be reopened before responses can change.

**CLI**

```bash
drop signoff clear migrate-drop-to-ai
```

**MCP**

```json
{
  "tool": "drop_signoff_clear",
  "arguments": {
    "slug": "migrate-drop-to-ai"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/apps/migrate-drop-to-ai/signoff/clear' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "configured": false,
  "status": "example",
  "phase": "draft",
  "proposition": "example",
  "roster": [],
  "approvers": [],
  "signedCount": 0,
  "totalApprovers": 0,
  "responseCount": 0,
  "verdict": {
    "outcome": "approved",
    "summary": "example",
    "decidedBy": "example",
    "decidedAt": "example"
  },
  "openedAt": "example",
  "viewer": {
    "email": "example",
    "isApprover": false,
    "isDecider": false,
    "vote": "agree",
    "comment": "example",
    "canManage": false,
    "canFacilitate": false,
    "castBy": "example",
    "castByName": "example"
  },
  "canClose": false,
  "blockedApprovers": [],
  "actions": []
}
```

### Finalize sign-off

`POST /api/apps/{slug}/signoff/finalize`

Record the decider's call and advance the Droplet's status (approve → decided, block → blocked). Decider-gated — the caller must hold the decider role. A summary is required.

**CLI**

```bash
drop signoff finalize migrate-drop-to-ai --outcome approved --summary "Approved — migrate to himarley.ai over two weeks."
```

**MCP**

```json
{
  "tool": "drop_signoff_finalize",
  "arguments": {
    "slug": "migrate-drop-to-ai",
    "outcome": "approved",
    "summary": "Approved — migrate to himarley.ai over two weeks."
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/apps/migrate-drop-to-ai/signoff/finalize' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"outcome":"approved","summary":"Approved — migrate to himarley.ai over two weeks."}'
```

**Response** (200)

```json
{
  "configured": false,
  "status": "example",
  "phase": "draft",
  "proposition": "example",
  "roster": [],
  "approvers": [],
  "signedCount": 0,
  "totalApprovers": 0,
  "responseCount": 0,
  "verdict": {
    "outcome": "approved",
    "summary": "example",
    "decidedBy": "example",
    "decidedAt": "example"
  },
  "openedAt": "example",
  "viewer": {
    "email": "example",
    "isApprover": false,
    "isDecider": false,
    "vote": "agree",
    "comment": "example",
    "canManage": false,
    "canFacilitate": false,
    "castBy": "example",
    "castByName": "example"
  },
  "canClose": false,
  "blockedApprovers": [],
  "actions": []
}
```

### Reopen sign-off

`POST /api/apps/{slug}/signoff/reopen`

Clear a recorded verdict and return the Droplet to the open status so approvers can sign off again. Open to the decider AND to facilitators — a facilitator can record the decider's call, so they have to be able to unmake it, or proxying a verdict would be a one-way door.

**CLI**

```bash
drop signoff reopen migrate-drop-to-ai
```

**MCP**

```json
{
  "tool": "drop_signoff_reopen",
  "arguments": {
    "slug": "migrate-drop-to-ai"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/apps/migrate-drop-to-ai/signoff/reopen' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "configured": false,
  "status": "example",
  "phase": "draft",
  "proposition": "example",
  "roster": [],
  "approvers": [],
  "signedCount": 0,
  "totalApprovers": 0,
  "responseCount": 0,
  "verdict": {
    "outcome": "approved",
    "summary": "example",
    "decidedBy": "example",
    "decidedAt": "example"
  },
  "openedAt": "example",
  "viewer": {
    "email": "example",
    "isApprover": false,
    "isDecider": false,
    "vote": "agree",
    "comment": "example",
    "canManage": false,
    "canFacilitate": false,
    "castBy": "example",
    "castByName": "example"
  },
  "canClose": false,
  "blockedApprovers": [],
  "actions": []
}
```

### Open for sign-off

`POST /api/apps/{slug}/signoff/open`

Open a draft decision for sign-off — flips its status to the open value so approvers can respond. Facilitator-gated: editors on the Droplet, plus @drop-facilitators members who can read it. Backs the "Enable sign-off" author action and the rapid agent opening the room after staffing.

**CLI**

```bash
drop signoff open migrate-drop-to-ai
```

**MCP**

```json
{
  "tool": "drop_signoff_open",
  "arguments": {
    "slug": "migrate-drop-to-ai"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/apps/migrate-drop-to-ai/signoff/open' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "configured": false,
  "status": "example",
  "phase": "draft",
  "proposition": "example",
  "roster": [],
  "approvers": [],
  "signedCount": 0,
  "totalApprovers": 0,
  "responseCount": 0,
  "verdict": {
    "outcome": "approved",
    "summary": "example",
    "decidedBy": "example",
    "decidedAt": "example"
  },
  "openedAt": "example",
  "viewer": {
    "email": "example",
    "isApprover": false,
    "isDecider": false,
    "vote": "agree",
    "comment": "example",
    "canManage": false,
    "canFacilitate": false,
    "castBy": "example",
    "castByName": "example"
  },
  "canClose": false,
  "blockedApprovers": [],
  "actions": []
}
```

### Close (un-enable) sign-off

`POST /api/apps/{slug}/signoff/close`

Un-enable sign-off — return an open decision to draft. Facilitator-gated: editors on the Droplet, plus @drop-facilitators members who can read it. The inverse of `signoff.open`; retains responses so re-opening keeps prior sign-offs.

**CLI**

```bash
drop signoff close migrate-drop-to-ai
```

**MCP**

```json
{
  "tool": "drop_signoff_close",
  "arguments": {
    "slug": "migrate-drop-to-ai"
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/apps/migrate-drop-to-ai/signoff/close' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "configured": false,
  "status": "example",
  "phase": "draft",
  "proposition": "example",
  "roster": [],
  "approvers": [],
  "signedCount": 0,
  "totalApprovers": 0,
  "responseCount": 0,
  "verdict": {
    "outcome": "approved",
    "summary": "example",
    "decidedBy": "example",
    "decidedAt": "example"
  },
  "openedAt": "example",
  "viewer": {
    "email": "example",
    "isApprover": false,
    "isDecider": false,
    "vote": "agree",
    "comment": "example",
    "canManage": false,
    "canFacilitate": false,
    "castBy": "example",
    "castByName": "example"
  },
  "canClose": false,
  "blockedApprovers": [],
  "actions": []
}
```

### Set the proposition

`POST /api/apps/{slug}/signoff/proposition`

Set "what you're signing off on" — the succinct statement shown above the sign-off. Facilitator-gated (`canFacilitateSignoff`): editors on the Droplet, plus @drop-facilitators members who can read it. The tray's inline editor and the rapid agent both drive this; pass an empty string to clear it.

**CLI**

```bash
drop signoff set-proposition migrate-drop-to-ai --proposition "Adopt RDS Proxy in front of the SSR fleet to end burst-time connection storms."
```

**MCP**

```json
{
  "tool": "drop_signoff_set_proposition",
  "arguments": {
    "slug": "migrate-drop-to-ai",
    "proposition": "Adopt RDS Proxy in front of the SSR fleet to end burst-time connection storms."
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/apps/migrate-drop-to-ai/signoff/proposition' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"proposition":"Adopt RDS Proxy in front of the SSR fleet to end burst-time connection storms."}'
```

**Response** (200)

```json
{
  "configured": false,
  "status": "example",
  "phase": "draft",
  "proposition": "example",
  "roster": [],
  "approvers": [],
  "signedCount": 0,
  "totalApprovers": 0,
  "responseCount": 0,
  "verdict": {
    "outcome": "approved",
    "summary": "example",
    "decidedBy": "example",
    "decidedAt": "example"
  },
  "openedAt": "example",
  "viewer": {
    "email": "example",
    "isApprover": false,
    "isDecider": false,
    "vote": "agree",
    "comment": "example",
    "canManage": false,
    "canFacilitate": false,
    "castBy": "example",
    "castByName": "example"
  },
  "canClose": false,
  "blockedApprovers": [],
  "actions": []
}
```

### Sign-offs awaiting me

`GET /api/signoff/mine`

List the sign-offs across all Droplets you can reach where you hold a role — filtered to those awaiting your action. Powers a personal "decisions awaiting your sign-off" inbox and lets an agent ask "what's blocked on me?"

**CLI**

```bash
drop signoff mine
```

**MCP**

```json
{
  "tool": "drop_signoff_mine",
  "arguments": {}
}
```

**HTTP**

```bash
curl -X GET 'https://drop.himarley.ai/api/signoff/mine' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "items": []
}
```

### List decisions

`GET /api/signoff`

List every decision you can see, each with its sign-off state (phase, who decides, how far along). Optionally filter by `phase`, your `role` (decider|approver), or `tag`. This is the query behind the /decisions dashboard — the answer to "list all in-flight decisions and where each stands" or "decisions where I'm the decider".

**CLI**

```bash
drop signoff list
```

**MCP**

```json
{
  "tool": "drop_signoff_list",
  "arguments": {}
}
```

**HTTP**

```bash
curl -X GET 'https://drop.himarley.ai/api/signoff' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "items": []
}
```
