---
title: "Call widget.update"
method: POST
path: "/api/v1/tools/widget.update"
tags: ["widget"]
---

# Call widget.update

`POST /api/v1/tools/widget.update`

## Request

- Base URL: `https://app.goosybear.ai`
- URL: `https://app.goosybear.ai/api/v1/tools/widget.update`
- Auth: HTTP bearer

Change what a widget that already exists shows — 'make it look back 90 days', 'point the button at the weekly report automation', 'show bookings instead of visits' — the numbers it shows and how far back it looks, its 'tell me when' threshold, a note's words, where a link goes, or what a button presses or a switch turns. Only the parts you pass change. What kind of widget it is cannot change: that is a new widget. The change shows wherever the widget is placed. Not for its name — that is `item.rename`; not for what one placement reads — that is `widget.bind`; not for how long it stays up — that is `widget.set_expiry`.

Every result names the workspace it ran in. Say which workspace the answer is about. When the account has more than one workspace and none is selected, this tool refuses with `workspace_ambiguous` and lists the choices — offer them, never pick one.

## Headers

- `x-request-id` string

## Request body

- object
  - `widget_id` string, required: The widget's id, from `widget.list`. Never an id you did not read.
  - `metric_keys` string[]: Which numbers it shows. Required on chart, table, kpi and monitor; omitted on every other kind.
  - `grain` 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year' | 'lifetime': How the numbers are bucketed. Use 'day' for a chart unless they asked for weeks or months.
  - `window_periods` integer: How far back it looks, counted in `grain` periods. 'the last 28 days' with grain=day is 28.
  - `compare` 'none' | 'previous_period' | 'same_period_last_year': What it compares against. 'previous_period' is the usual answer; 'none' shows a bare number.
  - `entity` object: The thing a monitor watches, e.g. {kind: 'ad_set', id: '...'}. Required for that kind.
    - `kind` string, required
    - `id` string, required
  - `condition` object: The 'tell me when' threshold. 'no_change_for' takes a number of days as its value.
    - `metric_key` 'rev_leads' | 'rev_booked_calls' | 'rev_calls_showed' | 'rev_show_rate' | 'rev_sales_won' | 'rev_revenue_won' | 'rev_revenue_by_source' | 'rev_open_pipeline_value' | 'email_sent' | 'email_delivered' | 'email_delivery_rate' | 'email_opened' | 'email_open_rate' | 'email_clicked' | 'email_click_rate' | 'email_replied' | 'email_complaint_rate' | 'email_bounce_rate' | 'list_size' | 'list_new_subscribers' | 'list_unsubscribe_rate' | 'ads_spend' | 'ads_impressions' | 'ads_clicks' | 'ads_ctr' | 'ads_cpc' | 'ads_cpm' | 'ads_reach' | 'ads_frequency' | 'ads_conversions' | 'ads_cost_per_lead' | 'ads_cost_per_booked_call' | 'ads_attributed_revenue' | 'vid_views' | 'vid_watch_time_minutes' | 'vid_avg_view_duration' | 'vid_impressions' | 'vid_impressions_ctr' | 'vid_subscribers_gained' | 'vid_net_subscribers' | 'vid_breakout_score' | 'soc_posts_published' | 'soc_impressions' | 'soc_engagements' | 'soc_engagement_rate' | 'soc_meta_impressions' | 'soc_meta_engagements' | 'soc_meta_engagement_rate' | 'soc_fb_impressions' | 'soc_fb_engagements' | 'soc_fb_engagement_rate' | 'soc_ig_impressions' | 'soc_ig_reach' | 'soc_ig_engagements' | 'soc_ig_engagement_rate' | 'soc_ig_profile_visits' | 'soc_ig_new_follows' | 'sys_produced' | 'hyros_attributed_revenue' | 'hyros_attributed_leads' | 'web_sessions' | 'web_active_users' | 'web_conversions' | 'web_revenue' | 'web_search_impressions' | 'web_search_clicks' | 'web_search_ctr' | 'web_search_position' | 'site_views' | 'site_visitors' | 'site_sessions' | 'site_conversions' | 'site_conversion_rate', required
    - `operator` 'above' | 'below' | 'no_change_for', required
    - `value` number, required
  - `link_target_kind` 'page' | 'site' | 'board' | 'data_table': For a link: what it opens — a site, a page, a board (or project), or a Data table. Required for that kind.
  - `link_target_id` string: For a link: the id of the thing it opens, read from a tool that listed it — a link to an id that does not resolve is a dead widget.
  - `note_body` string: For a note: what it says, in their words where you have them. Markdown renders. Required for that kind.
  - `site_id` string, uuid: For site_* numbers only: narrow it to one of their sites, by its id from `page.list` — never an invented id. Add page_path for one page of it.
  - `page_path` string: For site_* numbers only: narrow it to one page by its path, like /pricing — that path on every site unless site_id is also given.
  - `label` string: For a button or a switch: the words on it.
  - `press_tool` string: For a button that presses a tool: the tool's name, like 'briefing.regenerate'. Pressing it runs that tool behind the tool's own approval.
  - `press_automation_id` string, uuid: For a button that starts an automation: its id, read from a tool that listed it.
  - `switch_automation_id` string, uuid: For a switch that turns an automation on and off: its id, read from a tool that listed it.
  - `switch_data_table_id` string, uuid: For a switch on one yes/no cell: the Data table's id. Pass switch_row_id and switch_column with it.
  - `switch_row_id` string, uuid: For a switch on one yes/no cell: the row's id.
  - `switch_column` string: For a switch on one yes/no cell: the column's key.
  - `workspace` string: Which workspace to run in — its slug. Omit to use your default. With more than one reachable workspace and no default, the call is refused and the choices are listed.

## Response `200`

The call was admitted and dispatched. `ok` says whether the tool succeeded — a refusal the tool itself produced is still a 200, exactly as it is a successful JSON-RPC result over MCP.

- union
  - object: The tool ran and answered.
    - `ok` true, required
    - `widget_id` string, required
    - `board_id` string, nullable, required
    - `answer_note` string, required
    - `workspace` string, required: The slug of the workspace this call ran in.
    - `working_in` object, required: Which workspace this call ran in, and how that was decided. Present on every workspace-scoped result.
      - `label` string, required: The chip words — e.g. "working in: Acme Main".
      - `note` string, required: Why this workspace — e.g. "your default", "this call only".
      - `workspace` string, required: The resolved workspace's slug.
      - `source` 'call-override' | 'key-pin' | 'stored-default' | 'sole-reachable', required: Which rung of the R7 precedence resolved the workspace.
  - ToolRefusal: A call that was admitted and dispatched, and did not succeed.
    - `ok` false, required
    - `code` string, required: The typed reason — e.g. `workspace_ambiguous`, `invalid_arguments`, or the tool's own failure code.
    - `message` string, required: A sentence a person can act on.
    - `workspaces` string[]: On a workspace refusal: the slugs this key may pass as `workspace`.
    - `issues` object[]: On `invalid_arguments`: which fields failed, and why.
      - `path` string, required
      - `message` string, required
    - `path` string: On an authoring refusal: where in your call's input it points, e.g. `steps.<key>.params.<field>`.
    - `fix` string: On an authoring refusal: the change to your call that would pass.
    - `instruction` string: On an authoring refusal: what to do next with it.

## Other responses

- `400`: The request could not be used: the body is present but not a JSON object (`tenant_api.malformed_body`); it is a top-level array carrying more messages than this endpoint accepts (`tenant_mcp.batch_too_large`); or the `Idempotency-Key` header is malformed (`tenant_api.idempotency_key_invalid`), disagrees with the body's `idempotency_key` (`tenant_api.idempotency_key_conflict`), or was sent to an action that cannot replay a retry (`tenant_api.idempotency_unsupported`). Nothing ran.
- `401`: No bearer credential, or one that did not resolve. Deliberately ONE undifferentiated answer for absent / unknown / revoked / expired, so the endpoint is never an oracle over the key space.
- `403`: The credential is good but the surface is not open to it — the platform or account API switch is off, or the holder lacks the `api.access` capability. The `code` says which.
- `404`: No such tool. The body names no tool and is identical for every unrecognised name.
- `429`: Over this key's per-minute cap. Carries `Retry-After` and `error.retry_after_seconds`.

## Changes

- **2026-10-20** `f9b4aeb83edc`: 1 warning
  - removed the request property `title`

[Operation history](https://skmtc.dev/goosybear/apis/goosy-bear-api/changes/api/v1/tools/widget.update/post.md)

---

[API](https://skmtc.dev/goosybear/apis/goosy-bear-api.md) · [All operations](https://skmtc.dev/goosybear/apis/goosy-bear-api/llms.txt) · [OpenAPI document](https://skmtc.dev/goosybear/apis/goosy-bear-api/revisions/f9b4aeb83edc?raw)
