API reference/Outreach/Dashboards
Save a dashboard
Builds a dashboard, or updates it: the same key again changes what you send and keeps what you leave out, and blocks replaces all blocks. Its tab shows in mySDR right away. New blocks are computed first: a filter on one of your own signals or a colleague the workspace does not know is a 400, and nothing is saved. The reply says what each block shows now (results): check it matches what the user asked, and give the user dashboard_url. At most 10 dashboards and 12 blocks each.
Blocks, top to bottom; consecutive metrics sit side by side, consecutive charts two by two, a table and a note take the full width.
metric: one number. Withsourceleadsorcompanies, how many rows matchfilters; withactivity, how many lines of the log in the period.divide_by(the same filters, for activity withoutperiod) makes it a percentage, e.g. a reply rate.compare: true(activity only) adds the period of the same length just before.chart: on leads or companies, rows pergroup_by(lifecycle, status, lead_status, outreach, stage, owner, source, country, company, signal; companies hasindustryinstead ofcompany; alsofield.<key>for one of your own fields), the biggest first, at most 12 bars plus Other. On activity,group_bydayorweekdraws the log over the period (at most 5 series plus Other),type,campaignorby(the key that logged it) draws bars.table: rows with thecolumnsyou pick (up to 8; leads: name, title, company, location, lifecycle, outreach, lead_status, next_action, stage, owner, signals, last_signal_at, added_at, email, note; companies: name, domain, industry, country, employees, leads, lifecycle, lead_status, next_action, stage, owner, signals, technologies, funding, last_seen_at; both alsofield.<key>),sort(bya column,orderascordesc) andlimit(1 to 100, default 25). On activity the columns are those of the Activity tab, newest first.note:text, plain, up to 2000 characters, line breaks kept.linkedin_limits: the LinkedIn limits of the user who looks, live: invites today and in the last 7 days, new sequences today (each used of its limit), a pause and the warm-up. Nosourceorfilters;titleis optional. GET /outreach/linkedin and a start of a sequence suggest a dashboard with it indashboard_offer.
Filters. On leads and companies, the names of GET /tables/leads in JSON: q, lifecycle, status, lead_status, due, campaign_id, outreach, source, stage, owner, do_not_contact, agent_signal (lists as arrays) and field_filters ([{"key": "hubspot", "equals": "open_deal"}], with equals, gte, lte or exists). On activity: types (email_sent, email_replied, email_bounced, meeting_booked, not_interested, out_of_office, unsubscribed, referral, note, status_changed, stage_changed, owner_changed, signal_hit, linkedin_invite_sent, linkedin_invite_accepted, linkedin_invite_declined, linkedin_invite_withdrawn, linkedin_message_sent, linkedin_post_liked, linkedin_like_skipped, linkedin_message_skipped, linkedin_replied, linkedin_stopped), actions (your own actions, e.g. call; with types, a line matches either), campaign_id and period (today, this_week, this_month, last_7_days, last_30_days, last_90_days, all; Brussels calendar days, default last_30_days).
A dashboard the user asks your agent to build: it shows as its own tab on the Dashboard in mySDR (the tabs are only dashboards; mySDR builds the first one, Overview with the key mysdr_overview, from the first data) and refreshes by itself. You describe it; mySDR draws it from the live Leads and Companies tables and the activity log, with the same filters as GET /tables/leads, so a block counts the same rows as the table. No HTML or code: a dashboard is a list of blocks of five types. Changing or removing it is something the user asks your agent, not a button in mySDR. These routes are the tools save_dashboard, list_dashboards, get_dashboard and delete_dashboard of the assistant server.
Authentication#
Send Authorization: Bearer gtk_…. Create the key under API keys in the menu. Every gtk_ key can call these routes, also while the Signals API is closed. A missing, invalid or revoked key is 401.
Body parameters#
keystring· required — Your own name for the dashboard: lowercase letters, digits and_, starting with a letter, up to 40 characters (Pipeline Q4becomespipeline_q4). Use the same key every time.namestring— The label of its tab, up to 40 characters. Required the first time.descriptionstring | null— One line under the name, up to 200 characters.nullclears it.positioninteger | null— Where its tab goes among the dashboards: 1 to 99, 1 = first; null = last (in the order they came).blocksobject[]— 1 to 12 blocks, top to bottom. Required the first time; replaces all blocks.blocks[].typestring· required — What the block is. One of:metric,chart,table,note,linkedin_limits.blocks[].titlestring— What it shows, above it, up to 80 characters. Required except for a note andlinkedin_limits.blocks[].sourcestring— The Leads or Companies table, or the activity log. Not for a note orlinkedin_limits. One of:leads,companies,activity.blocks[].filtersobject— Which rows or log lines count (see Filters).blocks[].divide_byobject— metric: a percentage of filters over these.blocks[].compareboolean— metric on activity: also the period just before.blocks[].group_bystring— chart: what the bars or the line group on. Required for a chart.blocks[].columnsstring[]— table on leads or companies: the columns, in order.blocks[].sortobject— table on leads or companies:by(a column) andorder.blocks[].limitinteger— table: how many rows it shows, 1 to 100.blocks[].textstring— note: the text. Required for a note.
Response#
objectstring· required — Alwaysdashboard. One of:dashboard.keystring· required — Your own name for it: lowercase letters, digits and_.namestring· required — The label of its tab.descriptionstring | null· required — The line under the name.positioninteger | null· required — Where its tab goes among the dashboards (1 = first); null = last.blocksobject[]· required — The blocks as saved.viastring | null· required — The name of the key that last saved it ("Claude").created_atstring· required — When it was created.updated_atstring· required — When it last changed.dashboard_urlstring· required — Its tab in mySDR.computed_atstring· required — When the blocks were computed.resultsobject[]· required — Per block, in order:index,type,title,source, andresultorerror. A metric givesvalue(null when a percentage has nothing to divide by),unit(countorpercent),previous,partandwhole; a chartkindbarswithgroups(key,label,value) andtotal, orkindserieswithinterval,seriesandbuckets(start,current,values); a tablecolumns,rows(id,href,cells) andtotal; a note itstext.capped= there is more than the dashboard reads at once (5,000 leads, or 5,000 lines of the log per source), so the number is at least this.error(type,message,param) says why a block shows nothing, e.g. a filter on a signal that is gone.createdboolean· required — True when this call created it; false when it updated one.
Credits#
Free. This call never uses credits.
Errors#
- 400
invalid_request— Nokey, a key that is not valid, nonameorblocksthe first time, a block that is not valid (paramnames it, e.g.blocks[2].group_by), a filter on something the workspace does not have, or an unknown field. - 409
invalid_state— The workspace has 10 dashboards already: delete one first. - 503
service_unavailable— Dashboards are not available in this environment yet. - 409
invalid_state— The key has no user behind it, so mySDR cannot record who did it. - 401
authentication_error— The Authorization header is missing, or the key is unknown or revoked. - 403
permission_error— API access is not part of your plan, or your account is suspended. - 429
rate_limit_error— More than 60 requests per minute, or more than 3 at the same time, on one key. Wait for the Retry-After header. - 503
service_unavailable— The API is temporarily unavailable. Retry later. - 500
internal_error— Something went wrong on our side.
Example
curl -X POST "https://mysdr.app/api/v1/dashboards" \
-H "Authorization: Bearer $MYSDR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"key": "pipeline_q4",
"name": "Pipeline Q4",
"description": "Where Q4 stands, every morning.",
"position": 1,
"blocks": [
{
"type": "metric",
"title": "Meetings booked",
"source": "leads",
"filters": {
"lifecycle": [
"meeting"
]
}
},
{
"type": "metric",
"title": "Reply rate, 30 days",
"source": "activity",
"filters": {
"types": [
"email_replied",
"linkedin_replied"
]
},
"divide_by": {
"types": [
"email_sent",
"linkedin_message_sent"
]
},
"compare": true
},
{
"type": "chart",
"title": "Leads per stage",
"source": "leads",
"filters": {
"lifecycle": [
"open"
]
},
"group_by": "stage"
},
{
"type": "chart",
"title": "Emails and replies per day",
"source": "activity",
"filters": {
"types": [
"email_sent",
"email_replied"
],
"period": "last_30_days"
},
"group_by": "day"
},
{
"type": "table",
"title": "Open deals in HubSpot",
"source": "leads",
"filters": {
"field_filters": [
{
"key": "hubspot",
"equals": "open_deal"
}
]
},
"columns": [
"name",
"company",
"lifecycle",
"next_action",
"field.hubspot"
],
"sort": {
"by": "next_action",
"order": "asc"
},
"limit": 25
}
]
}'{
"object": "dashboard",
"key": "pipeline_q4",
"name": "Pipeline Q4",
"description": "Where Q4 stands, every morning.",
"position": 1,
"blocks": [
{
"type": "metric",
"title": "Meetings booked",
"source": "leads",
"filters": {
"lifecycle": [
"meeting"
]
}
},
{
"type": "metric",
"title": "Reply rate, 30 days",
"source": "activity",
"filters": {
"types": [
"email_replied",
"linkedin_replied"
]
},
"divide_by": {
"types": [
"email_sent",
"linkedin_message_sent"
]
},
"compare": true
},
{
"type": "chart",
"title": "Leads per stage",
"source": "leads",
"filters": {
"lifecycle": [
"open"
]
},
"group_by": "stage"
},
{
"type": "chart",
"title": "Emails and replies per day",
"source": "activity",
"filters": {
"types": [
"email_sent",
"email_replied"
],
"period": "last_30_days"
},
"group_by": "day"
},
{
"type": "table",
"title": "Open deals in HubSpot",
"source": "leads",
"filters": {
"field_filters": [
{
"key": "hubspot",
"equals": "open_deal"
}
]
},
"columns": [
"name",
"company",
"lifecycle",
"next_action",
"field.hubspot"
],
"sort": {
"by": "next_action",
"order": "asc"
},
"limit": 25
}
],
"via": "Claude",
"created_at": "2026-10-27T08:00:00.000Z",
"updated_at": "2026-10-27T08:00:00.000Z",
"dashboard_url": "https://mysdr.app/en/gtm/views/pipeline_q4",
"computed_at": "2026-10-27T08:00:01.000Z",
"results": [
{
"index": 0,
"type": "metric",
"title": "Meetings booked",
"source": "leads",
"result": {
"type": "metric",
"value": 4,
"unit": "count",
"previous": null,
"part": null,
"whole": null,
"period": null,
"capped": false
},
"error": null
},
{
"index": 1,
"type": "metric",
"title": "Reply rate, 30 days",
"source": "activity",
"result": {
"type": "metric",
"value": 12.5,
"unit": "percent",
"previous": 9.1,
"part": 10,
"whole": 80,
"period": "last_30_days",
"capped": false
},
"error": null
}
],
"created": true
}