Datto SaaS Protection API Patterns — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Datto SaaS Protection API Patterns (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The MCP server (datto-saas-protection-mcp) and SDK (@wyre-technology/node-datto-saas-protection) are in development.
Datto SaaS Protection (rebranded Backupify, now also folded together with Spanning under the same product family) provides cloud-to-cloud backup for Microsoft 365 and Google Workspace tenants.
Reference: <https://saasprotection.datto.com/help/M365/Content/Other_Administrative_Tasks/using-rest-api-saas-protection.htm>
Base URLs (per region):
https://api.datto.com/api/v1 (US)
https://api.eu.datto.com/api/v1 (EU)The MCP server takes a region credential field (us or eu); never hard-code.
API key issued from the SaaS Protection partner portal:
Authorization: Bearer <api_key>Keys are long-lived; rotate from the same UI.
Partner
└── Client (a customer organization)
└── Domain (M365 tenant / Google domain)
└── Seat (mailbox / OneDrive / SharePoint site / Google user)
└── Backup runs / restore points| Domain | Endpoint | Notes |
|---|---|---|
| Clients | GET /clients | All client orgs |
| Domains | GET /clients/{clientId}/domains | M365/Google tenants |
| Seats | GET /clients/{clientId}/domains/{domainId}/seats | Active + archived |
| Single seat | GET /seats/{seatId} | |
| Backup status | GET /seats/{seatId}/backups | Most recent backup runs |
| Activity log | GET /clients/{clientId}/activity | Org-level events |
| Restore (request) | POST /seats/{seatId}/restores | Queue restore |
| Restore status | GET /restores/{restoreId} | |
| License usage | GET /clients/{clientId}/usage | Seat counts |
Cursor-based:
GET /clients?limit=100
→ { items: [...], nextCursor: "abc123" }
GET /clients?limit=100&cursor=abc123Default limit is 50, max is 250. Stop when nextCursor is missing or null.
Restores are async. The flow:
1. POST /seats/{seatId}/restores → { restoreId, status: "queued" }
2. Poll GET /restores/{restoreId} → status transitions queued → running → completed | failed
3. On completed, fetch the result location (URL or destination metadata)Typical restore wall-clock: minutes to hours depending on data volume. Poll at 30-second intervals; do not poll faster.
60 req/min per API key under default policy. HTTP 429 includes Retry-After. Aggregate operations (list all seats across all clients) should use Promise.all with concurrency capped at 4.
| HTTP | Meaning | Action |
|---|---|---|
| 200 | OK | |
| 400 | Bad request | Validate inputs |
| 401 | Bad / expired key | Rotate key |
| 403 | Key lacks scope on this client | Verify scope |
| 404 | Unknown seatId / clientId / domainId | |
| 409 | Restore already queued for this seat | Surface; offer to cancel-and-retry |
| 429 | Rate limited | Back off per Retry-After |
| 500-503 | Transient | Exponential backoff |
?includeArchived=true to see seats whose source mailboxes were deleted but whose backups are retained.spanning-mcp plugin) — don't conflate keys.When the build-out lands, expect domain skills for: clients, seats, restores, activity-logs, license-usage.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.