{"openapi":"3.1.0","info":{"title":"POST /api/v1/dashboards/{dashboard_id}/refresh","version":"1.0.0","description":"Refresh a dashboard's widgets now"},"servers":[{"url":"https://api.ondayzero.com","description":"Production"}],"paths":{"/api/v1/dashboards/{dashboard_id}/refresh":{"post":{"tags":["dashboards"],"summary":"Refresh a dashboard's widgets now","description":"Re-run every refreshable widget source immediately, as the caller — the same code path as a scheduled refresh, with the caller's live permissions applied per tool. Editors only, because the refreshed snapshots are what everyone sharing the dashboard sees. Widgets that fail keep their previous snapshot and report `last_refresh_error`.","operationId":"refresh_dashboard","parameters":[{"name":"dashboard_id","in":"path","required":true,"schema":{"type":"string","title":"Dashboard Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-business-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Business-Id"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope_RefreshDashboardResponse_"}}}},"400":{"description":"Bad Request - Invalid input","content":{"application/json":{"example":{"error":"validation_error","message":"Validation failed: due_date: Input should be a valid date","errors":{"due_date":"Input should be a valid date"},"code":"GEN_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Missing or expired token, an MCP OAuth token used against the REST API, or no access to this business","content":{"application/json":{"example":{"error":"unauthorized","message":"Authentication required","code":"AUTH_001","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Insufficient permissions","content":{"application/json":{"example":{"error":"forbidden","message":"You don't have permission to access this resource","code":"AUTH_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - Resource does not exist","content":{"application/json":{"example":{"error":"not_found","message":"Resource not found","code":"NOT_FOUND_001","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"validation_error","message":"Validation failed: amount: Input should be a valid integer","errors":{"amount":"Input should be a valid integer"},"code":"GEN_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"}}}}},"x-fastapi-operation-id":"refresh_dashboard_api_v1_dashboards__dashboard_id__refresh_post"}}},"components":{"schemas":{"ChartWidget":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","maxLength":200,"title":"Title"},"layout":{"$ref":"#/components/schemas/WidgetLayout"},"source":{"anyOf":[{"$ref":"#/components/schemas/WidgetSource"},{"type":"null"}]},"snapshot":{"$ref":"#/components/schemas/WidgetSnapshot"},"last_refresh_error":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Last Refresh Error"},"last_refreshed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Refreshed At"},"widget_type":{"type":"string","const":"chart","title":"Widget Type","default":"chart"}},"additionalProperties":false,"type":"object","required":["id","title","snapshot"],"title":"ChartWidget"},"DashboardSpec":{"properties":{"version":{"type":"integer","const":1,"title":"Version","default":1},"id":{"type":"string","title":"Id"},"title":{"type":"string","maxLength":200,"title":"Title"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description"},"visibility":{"type":"string","enum":["private","assigned","business"],"title":"Visibility","default":"private"},"widgets":{"items":{"oneOf":[{"$ref":"#/components/schemas/ChartWidget"},{"$ref":"#/components/schemas/TableWidget"},{"$ref":"#/components/schemas/MetricWidget"},{"$ref":"#/components/schemas/GaugeWidget"},{"$ref":"#/components/schemas/TimelineWidget"},{"$ref":"#/components/schemas/TextWidget"}],"discriminator":{"propertyName":"widget_type","mapping":{"chart":"#/components/schemas/ChartWidget","gauge":"#/components/schemas/GaugeWidget","metric":"#/components/schemas/MetricWidget","table":"#/components/schemas/TableWidget","text":"#/components/schemas/TextWidget","timeline":"#/components/schemas/TimelineWidget"}}},"type":"array","maxItems":24,"title":"Widgets"},"assigned_user_ids":{"items":{"type":"string"},"type":"array","title":"Assigned User Ids"},"created_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By User Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_refreshed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Refreshed At"},"can_edit":{"type":"boolean","title":"Can Edit","description":"Whether the caller may edit or delete this dashboard: its creator, a business owner/admin, or a superuser. Mirrors the server-side check so the UI can show the right actions.","default":false}},"additionalProperties":false,"type":"object","required":["id","title","created_at"],"title":"DashboardSpec","description":"The full dashboard payload — exactly what the FE renderer consumes."},"DerivedSpec":{"properties":{"key":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,59}$","title":"Key"},"expr":{"type":"string","maxLength":500,"minLength":1,"title":"Expr"},"label":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Label"}},"additionalProperties":false,"type":"object","required":["key","expr"],"title":"DerivedSpec","description":"A column computed from other columns, e.g. ``billed - collected``.\n\nExpressions are arithmetic over measure/derived keys only — no attribute\naccess, no calls beyond ``abs``/``min``/``max``/``round`` — so a spec that\nparses can never reach anything but the row it is computing."},"ExtraWidgetSource":{"properties":{"tool_name":{"type":"string","maxLength":120,"minLength":1,"title":"Tool Name"},"params":{"additionalProperties":true,"type":"object","title":"Params"}},"additionalProperties":false,"type":"object","required":["tool_name"],"title":"ExtraWidgetSource","description":"A second (third, …) READ tool feeding the same widget.\n\nOnly meaningful alongside a ``transform``: the extra source's rows are\ngrouped on the same keys and outer-joined onto the primary source's, which\nis how \"contract value\" from one tool lands in the column next to \"billed\"\nfrom another."},"FilterSpec":{"properties":{"field":{"type":"string","maxLength":120,"minLength":1,"title":"Field"},"op":{"type":"string","enum":["eq","ne","gt","gte","lt","lte","in","not_in","contains","starts_with","is_empty","not_empty"],"title":"Op","default":"eq"},"value":{"title":"Value"}},"additionalProperties":false,"type":"object","required":["field"],"title":"FilterSpec","description":"One predicate over a source row, evaluated before aggregation."},"GaugeWidget":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","maxLength":200,"title":"Title"},"layout":{"$ref":"#/components/schemas/WidgetLayout"},"source":{"anyOf":[{"$ref":"#/components/schemas/WidgetSource"},{"type":"null"}]},"snapshot":{"$ref":"#/components/schemas/WidgetSnapshot"},"last_refresh_error":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Last Refresh Error"},"last_refreshed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Refreshed At"},"widget_type":{"type":"string","const":"gauge","title":"Widget Type","default":"gauge"}},"additionalProperties":false,"type":"object","required":["id","title","snapshot"],"title":"GaugeWidget","description":"A linear meter showing a single value against a min/max range."},"GroupKey":{"properties":{"key":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,59}$","title":"Key"},"fields":{"items":{"type":"string"},"type":"array","maxItems":8,"minItems":1,"title":"Fields"},"label":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Label"},"missing_label":{"type":"string","maxLength":60,"title":"Missing Label","default":"Unassigned"}},"additionalProperties":false,"type":"object","required":["key","fields"],"title":"GroupKey","description":"One dimension column — the \"by customer\" in \"revenue by customer\".\n\n``fields`` is a candidate list, not a single name, because the same\ndimension is spelled differently by different tools (``customer_name``\nhere, ``customer`` there). The first field present on a row wins, so one\ngroup key can join rows coming from two tools."},"MeasureSpec":{"properties":{"key":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,59}$","title":"Key"},"fields":{"items":{"type":"string"},"type":"array","maxItems":8,"title":"Fields"},"agg":{"type":"string","enum":["sum","avg","min","max","count","count_distinct","first","last"],"title":"Agg","default":"sum"},"label":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Label"},"source":{"type":"integer","maximum":5.0,"minimum":0.0,"title":"Source","default":0},"filters":{"items":{"$ref":"#/components/schemas/FilterSpec"},"type":"array","maxItems":10,"title":"Filters"}},"additionalProperties":false,"type":"object","required":["key"],"title":"MeasureSpec","description":"One aggregated column.\n\n``filters`` is what makes a measure a SUMIF: two measures over the same\nsource, one filtered to sent invoices and one to drafts, become two\ncolumns the user can read side by side."},"MetricWidget":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","maxLength":200,"title":"Title"},"layout":{"$ref":"#/components/schemas/WidgetLayout"},"source":{"anyOf":[{"$ref":"#/components/schemas/WidgetSource"},{"type":"null"}]},"snapshot":{"$ref":"#/components/schemas/WidgetSnapshot"},"last_refresh_error":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Last Refresh Error"},"last_refreshed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Refreshed At"},"widget_type":{"type":"string","const":"metric","title":"Widget Type","default":"metric"}},"additionalProperties":false,"type":"object","required":["id","title","snapshot"],"title":"MetricWidget","description":"A single headline number (e.g. cash on hand, total AR)."},"RefreshDashboardResponse":{"properties":{"refreshed":{"type":"integer","title":"Refreshed","default":0},"failed":{"type":"integer","title":"Failed","default":0},"dashboard":{"$ref":"#/components/schemas/DashboardSpec"}},"additionalProperties":false,"type":"object","required":["dashboard"],"title":"RefreshDashboardResponse","description":"Returned from the refresh endpoints (Phase B)."},"SuccessEnvelope_RefreshDashboardResponse_":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"},"data":{"anyOf":[{"$ref":"#/components/schemas/RefreshDashboardResponse"},{"type":"null"}]}},"additionalProperties":true,"type":"object","title":"SuccessEnvelope[RefreshDashboardResponse]"},"TableWidget":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","maxLength":200,"title":"Title"},"layout":{"$ref":"#/components/schemas/WidgetLayout"},"source":{"anyOf":[{"$ref":"#/components/schemas/WidgetSource"},{"type":"null"}]},"snapshot":{"$ref":"#/components/schemas/WidgetSnapshot"},"last_refresh_error":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Last Refresh Error"},"last_refreshed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Refreshed At"},"widget_type":{"type":"string","const":"table","title":"Widget Type","default":"table"}},"additionalProperties":false,"type":"object","required":["id","title","snapshot"],"title":"TableWidget"},"TextWidget":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","maxLength":200,"title":"Title"},"layout":{"$ref":"#/components/schemas/WidgetLayout"},"source":{"anyOf":[{"$ref":"#/components/schemas/WidgetSource"},{"type":"null"}]},"snapshot":{"anyOf":[{"$ref":"#/components/schemas/WidgetSnapshot"},{"type":"null"}]},"last_refresh_error":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Last Refresh Error"},"last_refreshed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Refreshed At"},"widget_type":{"type":"string","const":"text","title":"Widget Type","default":"text"},"body":{"type":"string","maxLength":8000,"title":"Body","default":""}},"additionalProperties":false,"type":"object","required":["id","title"],"title":"TextWidget","description":"Free-form markdown note; has no data snapshot requirement."},"TimelineWidget":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","maxLength":200,"title":"Title"},"layout":{"$ref":"#/components/schemas/WidgetLayout"},"source":{"anyOf":[{"$ref":"#/components/schemas/WidgetSource"},{"type":"null"}]},"snapshot":{"$ref":"#/components/schemas/WidgetSnapshot"},"last_refresh_error":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Last Refresh Error"},"last_refreshed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Refreshed At"},"widget_type":{"type":"string","const":"timeline","title":"Widget Type","default":"timeline"}},"additionalProperties":false,"type":"object","required":["id","title","snapshot"],"title":"TimelineWidget","description":"Chronological event list with status indicators."},"WidgetLayout":{"properties":{"x":{"type":"integer","maximum":11.0,"minimum":0.0,"title":"X","default":0},"y":{"type":"integer","maximum":999.0,"minimum":0.0,"title":"Y","default":0},"w":{"type":"integer","maximum":12.0,"minimum":1.0,"title":"W","default":6},"h":{"type":"integer","maximum":12.0,"minimum":1.0,"title":"H","default":4}},"additionalProperties":false,"type":"object","title":"WidgetLayout","description":"Position on a 12-column grid."},"WidgetSnapshot":{"properties":{"data":{"additionalProperties":true,"type":"object","title":"Data"},"ui_hint":{"type":"string","maxLength":60,"minLength":1,"title":"Ui Hint"},"captured_at":{"type":"string","format":"date-time","title":"Captured At"}},"type":"object","required":["data","ui_hint","captured_at"],"title":"WidgetSnapshot","description":"The captured chat payload the widget renders from.\n\n``data`` is intentionally open-shaped (chat payloads vary per tool), so no\n``extra=\"forbid\"`` here — only the size guard applies."},"WidgetSource":{"properties":{"tool_name":{"type":"string","maxLength":120,"minLength":1,"title":"Tool Name"},"params":{"additionalProperties":true,"type":"object","title":"Params"},"refreshable":{"type":"boolean","title":"Refreshable","default":true},"transform":{"anyOf":[{"$ref":"#/components/schemas/WidgetTransform"},{"type":"null"}]},"extra_sources":{"items":{"$ref":"#/components/schemas/ExtraWidgetSource"},"type":"array","maxItems":4,"title":"Extra Sources"},"scope":{"type":"string","enum":["business","firm"],"title":"Scope","default":"business"},"firm_business_ids":{"items":{"type":"string"},"type":"array","maxItems":500,"title":"Firm Business Ids"}},"additionalProperties":false,"type":"object","required":["tool_name"],"title":"WidgetSource","description":"The READ tool invocation that can re-produce this widget's data.\n\n``transform`` (optional) reshapes what the tool(s) returned into a\ngrouped, multi-measure breakdown before it is snapshotted. It lives on the\n*source*, not the snapshot, so a scheduled refresh re-derives exactly the\nsame shape from fresh rows.\n\n``scope`` records the breadth the widget was *created* at. A\n``\"business\"`` widget resolves against the dashboard's own business; a\n``\"firm\"`` widget aggregates across ``firm_business_ids`` — the client\nbusinesses the creator could reach at capture time.\n\n``firm_business_ids`` is a security input, not a display field: at\nview/refresh time it is intersected against the *current* viewer's firm\naccess so a widget can never keep surfacing a client the viewer has since\nlost access to. It is deliberately stored on the widget (rather than\nre-derived) so a shrinking firm roster narrows the widget instead of\nsilently re-broadening it."},"WidgetTransform":{"properties":{"group_by":{"items":{"$ref":"#/components/schemas/GroupKey"},"type":"array","maxItems":3,"title":"Group By"},"measures":{"items":{"$ref":"#/components/schemas/MeasureSpec"},"type":"array","maxItems":12,"minItems":1,"title":"Measures"},"derived":{"items":{"$ref":"#/components/schemas/DerivedSpec"},"type":"array","maxItems":8,"title":"Derived"},"filters":{"items":{"$ref":"#/components/schemas/FilterSpec"},"type":"array","maxItems":10,"title":"Filters"},"sort_by":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}],"title":"Sort By"},"sort_dir":{"type":"string","enum":["asc","desc"],"title":"Sort Dir","default":"desc"},"limit":{"type":"integer","maximum":200.0,"minimum":1.0,"title":"Limit","default":200},"summarize":{"type":"boolean","title":"Summarize","default":true}},"additionalProperties":false,"type":"object","required":["measures"],"title":"WidgetTransform","description":"Pivot a widget's source rows into a grouped, multi-measure table."},"ErrorResponse":{"title":"ErrorResponse","type":"object","description":"Envelope returned by every non-2xx response. Branch on `code` (stable) rather than `message` (human-readable, may change).","required":["error","message","code"],"properties":{"error":{"type":"string","description":"Error category: `validation_error`, `unauthorized`, `forbidden`, `not_found`, `conflict`, `rate_limited`, `server_error`, or `service_unavailable`."},"message":{"type":"string","description":"Human-readable explanation, safe to show to end users."},"code":{"type":"string","description":"Stable machine-readable code in `CATEGORY_NNN` form (e.g. `NOT_FOUND_006`, `AUTH_010`, `GEN_002`)."},"request_id":{"type":"string","description":"Correlation id for support requests. Echoes the `x-request-id` request header when one was supplied."},"errors":{"type":"object","additionalProperties":{"type":"string"},"description":"Field-level validation messages keyed by field name. Present on 400/422 validation failures only."}}}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Token","description":"API token authentication. Format: `Bearer dz_...`"}}},"security":[{"BearerAuth":[]}]}