Trey Hoffman (Data Architect at JR286) posted this in the Domo User Group this week:
"I don't see it in the list of available tiles in Workflows, but is there a way to trigger an App Studio Report Builder distribution from workflows?"
This is the kind of question I love — not because the answer is complicated, but because it reveals a gap in how most people think about what Domo can do. Search the tile picker, come up empty, assume it's not possible. Move on.
It is possible. And once you have a mental model for how Domo's layers fit together, the solution is obvious before you write a single line of code.
Domo Has Three Layers
Before touching any code, it helps to understand what you're navigating:
┌──────────────────────────────────────────────┐
│ Tile layer — Workflows, App Studio actions │ ← No-code. Opinionated. Incomplete.
├──────────────────────────────────────────────┤
│ Function layer — Code Engine, BeastMode │ ← Low-code bridge.
├──────────────────────────────────────────────┤
│ API layer — REST endpoints │ ← Full programmatic access.
└──────────────────────────────────────────────┘
The rule: if it exists in the UI, it exists in the API. Workflows tiles are wrappers around API calls — Domo just hasn't built tiles for everything.
Trey's observation ("I don't see it in the list of available tiles") is a tile-layer observation. The right follow-up isn't "can Domo do this?" — it's "what API endpoint maps to this action?"
Step 1: Name the Resource
Every Domo API is organized around a resource noun. Once you name it, the endpoint pattern is predictable:
/api/{domain}/v{n}/{resource}/{id}/{action}
Report Builder distributions are managed by report schedules. Search the Domo API reference for "scheduled reports" and you land immediately on:
POST /api/content/v1/reportschedules/{scheduleId}/sendnow
POST /api/content/v1/reportschedules/views/{viewId}/sendNow
That's the research step. Naming the resource shrinks the search space from "the entire Domo API" to two candidates.
Postman is my go-to tool for this. Domo's internal API calls are visible in browser DevTools — you can record them into a Postman collection and build a living reference for every endpoint you've ever touched. I covered that workflow here: How to Document APIs Using Postman.
Step 2: Understand the Two IDs
Report Builder uses views as the unit of distribution. One report can have multiple views, each with its own filters, recipients, and schedule.
| ID type | What it targets | Where to find it |
|---|---|---|
scheduleId |
One specific schedule on one view | Schedule tab URL in Report Builder |
viewId |
All active schedules on a view | Report Builder URL, or the list endpoint |
To list all schedules on your instance:
GET /api/content/v1/reportschedules
The viewId is also in the Report Builder URL:
https://mycompany.domo.com/appstore/apps/{appId}/report-builder/views/{viewId}
Step 3: Build the Bridge
The gap isn't missing capability — it's that the tile layer doesn't expose it. The bridge is Code Engine.
Write a Python function that calls the sendnow endpoint, deploy it to a Code Engine package, then add it as a Custom Function tile in your Workflow. You're not adding a feature to Domo — you're wrapping an API call in a shape Workflows knows how to invoke.
This pattern covers probably 80% of "Domo can't do X" problems:
Trigger condition (Workflow) → Custom Function tile (Code Engine) → Domo API endpoint
The Implementation
Option A: Trigger by Schedule ID
import requests
def trigger_report_by_schedule(
domo_instance: str,
schedule_id: str,
recipient_emails: list[str],
access_token: str,
) -> dict:
url = f"https://{domo_instance}.domo.com/api/content/v1/reportschedules/{schedule_id}/sendnow"
recipients = [{"type": "EMAIL", "value": email} for email in recipient_emails]
response = requests.post(
url,
json=recipients,
headers={
"X-DOMO-Developer-Token": access_token,
"Content-Type": "application/json",
},
)
return {
"success": response.status_code == 202,
"status": response.status_code,
"error": response.text if response.status_code != 202 else None,
}Option B: Trigger by View ID
def trigger_report_by_view(
domo_instance: str,
view_id: str,
recipient_emails: list[str],
access_token: str,
) -> dict:
url = f"https://{domo_instance}.domo.com/api/content/v1/reportschedules/views/{view_id}/sendNow"
recipients = [{"type": "EMAIL", "value": email} for email in recipient_emails]
response = requests.post(
url,
json=recipients,
headers={
"X-DOMO-Developer-Token": access_token,
"Content-Type": "application/json",
},
)
return {
"success": response.status_code == 202,
"status": response.status_code,
"error": response.text if response.status_code != 202 else None,
}Both return 202 Accepted when queued. Recipients support three types:
{"type": "EMAIL", "value": "user@example.com"}
{"type": "USER_ID", "value": "1234567"}
{"type": "GROUP_ID", "value": "987"}Wiring into Workflows
- Deploy the function to a Code Engine package
- Add a Custom Function tile in your Workflow
- Select your package + function
- Map the parameters:
domo_instance,schedule_idorview_id,recipient_emails,access_token - Generate a Developer Access Token under Admin → Security → Access Tokens with
contentscope — store it as a Domo credential
Heads up on auth: Code Engine has real quirks around how credentials are passed and why async Python silently fails. If this is your first function, watch Handling Domo Authentication in Code Engine or read the companion article before you start — it'll save you an hour.
Going Further with crew-dcs
For a one-off Code Engine function, the above is all you need. But if you're building automation pipelines in Python — runbooks, CI jobs, alert handlers — you'll rediscover these endpoints over and over across different clients and projects.
That's the problem crew-dcs was built to solve. It's an open-source Python SDK that wraps the Domo API. As of this week it includes the Scheduled Reports API.
Install via the DataCrew package index:
pip install crew-dcs --index-url https://datacrew.space/packages/Trigger a report:
import asyncio
from crew_dcs.auth import DomoTokenAuth
from crew_dcs.classes.DomoScheduledReport import DomoScheduledReport, ReportRecipient
async def main():
auth = DomoTokenAuth(
domo_instance="mycompany",
domo_access_token="your-developer-token",
)
# By schedule ID
report = DomoScheduledReport(schedule_id="abc123", auth=auth)
await report.send_now([
ReportRecipient.email("manager@example.com"),
ReportRecipient.email("analyst@example.com"),
])
# By view ID — triggers all active schedules on that view
report = DomoScheduledReport(view_id="def456", auth=auth)
await report.send_now([ReportRecipient.email("recipient@example.com")])
asyncio.run(main())List all schedules:
schedules = await DomoScheduledReport.get_all(auth=auth)
for s in schedules:
print(s.schedule_id, s.name, s.view_id, s.enabled)The class handles sendnow vs sendNow automatically based on which ID you set.
The Bigger Point
The three-layer model — tile, function, API — applies to almost every "Domo can't do X" problem I run into with clients. The answer is almost never "Domo can't do it." The answer is usually "there's no tile for it, but the API call exists."
The research and implementation on this one took about ten minutes. Not because it's simple — it is — but because knowing where to look makes the solution obvious.
Domo's real strength is that governance, PDP, access control, scheduling, and dataset lineage are all in one place. You don't need five separate tools to get from a data source to a distributed report. That infrastructure is already there.
What most teams are missing is someone who can work across all three layers — who knows the UI well enough to spot the gaps, and has the data engineering background to reach into the API layer when the tile layer falls short. That's what I do with DataCrew. If you're hitting walls like this, let's talk.
Relevant Docs
- Domo Scheduled Reports API — Send Report Now — trigger by scheduleId
- Domo Scheduled Reports API — Send Report Now by View ID — trigger by viewId
- App Studio Report Builder scheduling — how views and schedules work inside Report Builder
- Workflows Custom Functions — wiring Code Engine into Workflows
- crew-dcs on GitHub — the open-source Domo SDK used in the Python examples above
DataCrew Videos
- How to Document APIs Using Postman — record Domo's internal API calls into a living reference collection
- Handling Domo Authentication in Code Engine — credential patterns and async gotchas on your first Code Engine function (companion article)


