The Reporting API
Part of the MountDev Ops Workspace documentation. Updated October 29, 2026
MountDev Ops Workspace documentation | Updated October 29, 2026
The Reporting API lets your own systems read everything MountDev keeps about your websites: each website and its services, email delivery, uptime, backups, updates, security, speed and Core Web Vitals by day, each website's activity log, the issues MountDev found, your monthly reports, and your website events. Use it to build your own dashboards, send the numbers to your own tools, or keep your own copy. Your data is yours: it is never locked in, and nothing about your calls is tracked.
It is part of your workspace's REST API: the same address, the same keys, and the same Activity Log. Every address here is read only (GET), and its keys can read only. Changing what you pay for (adding a service or a website) is the Billing API's, with keys of its own.
MountDev's Three Services
Every website can have any of MountDev's three services, and the API names them the same way everywhere:
| Service | What the API shows for it |
|---|---|
hosting | Hosting, with managed email delivery for the website (email_delivery) |
maintenance | Maintenance: updates, backups, security, and uptime |
performance | Performance: speed history and Core Web Vitals |
Scopes
A key reads websites only with the scopes you give it when you make or edit it on Settings, Connections, API Keys (see API Keys). A key starts with none, and every scope is read only:
| Scope | What it lets the key read |
|---|---|
sites:read | The websites, and each one's Hosting, Maintenance, and Performance |
stats:read | Hosting's email delivery, Maintenance's uptime, backups, updates, and security, and Performance's speed and Core Web Vitals, by day |
reports:read | The monthly reports |
events:read | The website events (a website down or back up, a backup failed, a report ready) |
export:read | Everything above for every website, a few websites a page |
issues:read | The issues found on your websites: fixed by us, needing your team, or handled by MountDev |
activity:read | Each website's activity log: what changed and who changed it |
A call without its scope is refused with 403 and the scope it needs, such as { "error": "This key does not have the stats:read scope. ...", "code": "forbidden" }.
The Addresses
Each address follows your workspace's API address, such as https://mountdev.app/account/api/v1/.
| Method and address | Scope | What it answers |
|---|---|---|
GET sites | sites:read | Your websites, a page at a time (limit up to 100, 50 at first; cursor) |
GET sites/<id> | sites:read | One website, with its last 30 days in short |
GET sites/<id>/hosting/email | stats:read | Email delivery by day for the website's own domain |
GET sites/<id>/maintenance/uptime | stats:read | Uptime by day |
GET sites/<id>/maintenance/backups | stats:read | Backups by day |
GET sites/<id>/maintenance/updates | stats:read | Updates applied by day, with their names |
GET sites/<id>/maintenance/security | stats:read | Security scans and the firewall by day |
GET sites/<id>/performance | stats:read | Speed scores and Core Web Vitals by day (strategy: mobile, the default, or desktop) |
GET sites/<id>/activity | activity:read | The website's activity log, newest first (limit up to 500, 100 at first) |
GET issues | issues:read | Issues on every website (bucket, status, limit up to 100, 50 at first; cursor) |
GET sites/<id>/issues | issues:read | Issues on one website, with the same filters |
GET reports | reports:read | Your monthly reports, newest first (limit up to 36, 12 at first; cursor) |
GET reports/<YYYY-MM> | reports:read | One month's report as data (site: just one website's part) |
GET events | events:read | Your website events, oldest first, past after (the id of the last one you have) |
GET export | export:read | Everything for every website, five websites a page (cursor) |
A website's <id> is the id GET sites gives it. A website of another company answers 404, the same as one that does not exist.
Days, Ranges, and Pages
- The daily addresses take
fromandto(days such as2026-10-01, Pacific time). Without them you get the last 30 days to today. One call covers up to 400 days, and pages hold up to 400 days (90 at first,limit). - Every list comes as
{ "items": [...], "next_cursor": "..." }. Whennext_cursoris not null, send it back ascursor, with the same other values, for the next page. A cursor is opaque: use it as it is. - Everything comes from the daily readings MountDev already keeps, so the answers are quick and the same each time you ask. A day's numbers can still grow until the day ends.
What Each Answer Holds
A website (GET sites and sites/<id>):
{ "id": "1042", "host": "example.com", "client": "Example Bakery", "since": "2026-03-02T17:00:00.000Z", "status": "ok", "uptime": { "state": "up", "down_since": null, "checked_at": "2026-10-29T18:15:00.000Z" }, "services": { "hosting": { "status": "ready", "note": "", "email_delivery": "ready" }, "maintenance": { "status": "ready", "note": "" }, "performance": null }, "attention": [] }
statusis one word for the website as a whole:ok,attention(it is down or a check is failing),waiting(a step is waiting on you),setting_up,paused, orno_services.- Each service is
nullwhen the website does not have it, or itsstatus(ready,setting_up,waiting,paused, orattention) with anotein plain words. Hosting'semail_deliveryisready,waiting,setting_up, ornot_set_up. uptime.stateisup,down(two checks in a row did not answer), orunknown(not checked yet).- GET
sites/<id>addslast_30_days:maintenance(uptime_pct,last_backup,last_scan,updates) andperformance(the newestmobileanddesktopreading:score,vitals,ttfb_ms).
Email delivery by day: day, sent, delivered, bounced, soft_bounced, complained, held, declined, and suppressed, every kind of mail from the website's domain added together, with the domain they are for.
Activity: at, kind (such as plugin, theme, core, user, or content), action (such as updated or activated), object, name, from and to (such as versions), user (the WordPress user, when there is one), and by_us (true when MountDev made the change).
Uptime by day: day, checks (one every 15 minutes), fails, uptime_pct, minutes_down, and last_fail_at.
Backups by day: day, status (such as succeeded or failed), bytes, files, and tables.
Updates by day: day, plugins, themes, core (how many of each were updated), and names.
Security by day: day, scan (such as clean), firewall (on or off), and blocked (requests the firewall stopped).
Performance by day: day, optimized (each day can have a reading with and without optimization), scores (performance, accessibility, best_practices, seo, each 0 to 100), vitals (lcp_ms, fcp_ms, cls, tbt_ms), ttfb_ms, and bytes.
An issue (GET issues and sites/<id>/issues):
{ "id": "2", "site": { "id": "1042", "host": "example.com" }, "bucket": "needs_agency", "category": "license", "level": "action", "title": "Renew the slider plugin's license", "why": "Without a license the plugin gets no security updates.", "evidence_summary": "The plugin says its license expired on October 20.", "steps": [ "Renew the license in your account with the plugin's maker.", "Paste the new key in the plugin's settings." ], "status": "open", "opened_at": "2026-10-21T10:00:00.000Z", "updated_at": "2026-10-21T10:00:00.000Z", "fixed_at": null, "resolved_at": null }
bucketis who has it:fixed_by_us(MountDev fixed it),needs_agency(your team has a step to take, listed insteps), orhandled_by_mountdev(MountDev is on it). Filter withbucket=.statusisopen,in_progress,fixed, orresolved. Filter withstatus=.categoryis one ofhosting,uptime,updates,backups,security,performance,email,dns,forms,links,license,plugins,content,access,billing, orother.levelisurgent,action, orfyi.whysays why it matters andevidence_summarywhat was seen, each in plain words.steps(up to 12) is empty unless the bucket isneeds_agency.- Issues come newest first. Only the issues your team may see are listed.
A report (GET reports/<YYYY-MM>): month, label, sent_at, account, summary, tiles, attention, and sites, each website's sections as the report shows them. It is white-labeled: it carries the words and numbers only, never our name, a link to the report page, the people it was sent to, or a DNS record, so you can show it your own way.
An event (GET events): id, type, at, site (id and host, or null for the whole account), and data. The answer also has last (send it as after next time) and more (true when there are more right now).
The export (GET export): format (mountdev-export-v1), exported_at, from and to (the last 400 days), sites, each website as above with its hosting (email), maintenance (uptime, backups, updates, security), and performance (mobile, desktop) by day, its newest 500 activity entries, and next_cursor. The first page also has account, reports, and sites_total. Page through it with cursor until next_cursor is null to have everything. This is everything MountDev keeps about your websites; your workspace's own records and files are in Export Everything, which stays with owners.
Website Events
The same events can be sent to your systems the moment MountDev sees them, as signed webhooks: choose them under Websites when you add a webhook (see Webhooks).
| Event | When |
|---|---|
site.down | A website did not answer two checks in a row, 15 minutes apart |
site.up | A website answers again after being down (data.minutes: about how long it was down) |
backup.failed | A website's backup did not finish (data.day, data.kind) |
report.ready | The month's report was sent and can be read with GET reports/<YYYY-MM> (data.month) |
issue.needs_agency | An issue needs your team, with its steps (data.issue) |
issue.fixed | An issue was fixed (data.issue) |
issue.resolved | An issue was resolved (data.issue) |
notice.urgent | An urgent notice, only for a website down past your threshold, a hacked website, a failed payment, or a security hole in a plugin we are holding (data.notice: id, kind (site_down, site_hacked, payment_failed, or plugin_vulnerable), title, summary, level, and, when there is one, issue_id, since, and minutes) |
notice.digest | The daily digest, one for each group (data.digest: id, date (the day in your company's own time zone, from Business Hours), group (Action Needed or For Your Information), counts, and up to 50 tasks, each with title, category, bucket (needs_agency), status (open), site, and issue_id). In counts, needs_agency is the tasks open for your team, fixed_by_us what MountDev fixed in the last 24 hours, and handled_by_mountdev what MountDev is handling now |
Limits
Each key may make 120 calls a minute; an export page counts as 10. Every answer says where the key is in its X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers, and a call over the limit is refused with 429, "code": "rate_limited", and Retry-After (seconds until the next minute).
Privacy
- A key reads only its own company's websites. A website of another company answers exactly as one that does not exist.
- Answers never carry a DNS record's name or value, a password, a key, a secret, or the private link to a report.
- Nothing about your calls is tracked. Each call is written in your own Activity Log with the key's name.
For Example
GET /account/api/v1/sites/1042/maintenance/uptime?from=2026-10-01&to=2026-10-28 Authorization: Bearer mdo_...
{ "result": { "from": "2026-10-01", "to": "2026-10-28", "items": [ { "day": "2026-10-01", "checks": 96, "fails": 0, "uptime_pct": 100, "minutes_down": 0, "last_fail_at": null } ], "next_cursor": null } }
Coming Next Planned
These are planned and not part of the Reporting API yet:
- Hosting's storage and bandwidth by day.
- A report as a PDF with your own brand.
- A shorter address for the API of its own.
- Choosing which websites a key may read.
