Sensitivity tiers
17 scopes split by sensitivity. Reads are cheap. Writes are gated. Two are high-sensitivity.
17 scopes split by sensitivity. Reads are cheap. Writes are gated by family scope. Two scopes (gems_adjust, screentime_approve) are high-sensitivity: explicit consent + rate limits.
Three tiers of sensitivity
Most calls are family-scoped writes, safe with consent at handshake. Two carry an extra confirmation step and per-partner rate caps.
| Tier | Scopes | Notes |
|---|---|---|
| Reads | family:read, task:read, skill:read, canvas:read, reward:read, routine:read, screentime:read, gems:read, project:read | Cheap. No mutation. Family-scoped. |
| Writes | task:write, skill:write, canvas:write, reward:write, routine:write, project:write | Mutate. Family-scoped. Idempotency varies by tool. |
| High-sensitivity | gems:adjust, screentime:approve | Explicit consent at OAuth. Rate-limited. Audited. |
High-sensitivity
Two scopes carry an extra confirmation step at OAuth time (the parent explicitly opts in by name, not just bulk-approve) and are rate-limited at runtime:
gems:adjust: adds or removes gems with an audit reason. ~5 adjustments/min sustained, 20 burst. Every call writes an audit row with the suppliedreason.screentime:approve: approves or denies a pending screen-time unlock request. ~10/min sustained, 30 burst.action: "approve"deducts the gem cost;"deny"does not.
Both surface a typed error if you trip rate limits. Surface it to the parent, do not silently retry.
Family scoping
Every read or write is bound to the OAuth token's family. Cross-family ids return PERMISSION_DENIED, indistinguishable from "not found" so partners cannot enumerate. You cannot accidentally read or mutate another family.
Idempotency: three mechanisms
It is not a single header across the surface. Three mechanisms cover three families of writes:
Idempotency-Keyheader: high-sensitivity writes (gems_adjust,screentime_review_request). Same key, same envelope, no double-spend. Hash your payload.- dryRun +
specHashround-trip:canvas_create,canvas_update,skill_write,skill_update. The hash binds the preview to the commit; mismatched commits reject. Also doubles as your validation step. - skill_id to entity backstop: entities created under a skill record the linkage. A re-run can detect "already produced" without mutating. Today this covers canvases at authoring time; routine / task / keepsake linkage on invoke is roadmap, so for those your agent should check state (
task_list,routine_list) before retrying.
Dry-run
canvas_create, canvas_update, skill_write, skill_update all support dryRun: true + the specHash round-trip. routine_describe exists as a dedicated preview tool. task_create does not have a dry-run (asymmetric; file an issue if it bites).
Composite safety
A skill is exactly as sensitive as the most sensitive artifact it touches and the most exposed tool it triggers. A morning brief that calls post_message only is low-stakes; the same skill the moment it also calls gems_adjust inherits that scope's full sensitivity profile. Review skills at the bound of their reach, not the bound of their prompt text.
hands_referenced on a skill documents intent; dangerous tools self-gate at the handler regardless of which skill called them. Your agent should respect the declaration anyway; the platform will enforce it later.
Errors point at the fix
Tool responses and BAD_INPUT errors carry directional nextStep copy: literally the next call to make. Read it, follow it. Do not guess.
What this means for your agent
Build your authoring + delivery pipeline as dry-run, preview, confirm, commit. Use the Idempotency-Key on every commit. Surface rate-limit and consent errors to the parent. The Sprout interaction discipline ("checkpoint, do not barrel") is baked into both the docs and the error copy.
Try it
# Verify your scopes before any work
mcp_whoami
{
"user_id": "...",
"app_id": "...",
"sprout_scopes": [...]
}
# High-sensitivity call with Idempotency-Key
gems_adjust({
childId: "019c5229-...",
delta: 5,
reason: "for helping Shane with breakfast"
}, headers={"Idempotency-Key": "sha256(...)"})
# If rate-limited:
{ "error": "RATE_LIMITED", "retryAfter": 12 }Further reading
- OAuth & scopes (quickstart)
- Tip gems
- Triage screen-time