# updateDeal

Update a deal by id: title, price, close date, stage. THIS IS THE STAGE-MOVE OPERATION: send input { stageId } alone (from listPipelineStages); fields left out are untouched, and the move stamps lastStageChangeAt. A stage change is POTENTIALLY OUTBOUND by TWO paths: (1) entering the stage wakes any DEAL workflow triggered on it; (2) it fires a deal-updated event for EVERY attached contact, and a CONTACT workflow anywhere in the org can match the destination stage, so a clean pipeline does NOT prove the move is silent. Either can text or email a real person. Do NOT reason "reversible, so just do it": undoing the move does not recall a message. A move the person asked for by name is their yes; any other move of a deal with contacts needs their yes before it runs (a card counts). The deal, stage and linked contactIds/userIds must belong to your organization (or a pipeline shared with it). contactIds, propertyIds and userIds REPLACE the whole linked set: send every id to keep. Pass expectedUpdatedAt (updatedAt from getDeal, findDeals or a previous write) whenever you decided from an earlier read, so a concurrent edit fails loudly. It makes the write conditional and ALL-OR-NOTHING: a change since gives CONFLICT with nothing written, and a rejected userIds, participantSplits or customFieldValues rolls the whole edit back. The token is single-use: re-read before guarding again. Without it a later custom-field failure cannot undo the committed core write. A deleted deal is NOT_FOUND (do not retry); a bad token is BAD_USER_INPUT. MONEY AND CUSTOM FIELDS (the whole input is one variable, so discovery cannot expand it). One commission source per side: `commissionAmountCents` XOR `commissionPercentBps` (basis points, 300 = 3%, of priceCents), and `teamSplitAmountCents` XOR `teamSplitPercentBps` (of the COMMISSION). Both halves with values is rejected; one sent as null CLEARS that side. The server derives `commissionCents` (GCI) and returns it: read it there. A percent re-derives when priceCents changes; an amount does not. `participantSplits` is [{ userId, splitAmountCents | splitPercentBps }] of what remains after the team fee, within the pool. `customFieldValues` is [{ customFieldId (listDealCustomFields; fields are PER-PIPELINE), exactly ONE of textValue (TEXT/LINK) | numberValue (NUMBER/DOLLAR, a plain number, NOT cents) | dateValue (DATE, ISO 8601) | dropdownSelectedValues (DROPDOWN option LABELS, not ids) }]. The response echoes the money and custom fields: confirm the write from it.

- Call: `POST https://server.goliathdata.com/api/v1/call` with `{ "operationId": "updateDeal", "variables": {...} }`
- Scope: `WRITE`
- Type: mutation
- Idempotency: honors `Idempotency-Key` (24 h)
- Rate-limited: no

## Variables

- `dealId`: `ID!` (required)
- `input`: `PipelineItemMutationInput!` (required)
  - `title`: `String`
  - `stageId`: `ID`
  - `description`: `String`
  - `priceCents`: `BigInt`
  - `commissionAmountCents`: `BigInt`
  - `commissionPercentBps`: `Int`
  - `teamSplitAmountCents`: `BigInt`
  - `teamSplitPercentBps`: `Int`
  - `participantSplits`: `[PipelineItemParticipantSplitInput!]`
    - `userId`: `ID!` (required)
    - `splitAmountCents`: `BigInt`
    - `splitPercentBps`: `Int`
  - `closeDate`: `DateTime`
  - `propertyIds`: `[ID!]`
  - `propertyStrings`: `[String!]`
  - `contactIds`: `[ID!]`
  - `userIds`: `[ID!]`
  - `fileIds`: `[ID!]`
  - `order`: `Int`
  - `customFieldValues`: `[PipelineItemCustomFieldValueInput!]`
    - `customFieldId`: `ID!` (required)
    - `textValue`: `String`
    - `dateValue`: `DateTime`
    - `numberValue`: `Float`
    - `dropdownSelectedValues`: `[String!]`
- `expectedUpdatedAt`: `DateTime`

## Response (data, an example)

```json
{
  "dealMutation": {
    "updateDeal": {
      "id": "<ID>",
      "title": "…",
      "priceCents": "…",
      "closeDate": "2026-01-01T00:00:00.000Z",
      "isArchived": true,
      "updatedAt": "2026-01-01T00:00:00.000Z",
      "lastStageChangeAt": "2026-01-01T00:00:00.000Z",
      "commissionCents": "…",
      "commissionAmountCents": "…",
      "commissionPercentBps": 0,
      "teamSplitAmountCents": "…",
      "teamSplitPercentBps": 0,
      "participantSplits": [
        {
          "user": {
            "id": "<ID>",
            "firstName": "…",
            "lastName": "…"
          },
          "splitAmountCents": "…",
          "splitPercentBps": 0
        }
      ],
      "customFieldValues": [
        {
          "id": "<ID>",
          "textValue": "…",
          "dateValue": "2026-01-01T00:00:00.000Z",
          "numberValue": 0,
          "dropdownSelectedValues": [
            "…"
          ],
          "dealCustomField": {
            "id": "<ID>",
            "name": "…",
            "type": "DROPDOWN"
          }
        }
      ],
      "stage": {
        "id": "<ID>",
        "name": "…"
      },
      "pipeline": {
        "id": "<ID>",
        "name": "…"
      },
      "contacts": [
        {
          "id": "<ID>",
          "name": "…"
        }
      ],
      "properties": [
        {
          "id": "<ID>",
          "address": {
            "addressFull": "…"
          }
        }
      ]
    }
  }
}
```

## Example

```bash
curl https://server.goliathdata.com/api/v1/call \
  -H "Authorization: Bearer $GOLIATH_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "operationId": "updateDeal",
    "variables": {
      "dealId": "<dealId>",
      "input": {}
    }
  }'
```
