# saveWorkflowTable

Create a Workflow Table (`workflowTableId` omitted) or replace an existing one's name, description and columns. The key (`keyKind`, `keyFieldPath`) is fixed at creation; fallback levels may be added later. A column left out of an update is DELETED, with its value in every row: permanent, so this needs the person's approval. Rows are written with saveWorkflowTableRows. Returns the table's shape.

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

## Variables

- `input`: `SaveWorkflowTableInput!` (required). Creates a table when `workflowTableId` is null, otherwise replaces that table's name, description and COLUMN SET. Rows are saved separately (see `saveWorkflowTableRows`) because the editor saves a grid of rows at once and should not have to resend every row to rename a column.
  - `workflowTableId`: `ID`. Null creates a new table.
  - `name`: `String!` (required)
  - `description`: `String`
  - `keyKind`: `WorkflowTableKeyKind!` (required, one of `COUNTY_FIPS`, `STATE`, `CITY`, `ZIP_CODE`, `PROPERTY_TYPE`, `OCCUPANCY_TYPE`, `OWNER_TYPE`, `OWNER_LOCATION`, `MLS_STATUS`, `FIELD_PATH`, `CONTACT_LIST`, `CONTACT_TAG`, `CONTACT_AGENT`, `CONTACT_CONTRIBUTOR`, `PIPELINE`, `DEAL_PIPELINE`, `PIPELINE_STAGE`, `DEAL_STAGE`, `PIPELINE_ITEM_ASSIGNEE`, `DEAL_ASSIGNEE`, `APPOINTMENT_ATTENDEE`). Fixed at creation and ignored on update: re-keying a table would silently invalidate every row's key, and the honest way to do that is a new table. COUNTY_FIPS: A 5-digit county FIPS code, matched against the property's `countyFips`. That is the value `countyFilters` compiles to, so a row matching here is one the same market's saved filter also matches. A display name is never a key. STATE: The property's 2-letter state code, e.g. `FL`. CITY: The property's city, by the `City` record's id: never a typed name. ZIP_CODE: The property's 5-digit ZIP code. PROPERTY_TYPE: The property's `PropertyTy…[truncated: +1389 chars]
  - `keyFieldPath`: `String`. Required when `keyKind` is `FIELD_PATH`: the BUILT-IN contact, deal or appointment variable rows are keyed on, such as `contactPropertyCity` or `dealTitle`. A custom field, a property variable and the refused deal and appointment variables are rejected: see `WorkflowTable.keyFieldPath`. Ignored for every other kind, and ignored on update like `keyKind`.
  - `keyShape`: `WorkflowTableKeyShapeInput`. The fallback chain, composite and ranged keys: see `WorkflowTable.keyShape`. Optional: absent means the single exact key `keyKind` / `keyFieldPath` name. On create, its first part must be that same key. On update it may ADD fallback levels, and change or remove a fallback level that holds no rows; a change to the first level, or to a level that has rows, is refused, because it would reinterpret every one of those rows' keys at once. Absent on update leaves the shape as it is.
    - `levels`: `[WorkflowTableKeyLevelInput!]!` (required)
      - `parts`: `[WorkflowTableKeyPartInput!]!` (required)
        - `kind`: `WorkflowTableKeyKind!` (required, one of `COUNTY_FIPS`, `STATE`, `CITY`, `ZIP_CODE`, `PROPERTY_TYPE`, `OCCUPANCY_TYPE`, `OWNER_TYPE`, `OWNER_LOCATION`, `MLS_STATUS`, `FIELD_PATH`, `CONTACT_LIST`, `CONTACT_TAG`, `CONTACT_AGENT`, `CONTACT_CONTRIBUTOR`, `PIPELINE`, `DEAL_PIPELINE`, `PIPELINE_STAGE`, `DEAL_STAGE`, `PIPELINE_ITEM_ASSIGNEE`, `DEAL_ASSIGNEE`, `APPOINTMENT_ATTENDEE`). What a Workflow Table's key column is keyed ON: the field of the subject each row is matched against. One kind per table, fixed when the table is created. COUNTY_FIPS: A 5-digit county FIPS code, matched against the property's `countyFips`. That is the value `countyFilters` compiles to, so a row matching here is one the same market's saved filter also matches. A display name is never a key. STATE: The property's 2-letter state code, e.g. `FL`. CITY: The property's city, by the `City` record's id: never a typed name. ZIP_CODE: The property's 5-digit ZIP code. PROPERTY_TYPE: The property's `Pr…[truncated: +1397 chars]
        - `fieldPath`: `String`. Required for a `FIELD_PATH` part, refused on every other kind.
        - `match`: `WorkflowTableKeyMatch!` (required, one of `EXACT`, `RANGE`). How one part of a Workflow Table's key is compared with the subject's value. EXACT: Equal, in the key kind's canonical spelling. RANGE: Inside the row's inclusive range. Only offered for kinds whose values have an order: a ZIP code, for one.
  - `columns`: `[SaveWorkflowTableColumnInput!]!` (required). The complete column set, in the order given. A column absent from this list is DELETED along with its values on every row: and a delete is refused outright while a workflow step or a message body still names that column, unless `dependencyResolution` carries a replacement for it.
    - `id`: `ID`. Null creates a new column. An unknown id is rejected.
    - `name`: `String!` (required)
    - `kind`: `WorkflowTableColumnKind!` (required, one of `TEXT`, `LONG_TEXT`, `NUMBER`, `CURRENCY`, `PERCENT`, `BOOLEAN`, `DURATION`, `SELECT`, `DATE`, `TIME_OF_DAY`, `TIMEZONE`, `URL`, `EMAIL`, `PHONE`, `CONTACT_LIST`, `PROPERTY_LIST`, `WORKFLOW_GROUP`, `USER`, `PHONE_LINE`, `TEXT_TEMPLATE`, `EMAIL_TEMPLATE`, `CONTACT_TAG`, `USER_GROUP`, `AI_EMPLOYEE`, `PIPELINE_STAGE`, `DEAL_STAGE`). Ignored on an existing column. A column's kind is what its stored values mean, so changing it would reinterpret every row at once. TEXT: A literal string, rendered into a message body as typed. LONG_TEXT: A literal block of copy. Plain text: it carries no merge fields of its own. NUMBER: A number, stored canonically and rendered grouped (`1,234.5`). CURRENCY: A USD amount, stored as a decimal string and rendered to the cent (`$1,200.00`). PERCENT: A percentage in percent units: `12.5` means 12.5%, rendered `12.5%`. BOOLEAN: A yes/no, stored `true`/`false` and rendered `Yes`/`No`. DURATION: A…[truncated: +1408 chars]
    - `options`: `[WorkflowTableColumnOptionInput!]`. The complete choice set for a `SELECT` column, in the order to present it. Required there and refused on every other kind. Unlike `kind`, this IS editable after the column exists: adding a choice, or renaming one, reinterprets nothing: a cell stores the choice's `value`, and a `value` that disappears leaves its cells readable and merely unpickable.
      - `value`: `String!` (required). What a cell stores. Stable; renaming the label never changes it.
      - `label`: `String!` (required). What a person reads, in the grid and in a rendered message.
  - `dependencyResolution`: `DependencyResolutionInput`. How to repoint everything that names a column this save deletes. Required only when the save actually removes a column that has dependents; the deletion impact dialog is what produces it.
    - `impactVersion`: `String!` (required)
    - `replacements`: `[DependencyReplacementSelectionInput!]!` (required)
      - `targetKind`: `DependencyTargetKind!` (required, one of `PIPELINE_ITEM`, `DEAL`, `PIPELINE_STAGE_PIPELINE_ITEMS`, `DEAL_STAGE_DEALS`, `PIPELINE_ITEM_CUSTOM_FIELD`, `DEAL_CUSTOM_FIELD`, `PIPELINE_ITEM_CUSTOM_FIELD_OPTION`, `DEAL_CUSTOM_FIELD_OPTION`, `PROPERTY_TAG`, `PROPERTY_LIST`, `CONTACT_TAG`, `CONTACT_LIST`, `CONTACT_CUSTOM_FIELD`, `CONTACT_CUSTOM_FIELD_OPTION`, `PIPELINE`, `DEAL_PIPELINE`, `PIPELINE_STAGE`, `DEAL_STAGE`, `TWILIO_PHONE_NUMBER`, `EMAIL_IDENTITY`, `LIBRARY_FOLDER`, `USER`, `USER_GROUP`, `WORKFLOW_GROUP`, `WORKFLOW_AUTOMATION`, `CONTENT_TEMPLATE`, `AGENT`, `MARKETING_CAMPAIGN`, `UPLOADED_FILE`, `WORKFLOW_WEBHOOK_CREDENTIAL`, `SCRAPER_PIPELINE`, `FILTER`, `EMBED_FORM`, `WORKFLOW_TABLE`, `WORKFLOW_TABLE_COLUMN`, `CONTACT`, `PROPERTY`, `ORGANIZATION`, `SLACK_CHANNEL`, `SLACK_USER`, `MACHINE_GROUP`, `MACHINE_HOST`)
      - `targetId`: `ID!` (required)
      - `replacementId`: `ID!` (required)
  - `expectedUpdatedAt`: `DateTime`. Optimistic-concurrency token: the table's `updatedAt` as the editor loaded it. When provided and the row has changed since, the save is refused with CONFLICT rather than replacing whatever the other editor committed. Omit only on create, where there is nothing to conflict with.

## Response (data, an example)

```json
{
  "workflowAutomationsMutation": {
    "saveWorkflowTable": {
      "id": "<ID>",
      "name": "…",
      "description": "…",
      "keyKind": "COUNTY_FIPS",
      "keyFieldPath": "…",
      "keyShape": {
        "levels": [
          {
            "parts": [
              {
                "kind": "COUNTY_FIPS",
                "fieldPath": "…",
                "match": "EXACT"
              }
            ]
          }
        ]
      },
      "rowCount": 0,
      "isProvisioned": true,
      "updatedAt": "2026-01-01T00:00:00.000Z",
      "columns": [
        {
          "id": "<ID>",
          "name": "…",
          "kind": "TEXT",
          "displayOrder": 0,
          "tokenKey": "…",
          "options": [
            {
              "value": "…",
              "label": "…"
            }
          ]
        }
      ]
    }
  }
}
```

## 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": "saveWorkflowTable",
    "variables": {
      "input": {
        "name": "<String>",
        "keyKind": "COUNTY_FIPS",
        "columns": [
          {
            "name": "<String>",
            "kind": "TEXT"
          }
        ]
      }
    }
  }'
```
