{"openapi":"3.1.0","info":{"title":"GET /api/v1/public/dashboards/{token}","version":"1.0.0","description":"Public dashboard viewer (unauthenticated)"},"servers":[{"url":"https://api.ondayzero.com","description":"Production"}],"paths":{"/api/v1/public/dashboards/{token}":{"get":{"tags":["dashboards-public"],"summary":"Public dashboard viewer (unauthenticated)","description":"Resolve a share token to a read-only, redacted dashboard. Returns 404 for unknown, expired, revoked, or deleted targets — the four are indistinguishable on purpose. Rate-limited per IP and cached for 60 seconds; the payload reflects each widget's stored snapshot, so it is exactly as fresh as the dashboard itself.","operationId":"fetch_public_dashboard_api_v1_public_dashboards__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope_PublicDashboardResponse_"}}}},"404":{"description":"Not Found - Resource does not exist","content":{"application/json":{"example":{"detail":"Resource not found"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"detail":"Rate limit exceeded"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"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"},"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."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"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)."},"PublicDashboardResponse":{"properties":{"dashboard":{"$ref":"#/components/schemas/PublicDashboardSpec"},"cached":{"type":"boolean","title":"Cached","default":false}},"additionalProperties":false,"type":"object","required":["dashboard"],"title":"PublicDashboardResponse","description":"Public, redacted DashboardSpec served via the unauth share route."},"PublicDashboardSpec":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"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"},"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"}},"type":"object","required":["id","title","created_at"],"title":"PublicDashboardSpec","description":"Redacted dashboard payload for unauthenticated share viewers.\n\nOmits assignment/creator identifiers and widget sources so tenant data\nand internal tool parameters never leak to the public route.\n``extra=\"ignore\"`` tolerates stale Redis cache entries."},"SuccessEnvelope_PublicDashboardResponse_":{"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/PublicDashboardResponse"},{"type":"null"}]}},"additionalProperties":true,"type":"object","title":"SuccessEnvelope[PublicDashboardResponse]"},"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."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"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."}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Token","description":"API token authentication. Format: `Bearer dz_...`"}}},"security":[{"BearerAuth":[]}]}