{"name":"sproutpad","mcp":"https://api.sproutpad.ai/mcp","docs":"https://api.sproutpad.ai/agents.md","formatVersion":"2","contractDigest":"3b7b36db05e94ac85b5656b6d0ed9b9eb9ecfed27fbe095f31deb229933dc44b","coverage":{"mcpOperationCount":47,"mcpDescriptorCount":47,"operationCount":57,"operationIds":["apply_registrant_contact","apply_workflow","attach_address","attach_domain","can_i","check_domain_connection","connect_domain","create_inbox","create_project","detach_address","detach_domain","estimate","estimate_observation","export_dns_zone","export_project","get_audit_log","get_budget_mandate","get_costs","get_drift_observation","get_registrant_contact","get_resource_inventory_observation","get_started","get_status","get_status_observation","get_task","get_task_observation","get_workflow_plan","help","launch_service","list_dns_records","list_domains","list_drift","list_emails","list_resources","list_webhooks","move_service","poll_billing","poll_signup","prepare_workflow","quote","read_email","remove_service","request_budget_increase","scan_drift","search_domains","send_email","set_project_purpose","set_registrant_contact","setup_billing","teardown","upload_assets","upsert_dns_record","verify_architecture","verify_architecture_observation","verify_sandbox_proof","verify_sandbox_proof_observation","whoami"],"supplementalRestOperationIds":["apply_workflow","estimate_observation","get_drift_observation","get_resource_inventory_observation","get_status_observation","get_task_observation","get_workflow_plan","prepare_workflow","verify_architecture_observation","verify_sandbox_proof_observation"],"completeSchemaOperationIds":["apply_registrant_contact","apply_workflow","attach_address","attach_domain","can_i","check_domain_connection","connect_domain","create_inbox","create_project","detach_address","detach_domain","estimate","estimate_observation","export_dns_zone","export_project","get_audit_log","get_budget_mandate","get_costs","get_drift_observation","get_registrant_contact","get_resource_inventory_observation","get_started","get_status","get_status_observation","get_task","get_task_observation","get_workflow_plan","help","launch_service","list_dns_records","list_domains","list_drift","list_emails","list_resources","list_webhooks","move_service","poll_billing","poll_signup","prepare_workflow","quote","read_email","remove_service","request_budget_increase","scan_drift","search_domains","send_email","set_project_purpose","set_registrant_contact","setup_billing","teardown","upload_assets","upsert_dns_record","verify_architecture","verify_architecture_observation","verify_sandbox_proof","verify_sandbox_proof_observation","whoami"],"complete":true},"tools":[{"operationId":"apply_registrant_contact","title":"Apply registrant contact to a name.com domain","description":"Open a human-gated task to apply registrant identity, potentially triggering a 60-day lock.","useWhen":["A signed-in human has already entered an organization registrant contact in the dashboard, and you need to request its application to one owned canonical name.com domain with a stable replay key."],"doNotUseWhen":["You need to submit, read, or echo legal identity PII; apply a contact to a non-name.com domain; programmatically approve the change; inspect raw registrar/provider state; or use code mode, generated SDK, or CLI execution."],"examples":[{"kind":"positive","title":"Admit a registrant-contact approval workflow","expectedOutcome":"Returns one bounded working task and states that a signed-in human approval is required before any registrar mutation."},{"kind":"negative","title":"Do not send registrant PII through an agent credential","expectedOutcome":"The request accepts only domain and justification. Legal name, address, phone, email, contact version internals, and a direct approval action are unavailable to the agent."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.apply_registrant_contact.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.apply_registrant_contact.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_domain","auth:not_your_project","input:invalid_request","input:idempotency_key_required","input:missing_justification","input:no_registrant_contact","idempotency:key_reuse","billing:delinquent","billing:disputed","org:frozen","domain:wrong_registrar","config:not_supported","policy:denied","provider:intent_collision","provider:reconciliation_required","rate:limited","internal:tool_failure","internal:apply_registrant_contact_output_invalid"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false},"behavior":{"effect":"write","openWorld":true,"destructive":true,"destructiveException":"The call only admits a reversible-before-approval task, but it can cause a later human-approved registrant identity change and transfer-lock consequence; the descriptor therefore marks the governed write destructive.","idempotency":"client_key","taskMode":"returns_handle","execution":{"mode":"direct_only","reason":"Applying an already dashboard-entered registrant identity admits a durable human-approval workflow that can change WHOIS and trigger a transfer lock. It remains a direct MCP/REST write, never a programmatic read, code-mode operation, generated SDK call, or CLI command."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"apply_registrant_contact","discoveryGroup":"domains","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"description":"[domains] Open a human-gated task to apply registrant identity, potentially triggering a 60-day lock.\n\nUse when: A signed-in human has already entered an organization registrant contact in the dashboard, and you need to request its application to one owned canonical name.com domain with a stable replay key.\nDo not use when: You need to submit, read, or echo legal identity PII; apply a contact to a non-name.com domain; programmatically approve the change; inspect raw registrar/provider state; or use code mode, generated SDK, or CLI execution.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_domain, auth:not_your_project, input:invalid_request, input:idempotency_key_required, input:missing_justification, input:no_registrant_contact, idempotency:key_reuse, billing:delinquent, billing:disputed, org:frozen, domain:wrong_registrar, config:not_supported, policy:denied, provider:intent_collision, provider:reconciliation_required, rate:limited, internal:tool_failure, internal:apply_registrant_contact_output_invalid"},"rest":{"method":"post","path":"/v1/domains/{domain}/apply-registrant","successEncoding":"json_envelope_data"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"attach_address","title":"Attach an email alias to an inbox","description":"Route one explicit canonical alias on an owned mail-onboarded domain to one existing project inbox under an observed route version, a recorded justification, and required replay identity.","useWhen":["An existing canonical project inbox must receive one new alias on an already owned and mail-onboarded domain, and you have the route's current version plus a deliberate recorded reason."],"doNotUseWhen":["You need a new mailbox, domain-email onboarding, address normalization, an automatic inverse, raw provider or ledger details, or generated SDK, CLI, or code-mode execution."],"examples":[{"kind":"positive","title":"Route support mail to an existing inbox","expectedOutcome":"Returns only the bounded alias route, zero-cost route receipt, and terminal stop; it never returns a provider rule id, generic mutation envelope, or replay body marker."},{"kind":"negative","title":"Do not infer mail-routing intent","expectedOutcome":"inboxAddress, aliasLocal, aliasDomain, expectedVersion, and a meaningful justification are required exactly. Unknown fields, case-normalization aliases, generic nextActions, and replay body fields are rejected before side effects."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.attach_address.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.attach_address.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","input:invalid_request","input:idempotency_required","idempotency:key_reuse","input:unknown_mailbox","email:canonical_address","email:address_taken","email:domain_not_ready","domain:not_owned","domain:adoption_in_progress","domain:adoption_required","domain:owned_elsewhere","domain:reverification_required","resource:version_conflict","billing:delinquent","billing:disputed","org:frozen","policy:denied","config:not_supported","provider:routing_unsupported","provider:reconciliation_required","provider:previous_attempt_failed","provider:unavailable","rate:limited","internal:tool_failure","internal:attach_address_output_invalid"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false},"behavior":{"effect":"write","openWorld":true,"destructive":true,"destructiveException":"The operation does not delete a mailbox, but it changes externally delivered mail and its provider route; it is therefore governed as destructive.","idempotency":"client_key","taskMode":"none","execution":{"mode":"direct_only","reason":"Attaching an alias changes externally delivered mail through a policy-gated, versioned provider-intent mutation with a required replay identity. It stays on direct MCP/REST; it is not a programmatic read, code-mode operation, generated SDK call, or CLI command."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"attach_address","discoveryGroup":"mail","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"description":"[mail] Route one explicit canonical alias on an owned mail-onboarded domain to one existing project inbox under an observed route version, a recorded justification, and required replay identity.\n\nUse when: An existing canonical project inbox must receive one new alias on an already owned and mail-onboarded domain, and you have the route's current version plus a deliberate recorded reason.\nDo not use when: You need a new mailbox, domain-email onboarding, address normalization, an automatic inverse, raw provider or ledger details, or generated SDK, CLI, or code-mode execution.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, input:invalid_request, input:idempotency_required, idempotency:key_reuse, input:unknown_mailbox, email:canonical_address, email:address_taken, email:domain_not_ready, domain:not_owned, domain:adoption_in_progress, domain:adoption_required, domain:owned_elsewhere, domain:reverification_required, resource:version_conflict, billing:delinquent, billing:disputed, org:frozen, policy:denied, config:not_supported, provider:routing_unsupported, provider:reconciliation_required, provider:previous_attempt_failed, provider:unavailable, rate:limited, internal:tool_failure, internal:attach_address_output_invalid"},"rest":{"method":"post","path":"/v1/projects/{id}/addresses/attach","successEncoding":"json_envelope_data"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"attach_domain","title":"Attach an owned domain to a service","description":"Route one owned canonical domain to one named running service with an explicit binding version, recorded justification, and replay identity.","useWhen":["An owned canonical domain must route to one already-running named service, and you have its current binding version plus a deliberate recorded reason."],"doNotUseWhen":["You need a default web service, a new deployment, domain registration or connection, an unversioned DNS change, raw provider/ledger state, a generic rollback, or generated SDK, CLI, or code-mode execution."],"examples":[{"kind":"positive","title":"Attach an owned production domain","expectedOutcome":"Returns only the bounded attachment fact, zero-cost domain receipt, irreversible automatic-undo posture, and terminal result; it never returns provider references or replay metadata."},{"kind":"negative","title":"Do not infer a service or binding version","expectedOutcome":"service and expectedVersion are required exactly. The direct contract rejects defaults, aliases, unknown fields, and replay body markers before side effects."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.attach_domain.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.attach_domain.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","input:invalid_request","input:idempotency_required","idempotency:key_reuse","input:no_service","domain:not_owned","domain:adoption_in_progress","domain:adoption_required","domain:owned_elsewhere","domain:reverification_required","domain:ownership_changed","resource:version_conflict","billing:delinquent","billing:disputed","org:frozen","policy:denied","provider:reconciliation_required","provider:unavailable","rate:limited","internal:tool_failure","internal:attach_domain_output_invalid"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false},"behavior":{"effect":"write","openWorld":true,"destructive":true,"destructiveException":"The operation does not delete the domain, but it changes externally served DNS and TLS routing and can trigger a public hostname cutover; it is therefore governed as destructive.","idempotency":"client_key","taskMode":"none","execution":{"mode":"direct_only","reason":"Attaching a domain changes public DNS and TLS through a policy-gated, versioned provider-intent mutation with a required replay identity. It stays on direct MCP/REST; it is not a programmatic read, code-mode operation, generated SDK call, or CLI command."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"attach_domain","discoveryGroup":"domains","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"description":"[domains] Route one owned canonical domain to one named running service with an explicit binding version, recorded justification, and replay identity.\n\nUse when: An owned canonical domain must route to one already-running named service, and you have its current binding version plus a deliberate recorded reason.\nDo not use when: You need a default web service, a new deployment, domain registration or connection, an unversioned DNS change, raw provider/ledger state, a generic rollback, or generated SDK, CLI, or code-mode execution.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, input:invalid_request, input:idempotency_required, idempotency:key_reuse, input:no_service, domain:not_owned, domain:adoption_in_progress, domain:adoption_required, domain:owned_elsewhere, domain:reverification_required, domain:ownership_changed, resource:version_conflict, billing:delinquent, billing:disputed, org:frozen, policy:denied, provider:reconciliation_required, provider:unavailable, rate:limited, internal:tool_failure, internal:attach_domain_output_invalid"},"rest":{"method":"post","path":"/v1/projects/{id}/domains/attach","successEncoding":"json_envelope_data"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"can_i","title":"Check policy before acting","description":"Probe current project policy without minting a verdict, reserving budget, or writing the ledger. For domain launch/register actions it uses a live domain-cost check, not a full launch quote or execution authorization.","useWhen":["You need a current project-bound policy probe before attempting a governed action, without creating a reservation or ledger event."],"doNotUseWhen":["You need a reusable programmatic read, a budget reservation, an executable quote, or any provider mutation. For launch_service, this probes only current domain-cost policy; use quote for full compute pricing and a reservation."],"examples":[{"kind":"positive","title":"Preflight a scratch launch","expectedOutcome":"Returns the current domain-cost policy probe without reserving budget, minting a verdict, or writing the ledger."},{"kind":"negative","title":"Do not treat a preflight as a reservation","expectedOutcome":"Use the governed quote path only when the caller is ready to obtain a short-lived budget reservation."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.can_i.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.can_i.output.v1"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","input:invalid_action","input:invalid_domain","input:missing_domain","input:invalid_project_id","input:invalid_request","rate:limited","input:unknown_project","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false,"minimumTrustRung":1},"behavior":{"effect":"read","openWorld":true,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"Domain preflights can synchronously call a live registrar quote, so this governed direct boundary is not eligible for code mode, generated safe CLI invocation, or generic programmatic reads."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"can_i","discoveryGroup":"governance","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[governance] Probe current project policy without minting a verdict, reserving budget, or writing the ledger. For domain launch/register actions it uses a live domain-cost check, not a full launch quote or execution authorization.\n\nUse when: You need a current project-bound policy probe before attempting a governed action, without creating a reservation or ledger event.\nDo not use when: You need a reusable programmatic read, a budget reservation, an executable quote, or any provider mutation. For launch_service, this probes only current domain-cost policy; use quote for full compute pricing and a reservation.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, input:invalid_action, input:invalid_domain, input:missing_domain, input:invalid_project_id, input:invalid_request, rate:limited, input:unknown_project, internal:tool_failure"},"rest":{"method":"get","path":"/v1/projects/{id}/can-i","successEncoding":"json_envelope_data"}}},{"operationId":"check_domain_connection","title":"Check connected-domain verification","description":"Check public DNS proof and activate the connection when ownership requirements are satisfied.","useWhen":["A human has configured or restored the exact bounded TXT and nameserver proof for an owned connected-domain request and needs one governed verification transition."],"doNotUseWhen":["You need a read-only DNS lookup, arbitrary resolver output, a code-mode/SDK/CLI call, a connection outside the caller organization, or a retry without a fresh stable mutation key."],"examples":[{"kind":"positive","title":"Verify one owned domain connection","expectedOutcome":"Runs the governed public-DNS proof check once, returns a bounded closed status/proof projection, and either continues through quote, re-check, or a new challenge."},{"kind":"negative","title":"Do not treat DNS verification as a safe read","expectedOutcome":"Stop: a check increments verification generation before network I/O and can write authority/ledger state or notify a human, so it remains a direct mutation."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.check_domain_connection.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.check_domain_connection.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","auth:rung_required","input:invalid_body","input:method_not_allowed","input:unknown_connection","input:idempotency_key_required","idempotency:key_reuse","org:frozen","org:closing","org:closed","billing:delinquent","billing:disputed","policy:denied","policy:budget_cap","plan:max_projects","plan:org_budget_ceiling","rate:limited","internal:tool_failure","internal:domain_connection_output_invalid"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false,"minimumTrustRung":1},"behavior":{"effect":"write","openWorld":true,"destructive":false,"idempotency":"client_key","taskMode":"none","execution":{"mode":"direct_only","reason":"Checking a domain connection performs public DNS I/O and can advance verification generation, mutate authority state, append a ledger fact, or notify a human. It is a direct MCP/REST mutation with a stable replay identity, never a read, code-mode operation, generated SDK call, or CLI command."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"check_domain_connection","discoveryGroup":"domains","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[domains] Check public DNS proof and activate the connection when ownership requirements are satisfied.\n\nUse when: A human has configured or restored the exact bounded TXT and nameserver proof for an owned connected-domain request and needs one governed verification transition.\nDo not use when: You need a read-only DNS lookup, arbitrary resolver output, a code-mode/SDK/CLI call, a connection outside the caller organization, or a retry without a fresh stable mutation key.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, auth:rung_required, input:invalid_body, input:method_not_allowed, input:unknown_connection, input:idempotency_key_required, idempotency:key_reuse, org:frozen, org:closing, org:closed, billing:delinquent, billing:disputed, policy:denied, policy:budget_cap, plan:max_projects, plan:org_budget_ceiling, rate:limited, internal:tool_failure, internal:domain_connection_output_invalid"},"rest":{"method":"post","path":"/v1/domain-connections/{id}/check","successEncoding":"json_envelope_data"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"connect_domain","title":"Connect an existing domain","description":"Begin the TXT and nameserver proof flow for a domain the human already owns.","useWhen":["A human already owns a canonical DNS domain and has asked for a governed managed-zone connection with explicit TXT ownership proof and nameserver delegation."],"doNotUseWhen":["You need a free-form DNS read, arbitrary provider credentials, a code-mode/SDK/CLI call, a domain you do not own, or an unchecked provider-side connection."],"examples":[{"kind":"positive","title":"Start a human-owned domain proof","expectedOutcome":"Creates one pending managed-zone connection, returns its bounded TXT/nameserver proof, and requires a fresh idempotent verification check after the human configures DNS."},{"kind":"negative","title":"Do not classify a connection as a read","expectedOutcome":"Stop: starting a connection can create provider infrastructure and durable authority state, so it must be invoked directly with a stable replay identity."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.connect_domain.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.connect_domain.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","auth:rung_required","input:invalid_body","input:invalid_domain","input:idempotency_key_required","idempotency:key_reuse","org:frozen","org:closing","org:closed","billing:delinquent","billing:disputed","domain:connected_elsewhere","domain:owned_elsewhere","policy:denied","policy:budget_cap","provider:reconciliation_required","plan:max_projects","plan:org_budget_ceiling","rate:limited","internal:tool_failure","internal:domain_connection_output_invalid"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false,"minimumTrustRung":2},"behavior":{"effect":"write","openWorld":true,"destructive":false,"idempotency":"client_key","taskMode":"none","execution":{"mode":"direct_only","reason":"Connecting a domain can create or adopt a managed DNS zone and persists a durable ownership challenge. It is a direct MCP/REST mutation with a stable replay identity, never a programmatic read, code-mode operation, generated SDK call, or CLI command."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"connect_domain","discoveryGroup":"domains","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[domains] Begin the TXT and nameserver proof flow for a domain the human already owns.\n\nUse when: A human already owns a canonical DNS domain and has asked for a governed managed-zone connection with explicit TXT ownership proof and nameserver delegation.\nDo not use when: You need a free-form DNS read, arbitrary provider credentials, a code-mode/SDK/CLI call, a domain you do not own, or an unchecked provider-side connection.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, auth:rung_required, input:invalid_body, input:invalid_domain, input:idempotency_key_required, idempotency:key_reuse, org:frozen, org:closing, org:closed, billing:delinquent, billing:disputed, domain:connected_elsewhere, domain:owned_elsewhere, policy:denied, policy:budget_cap, provider:reconciliation_required, plan:max_projects, plan:org_budget_ceiling, rate:limited, internal:tool_failure, internal:domain_connection_output_invalid"},"rest":{"method":"post","path":"/v1/domain-connections","successEncoding":"json_envelope_data"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"create_inbox","title":"Create an inbox on a domain","description":"Create mailboxes and publish and verify their MX, SPF, and DKIM records under a durable task.","useWhen":["You have a rung-2 provision-authorized project that owns a canonical domain and explicitly intend to admit managed mailbox/DNS setup with a stable replay key."],"doNotUseWhen":["You need a programmatic/code-mode call, permissive local-part casing, a default forwarding address, raw workflow/provider evidence, a replacement for get_task, or a way to cancel a task after durable admission."],"examples":[{"kind":"positive","title":"Admit canonical mailbox setup","expectedOutcome":"Returns one working setup_email owned task, a zero-cost task-admission receipt, irreversible-admission semantics, and a bounded non-secret setup summary."},{"kind":"negative","title":"Do not turn task admission into an email-provider read","expectedOutcome":"Forwarding targets, provider routing state, DNS/provider references, task raw results, and retry continuations are not returned; poll the owned task instead."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.create_inbox.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.create_inbox.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","trust:rung_too_low","input:invalid_body","input:invalid_addresses","input:invalid_address","input:missing_idempotency_key","idempotency:key_reuse","config:no_email_provider","domain:reverification_required","policy:domain_not_in_project","org:frozen","billing:delinquent","billing:disputed","policy:denied","plan:max_projects","plan:org_budget_ceiling","rate:limited","internal:create_inbox_output_invalid"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false,"minimumTrustRung":2},"behavior":{"effect":"write","openWorld":true,"destructive":false,"idempotency":"client_key","taskMode":"returns_handle","execution":{"mode":"direct_only","reason":"Creating an inbox admits durable email and DNS setup that is guarded by rung-2, owned-domain, billing/freeze, policy, provider-configuration, and replay controls. It is never a programmatic read or code-mode operation; the reviewed direct SDK/CLI projection requires a caller-owned replay key and makes one admission request."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"create_inbox","discoveryGroup":"mail","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[mail] Create mailboxes and publish and verify their MX, SPF, and DKIM records under a durable task.\n\nUse when: You have a rung-2 provision-authorized project that owns a canonical domain and explicitly intend to admit managed mailbox/DNS setup with a stable replay key.\nDo not use when: You need a programmatic/code-mode call, permissive local-part casing, a default forwarding address, raw workflow/provider evidence, a replacement for get_task, or a way to cancel a task after durable admission.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, trust:rung_too_low, input:invalid_body, input:invalid_addresses, input:invalid_address, input:missing_idempotency_key, idempotency:key_reuse, config:no_email_provider, domain:reverification_required, policy:domain_not_in_project, org:frozen, billing:delinquent, billing:disputed, policy:denied, plan:max_projects, plan:org_budget_ceiling, rate:limited, internal:create_inbox_output_invalid"},"rest":{"method":"post","path":"/v1/projects/{id}/email","successEncoding":"json_envelope_data"},"cli":{"path":["invoke","create_inbox"],"invocation":"mutation"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"create_project","title":"Create a project","description":"Create a governed project billing group inside the organization plan and budget ceiling.","useWhen":["You need a new lower-case governed project identifier with an explicit whole-cent monthly budget cap before quoting or launching resources."],"doNotUseWhen":["You need to set a purpose atomically with creation, rely on a default cap, use whitespace/case-normalized names, execute generated SDK/CLI/code-mode calls, or launch a service without a fresh quote."],"examples":[{"kind":"positive","title":"Create a project with an explicit cap","expectedOutcome":"Returns one project confirmation, a zero-cost project.created receipt, irreversible-creation semantics, and one typed quote continuation."},{"kind":"negative","title":"Do not combine creation and purpose mutation","expectedOutcome":"Create the project first, then use the separately versioned set_project_purpose operation if a purpose must be stored."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.create_project.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.create_project.output.v2"}},"errors":["input:invalid_request","input:idempotency_key_required","idempotency:key_reuse","auth:required","auth:insufficient_scope","trust:rung_too_low","plan:max_projects","plan:org_budget_ceiling","billing:delinquent","billing:disputed","org:frozen","policy:denied","rate:limited","internal:tool_failure","internal:project_create_output_invalid"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false,"minimumTrustRung":2},"behavior":{"effect":"write","openWorld":false,"destructive":false,"idempotency":"client_key","taskMode":"none","execution":{"mode":"direct_only","reason":"Creating a project changes durable plan-budget state and requires a transaction-bound replay checkpoint. Keep it on direct MCP/REST; it is not a programmatic read, code-mode operation, generated SDK call, or CLI command."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"create_project","discoveryGroup":"projects","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[projects] Create a governed project billing group inside the organization plan and budget ceiling.\n\nUse when: You need a new lower-case governed project identifier with an explicit whole-cent monthly budget cap before quoting or launching resources.\nDo not use when: You need to set a purpose atomically with creation, rely on a default cap, use whitespace/case-normalized names, execute generated SDK/CLI/code-mode calls, or launch a service without a fresh quote.\nErrors: input:invalid_request, input:idempotency_key_required, idempotency:key_reuse, auth:required, auth:insufficient_scope, trust:rung_too_low, plan:max_projects, plan:org_budget_ceiling, billing:delinquent, billing:disputed, org:frozen, policy:denied, rate:limited, internal:tool_failure, internal:project_create_output_invalid"},"rest":{"method":"post","path":"/v1/projects","successEncoding":"json_envelope_data"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"detach_address","title":"Detach an email alias from an inbox","description":"Stop routing one canonical alias address to one canonical project inbox under an observed alias version. The inbox remains intact.","useWhen":["A project-owned alias route should stop delivering to its canonical inbox and you have the exact addresses and observed alias version."],"doNotUseWhen":["You need to delete a mailbox, detach its canonical address, normalize address case or whitespace, omit the observed version, or use a programmatic/code-mode/SDK/CLI call."],"examples":[{"kind":"positive","title":"Detach one observed alias route","expectedOutcome":"Returns only the detached alias/inbox relationship, next alias version, zero-cost receipt, and terminal action."},{"kind":"negative","title":"Do not retry a version conflict unchanged","expectedOutcome":"A conflict returns only the current alias version. Read current inbox state and make a fresh explicit request if it still applies."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.detach_address.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.detach_address.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","input:invalid_request","input:idempotency_required","idempotency:key_reuse","email:canonical_address","email:unknown_alias","email:unknown_mailbox","resource:version_conflict","org:frozen","policy:denied","provider:reconciliation_required","provider:previous_attempt_failed","provider:routing_unsupported","provider:detach_failed","config:not_supported","rate:limited","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false},"behavior":{"effect":"delete","openWorld":true,"destructive":true,"destructiveException":"The operation removes one provider route while retaining the canonical inbox and mailbox.","idempotency":"client_key","taskMode":"none","execution":{"mode":"direct_only","reason":"Detaching an email alias mutates a versioned provider route and settles a governed ledger fact. It remains a direct MCP/REST mutation with required replay identity, never a programmatic read, code-mode operation, generated SDK call, or CLI command."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"detach_address","discoveryGroup":"mail","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"description":"[mail] Stop routing one canonical alias address to one canonical project inbox under an observed alias version. The inbox remains intact.\n\nUse when: A project-owned alias route should stop delivering to its canonical inbox and you have the exact addresses and observed alias version.\nDo not use when: You need to delete a mailbox, detach its canonical address, normalize address case or whitespace, omit the observed version, or use a programmatic/code-mode/SDK/CLI call.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, input:invalid_request, input:idempotency_required, idempotency:key_reuse, email:canonical_address, email:unknown_alias, email:unknown_mailbox, resource:version_conflict, org:frozen, policy:denied, provider:reconciliation_required, provider:previous_attempt_failed, provider:routing_unsupported, provider:detach_failed, config:not_supported, rate:limited, internal:tool_failure"},"rest":{"method":"post","path":"/v1/projects/{id}/addresses/detach","successEncoding":"json_envelope_data"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"detach_domain","title":"Detach a domain from its service","description":"Stop public routing for one owned canonical domain under an observed version while retaining the domain in organization custody.","useWhen":["An owned canonical domain should stop routing to its attached service, and you have the current domain attachment version."],"doNotUseWhen":["The domain must remain publicly routed, live mail still uses it, you need to delete registrar ownership, you do not have an observed version, or you need a programmatic/code-mode/SDK/CLI call."],"examples":[{"kind":"positive","title":"Detach one routed domain","expectedOutcome":"Returns the detached canonical domain, its next version, zero-cost bounded receipt count, and whether registered-domain renewal custody was parked."},{"kind":"negative","title":"Do not retry a version conflict unchanged","expectedOutcome":"A conflict returns only the current version. Read current state and make a new explicit request if detaching still applies."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.detach_domain.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.detach_domain.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","input:invalid_request","input:idempotency_required","idempotency:key_reuse","resource:version_conflict","domain:adoption_in_progress","domain:not_attached","domain:mail_in_use","domain:ownership_changed","org:frozen","policy:denied","provider:reconciliation_required","provider:previous_attempt_failed","provider:detach_failed","config:not_supported","rate:limited","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false},"behavior":{"effect":"delete","openWorld":true,"destructive":true,"destructiveException":"The operation removes public routing and can park registered-domain renewal custody, while retaining the domain in organization custody.","idempotency":"client_key","taskMode":"none","execution":{"mode":"direct_only","reason":"Detaching a domain changes public DNS/TLS routing and can park registered-domain renewal custody through a policy-gated, versioned provider-intent mutation. It remains direct MCP/REST with a required replay identity, never a programmatic read, code-mode operation, generated SDK call, or CLI command."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"conditional","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"detach_domain","discoveryGroup":"domains","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"description":"[domains] Stop public routing for one owned canonical domain under an observed version while retaining the domain in organization custody.\n\nUse when: An owned canonical domain should stop routing to its attached service, and you have the current domain attachment version.\nDo not use when: The domain must remain publicly routed, live mail still uses it, you need to delete registrar ownership, you do not have an observed version, or you need a programmatic/code-mode/SDK/CLI call.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, input:invalid_request, input:idempotency_required, idempotency:key_reuse, resource:version_conflict, domain:adoption_in_progress, domain:not_attached, domain:mail_in_use, domain:ownership_changed, org:frozen, policy:denied, provider:reconciliation_required, provider:previous_attempt_failed, provider:detach_failed, config:not_supported, rate:limited, internal:tool_failure"},"rest":{"method":"post","path":"/v1/projects/{id}/domains/detach","successEncoding":"json_envelope_data"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"estimate","title":"Estimate a launch without a hold","description":"Return terminal anonymous planning data with itemized prices and no policy evaluation, ledger writes, or budget reservation; use an authenticated project-bound quote only when a reservation is wanted.","useWhen":["You need an anonymous current-price estimate before policy evaluation or a budget hold."],"doNotUseWhen":["You need a project-bound policy verdict, a budget reservation, or to create any infrastructure."],"examples":[{"kind":"positive","title":"Estimate edge-hosting costs","expectedOutcome":"Returns itemized current prices with reserved=false and no project or budget hold."},{"kind":"negative","title":"Do not reserve through estimate","expectedOutcome":"Use quote only when the caller is ready for policy evaluation and a short-lived reservation."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.estimate.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.estimate.output.v1"}},"errors":["input:invalid_body","input:invalid_domain","input:invalid_managed_hostname","input:scratch_apex_reserved","input:invalid_target","input:invalid_mailbox_count","rate:limited"],"lifecycle":{"status":"active","since":"2026-07-21"},"authorization":{"auth":"anonymous","scopes":[],"humanOnly":false},"behavior":{"effect":"read","openWorld":true,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"Live estimate transports persist product-funnel telemetry, so this operation is not currently a zero-write programmatic read."}},"safety":{"sensitiveOutput":"none","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"estimate","discoveryGroup":"planning","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[planning] Return terminal anonymous planning data with itemized prices and no policy evaluation, ledger writes, or budget reservation; use an authenticated project-bound quote only when a reservation is wanted.\n\nUse when: You need an anonymous current-price estimate before policy evaluation or a budget hold.\nDo not use when: You need a project-bound policy verdict, a budget reservation, or to create any infrastructure.\nErrors: input:invalid_body, input:invalid_domain, input:invalid_managed_hostname, input:scratch_apex_reserved, input:invalid_target, input:invalid_mailbox_count, rate:limited"},"rest":{"method":"post","path":"/v1/estimate","successEncoding":"json_envelope_data"},"cli":{"path":["invoke","estimate"],"invocation":"read"}}},{"operationId":"export_dns_zone","title":"Export DNS zone file (BIND)","description":"Read an org-owned domain's live records and render a portable BIND zone file.","useWhen":["You need a direct, portable BIND export of one organization-owned DNS zone before a separately governed exit, backup, or provider migration step."],"doNotUseWhen":["You need a bounded or cacheable code-mode result, a zone outside the caller's organization, provider control authority, a DNS mutation, or an automatic route to another provider."],"examples":[{"kind":"positive","title":"Export one owned zone directly","expectedOutcome":"Returns the exact BIND text for the selected organization-owned domain without changing DNS, disclosing provider authority, or starting an exit workflow."},{"kind":"negative","title":"Do not use a BIND export as mutation authority","expectedOutcome":"Use the separately governed direct DNS mutation or exit operations when a change is intended; this read never applies the exported records anywhere."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.export_dns_zone.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.export_dns_zone.output.v1"}},"errors":["input:invalid_request","auth:required","auth:insufficient_scope","input:invalid_domain","auth:not_your_domain","org:frozen","config:not_supported","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false,"minimumTrustRung":1},"behavior":{"effect":"read","openWorld":true,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"The portable zone file is tenant-specific DNS material that can include arbitrary TXT content and has no server-enforced serialized-byte cap; use direct MCP/REST only and do not expose it to programmatic code mode, generated CLI execution, or response caching."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"export_dns_zone","discoveryGroup":"exit","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[exit] Read an org-owned domain's live records and render a portable BIND zone file.\n\nUse when: You need a direct, portable BIND export of one organization-owned DNS zone before a separately governed exit, backup, or provider migration step.\nDo not use when: You need a bounded or cacheable code-mode result, a zone outside the caller's organization, provider control authority, a DNS mutation, or an automatic route to another provider.\nErrors: input:invalid_request, auth:required, auth:insufficient_scope, input:invalid_domain, auth:not_your_domain, org:frozen, config:not_supported, internal:tool_failure"},"rest":{"method":"get","path":"/v1/domains/{domain}/zone-export","successEncoding":"raw_text"}}},{"operationId":"export_project","title":"Read bounded project exit metadata","description":"Read one explicitly selected, source-labelled page of current domain authority, retained asset metadata, or safe project audit receipts. BIND content stays in export_dns_zone; raw files, provider references, and raw ledger rows are unavailable.","useWhen":["You need a bounded, source-labelled inventory of the exit-relevant domain authority, retained-asset metadata, or safe project audit receipts for one owned project."],"doNotUseWhen":["You need BIND content, raw asset files, provider references, resource IDs, raw ledger rows, generated rebuild prose, an implicit full leave package, a mutation, or a CLI/code-mode response."],"examples":[{"kind":"positive","title":"Start a bounded project exit read","expectedOutcome":"Returns only the fixed source labels and page limits; then request one explicitly selected bounded inventory or receipt page."},{"kind":"negative","title":"Do not request raw artifacts through project export","expectedOutcome":"The strict contract has no artifact, README, provider reference, or idempotency input. DNS content remains the separate direct export_dns_zone operation."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.export_project.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.export_project.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:rung_required","auth:not_your_project","org:frozen","input:invalid_request","input:invalid_cursor","source:domain_authority_unavailable","source:asset_metadata_unavailable","source:audit_receipts_unavailable","internal:export_project_output_invalid","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false,"minimumTrustRung":1},"behavior":{"effect":"read","openWorld":true,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"Project exit metadata can reveal tenant-specific domain names, retained-bundle fingerprints, and billing history. It is a strict bounded direct MCP/REST read with no CLI, SDK, cache, or code-mode projection; full BIND data remains export_dns_zone and raw asset/ledger payloads are unavailable."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"export_project","discoveryGroup":"exit","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[exit] Read one explicitly selected, source-labelled page of current domain authority, retained asset metadata, or safe project audit receipts. BIND content stays in export_dns_zone; raw files, provider references, and raw ledger rows are unavailable.\n\nUse when: You need a bounded, source-labelled inventory of the exit-relevant domain authority, retained-asset metadata, or safe project audit receipts for one owned project.\nDo not use when: You need BIND content, raw asset files, provider references, resource IDs, raw ledger rows, generated rebuild prose, an implicit full leave package, a mutation, or a CLI/code-mode response.\nErrors: auth:required, auth:insufficient_scope, auth:rung_required, auth:not_your_project, org:frozen, input:invalid_request, input:invalid_cursor, source:domain_authority_unavailable, source:asset_metadata_unavailable, source:audit_receipts_unavailable, internal:export_project_output_invalid, internal:tool_failure"},"rest":{"method":"get","path":"/v1/projects/{id}/export","successEncoding":"json_envelope_data"}}},{"operationId":"get_audit_log","title":"Read audit log","description":"Read the org's append-only activity history with bounded filtering and pagination.","useWhen":["You need a direct chronological page of the current organization's immutable activity history, including human, system, and other-agent-key events, and can handle raw ledger material safely."],"doNotUseWhen":["You need a same-agent-only or project-only history, a bounded code-mode aggregate, a cacheable CLI/SDK result, an audit-export artifact, or authority to mutate, verify, or replay ledger history."],"examples":[{"kind":"positive","title":"Read the first direct audit page","expectedOutcome":"Returns at most 200 chronological V1 ledger rows, a page-local chain head, and a cursor only when another page may exist."},{"kind":"negative","title":"Do not filter the organization ledger by caller actor","expectedOutcome":"The authenticated key's identity controls only rate limiting; the page remains an organization-wide audit read and can contain human, system, and other-agent-key entries."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.get_audit_log.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.get_audit_log.output.v1"}},"errors":["auth:required","auth:insufficient_scope","input:invalid_request","input:invalid_date","rate:limited","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false,"minimumTrustRung":1},"behavior":{"effect":"read","openWorld":false,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"The V1 organization audit page intentionally exposes raw tenant ledger rows (including human, system, and other-agent-key activity) and has no server-enforced serialized-byte cap. Its actor value is only a per-key rate-limit identity, never a result filter; keep it on direct MCP with redacted logs and no CLI/cache/code-mode invocation."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"get_audit_log","discoveryGroup":"governance","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[governance] Read the org's append-only activity history with bounded filtering and pagination.\n\nUse when: You need a direct chronological page of the current organization's immutable activity history, including human, system, and other-agent-key events, and can handle raw ledger material safely.\nDo not use when: You need a same-agent-only or project-only history, a bounded code-mode aggregate, a cacheable CLI/SDK result, an audit-export artifact, or authority to mutate, verify, or replay ledger history.\nErrors: auth:required, auth:insufficient_scope, input:invalid_request, input:invalid_date, rate:limited, internal:tool_failure"}}},{"operationId":"get_budget_mandate","title":"Read verifiable budget mandate","description":"Return the active AP2 mandate and verification metadata without signing authority.","useWhen":["You need a direct, tenant-scoped view of the active AP2 budget mandate before explaining why spend is or is not currently enforceable."],"doNotUseWhen":["You need a raw signed mandate, a verification URL, authority to sign, enable, revoke, or alter a mandate, or a programmatic/code-mode budget source."],"examples":[{"kind":"positive","title":"Inspect active AP2 budget enforcement","expectedOutcome":"Returns the active mandate's bounded monetary summary and whether the current issuer presentation is bound, without signing, reserving, or changing budget state."},{"kind":"negative","title":"Do not treat a profile alone as active enforcement","expectedOutcome":"A monthly-profile mandate is enforcement-active only when AP2 mode is enabled and its delegated presenter matches the current issuer key."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.get_budget_mandate.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.get_budget_mandate.output.v1"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","input:invalid_request","input:invalid_project_id","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false,"minimumTrustRung":1},"behavior":{"effect":"read","openWorld":false,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"Current AP2 mandate status exposes tenant-scoped payment-authority state and has no separately reviewed bounded programmatic source/read model; keep it on the direct governed transport with no generated CLI or code-mode invocation."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"get_budget_mandate","discoveryGroup":"governance","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[governance] Return the active AP2 mandate and verification metadata without signing authority.\n\nUse when: You need a direct, tenant-scoped view of the active AP2 budget mandate before explaining why spend is or is not currently enforceable.\nDo not use when: You need a raw signed mandate, a verification URL, authority to sign, enable, revoke, or alter a mandate, or a programmatic/code-mode budget source.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, input:invalid_request, input:invalid_project_id, internal:tool_failure"},"rest":{"method":"get","path":"/v1/projects/{id}/ap2/mandates","successEncoding":"json_envelope_data"}}},{"operationId":"get_costs","title":"Project costs","description":"Return committed spend, active reservations, and the project budget cap.","useWhen":["You need the current committed spend, active reservations, and budget cap for an owned project."],"doNotUseWhen":["You need a quote, a reservation, a budget change, or any other governed mutation."],"examples":[{"kind":"positive","title":"Read a project's budget state","expectedOutcome":"Returns committed, reserved, and cap amounts without creating a reservation."},{"kind":"negative","title":"Do not reserve through a costs read","expectedOutcome":"Use the governed quote flow when a reservation or policy verdict is needed."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.get_costs.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.get_costs.output.v1"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project"],"lifecycle":{"status":"active","since":"2026-07-21"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false,"minimumTrustRung":1},"behavior":{"effect":"read","openWorld":false,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"programmatic_read","limits":{"maxCalls":10,"maxParallel":2,"maxRuntimeMs":10000,"maxResultBytes":4096}}},"safety":{"sensitiveOutput":"none","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"get_costs","discoveryGroup":"projects","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[projects] Return committed spend, active reservations, and the project budget cap.\n\nUse when: You need the current committed spend, active reservations, and budget cap for an owned project.\nDo not use when: You need a quote, a reservation, a budget change, or any other governed mutation.\nLimits: maxCalls=10, maxParallel=2, maxRuntimeMs=10000, maxResultBytes=4096.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project"},"rest":{"method":"get","path":"/v1/projects/{id}/costs","successEncoding":"json_envelope_data"},"cli":{"path":["invoke","get_costs"],"invocation":"read"}}},{"operationId":"get_registrant_contact","title":"Get registrant contact","description":"Return only whether an org registrant contact is configured and its non-PII version.","useWhen":["You need to know whether the current organization has a dashboard-owned registrant contact and, if so, its non-PII version."],"doNotUseWhen":["You need legal name, address, phone, email, a contact write, a domain apply, a human approval, or any registrar mutation."],"examples":[{"kind":"positive","title":"Read non-PII contact configuration","expectedOutcome":"Returns only configured state, version, and the dashboard-owned contact path without reading or changing legal identity data."},{"kind":"negative","title":"Do not request registrant PII through agent credentials","expectedOutcome":"Stop at the non-PII status and direct a human to the signed-in dashboard for legal contact changes."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.get_registrant_contact.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.get_registrant_contact.output.v1"}},"errors":["auth:required","auth:insufficient_scope"],"lifecycle":{"status":"active","since":"2026-07-21"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false,"minimumTrustRung":1},"behavior":{"effect":"read","openWorld":false,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"programmatic_read","limits":{"maxCalls":10,"maxParallel":2,"maxRuntimeMs":10000,"maxResultBytes":1024}}},"safety":{"sensitiveOutput":"none","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"get_registrant_contact","discoveryGroup":"domains","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[domains] Return only whether an org registrant contact is configured and its non-PII version.\n\nUse when: You need to know whether the current organization has a dashboard-owned registrant contact and, if so, its non-PII version.\nDo not use when: You need legal name, address, phone, email, a contact write, a domain apply, a human approval, or any registrar mutation.\nLimits: maxCalls=10, maxParallel=2, maxRuntimeMs=10000, maxResultBytes=1024.\nErrors: auth:required, auth:insufficient_scope"},"rest":{"method":"get","path":"/v1/org/registrant-contact","successEncoding":"json_envelope_data"},"cli":{"path":["invoke","get_registrant_contact"],"invocation":"read"}}},{"operationId":"get_started","title":"Start signup (human decides)","description":"Create a human review and Stripe checkout handoff when the user asked the agent to assist with setup.","useWhen":["Your human explicitly asked you to help open a new account and needs the first-party review handoff before their Stripe card decision."],"doNotUseWhen":["You need to create an account without the human review, browse the handoff yourself, disclose or cache the claim capability, reuse an existing authenticated key, or use CLI/code mode."],"examples":[{"kind":"positive","title":"Create one human account-review handoff","expectedOutcome":"Returns only the configured-origin review URL, its paired public session id, and the one-shot claim secret needed by poll_signup."},{"kind":"negative","title":"Do not automate the human card decision","expectedOutcome":"Stop before CLI/code-mode execution and send only the first-party review URL to the human; never send the claim secret."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.get_started.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.get_started.output.v1"}},"errors":["signup:already_authenticated","rate:limited","input:invalid_request","input:unsupported_content_type","config:no_payment_provider","provider:stripe","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"optional","scopes":[],"humanOnly":false,"minimumTrustRung":0},"behavior":{"effect":"credential","openWorld":true,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"This creates a one-shot human account-review handoff and paired claim capability; allow only the reviewed MCP and JSON REST transports, never a generated CLI command or programmatic code-mode operation."}},"safety":{"sensitiveOutput":"one_shot_secret","receipt":"zero_cost","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"get_started","discoveryGroup":"onboarding","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"description":"[onboarding] Create a human review and Stripe checkout handoff when the user asked the agent to assist with setup.\n\nUse when: Your human explicitly asked you to help open a new account and needs the first-party review handoff before their Stripe card decision.\nDo not use when: You need to create an account without the human review, browse the handoff yourself, disclose or cache the claim capability, reuse an existing authenticated key, or use CLI/code mode.\nErrors: signup:already_authenticated, rate:limited, input:invalid_request, input:unsupported_content_type, config:no_payment_provider, provider:stripe, internal:tool_failure"},"rest":{"method":"post","path":"/v1/signup","successEncoding":"json_envelope_data"}}},{"operationId":"get_status","title":"Project status","description":"Return project services, live-resource count, monthly burn, purpose, and budget summary.","useWhen":["You need a direct summary of live resources, service run-rate, optional purpose, and budget state for one owned project."],"doNotUseWhen":["You need a bounded programmatic result, a provider refresh, a detailed inventory, a quote, or authority to mutate infrastructure."],"examples":[{"kind":"positive","title":"Inspect one project summary","expectedOutcome":"Returns teardown-aware service and budget summary data without contacting a provider or changing the project."},{"kind":"negative","title":"Do not use status as a bounded code-mode inventory","expectedOutcome":"Use a future bounded status observation for programmatic aggregation; this direct response has no response-byte or cursor bound."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.get_status.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.get_status.output.v1"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project"],"lifecycle":{"status":"active","since":"2026-07-21"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false},"behavior":{"effect":"read","openWorld":false,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"The existing status route returns an unbounded service/domain summary without a server-enforced serialized-byte cap or cursor. It remains outside programmatic execution and response caching; the reviewed direct SDK/CLI projection makes one explicit request with a client-side output guard."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"get_status","discoveryGroup":"projects","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[projects] Return project services, live-resource count, monthly burn, purpose, and budget summary.\n\nUse when: You need a direct summary of live resources, service run-rate, optional purpose, and budget state for one owned project.\nDo not use when: You need a bounded programmatic result, a provider refresh, a detailed inventory, a quote, or authority to mutate infrastructure.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project"},"rest":{"method":"get","path":"/v1/projects/{id}/status","successEncoding":"json_envelope_data"},"cli":{"path":["invoke","get_status"],"invocation":"read"}}},{"operationId":"get_task","title":"Poll a task","description":"Read durable progress, human input requirements, terminal errors, and the disclosed result for an owned task.","useWhen":["You need the direct owned-task view, including safe human-review state, terminal result/error material, and typed continuation guidance."],"doNotUseWhen":["You need a bounded code-mode poll, a cacheable result, a task outside the caller's organization, a way to approve a human decision, or authority to retry the underlying mutation."],"examples":[{"kind":"positive","title":"Inspect one owned durable task directly","expectedOutcome":"Returns the established rich task view and typed actions without starting, retrying, or mutating the task."},{"kind":"negative","title":"Do not use the rich task view for programmatic polling","expectedOutcome":"Use get_task_observation when a host needs the reviewed scalar stop conditions; this direct response can carry result and human-handoff material."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.get_task.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.get_task.output.v2"}},"errors":["auth:required","auth:insufficient_scope","input:unknown_task"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false},"behavior":{"effect":"read","openWorld":false,"destructive":false,"idempotency":"none","taskMode":"polls_handle","execution":{"mode":"direct_only","reason":"The rich task read can disclose a closed, bounded typed completion, a controlled bounded task error, and human-review context. Raw durable workflow results and errors never cross this boundary. It remains outside programmatic execution; use get_task_observation for bounded code-mode polling, while the reviewed direct SDK/CLI projection makes one explicit read."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"get_task","discoveryGroup":"tasks","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[tasks] Read durable progress, human input requirements, terminal errors, and the disclosed result for an owned task.\n\nUse when: You need the direct owned-task view, including safe human-review state, terminal result/error material, and typed continuation guidance.\nDo not use when: You need a bounded code-mode poll, a cacheable result, a task outside the caller's organization, a way to approve a human decision, or authority to retry the underlying mutation.\nErrors: auth:required, auth:insufficient_scope, input:unknown_task"},"rest":{"method":"get","path":"/v1/tasks/{id}","successEncoding":"json_envelope_data"},"cli":{"path":["invoke","get_task"],"invocation":"read"}}},{"operationId":"help","title":"How to use SproutPad","description":"Return the quickstart or topic-specific, copy-paste guidance.","useWhen":["You need anonymous, topic-scoped navigation and copy-paste guidance for the existing SproutPad agent surface."],"doNotUseWhen":["You need tenant data, a REST or CLI result, a code-mode invocation, raw caller-topic echoing, an authority decision, or a mutation; use the appropriate governed operation instead."],"examples":[{"kind":"positive","title":"Start with the bounded topic index","expectedOutcome":"Returns the static topic index and quickstart without requiring an agent key or granting any authority."},{"kind":"positive","title":"Read one known guide","expectedOutcome":"Returns the resolved static guide, never the caller's original query spelling."},{"kind":"negative","title":"Do not use help as a programmatic control-plane read","expectedOutcome":"Stop at MCP guidance; this surface has no REST, CLI, SDK, or code-mode invocation binding."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.help.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.help.output.v1"}},"errors":["input:invalid_request","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"anonymous","scopes":[],"humanOnly":false,"minimumTrustRung":0},"behavior":{"effect":"read","openWorld":false,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"Help is anonymous MCP-local interaction guidance with host-contextual prose and navigation, not a reviewed REST or CLI documentation response contract; keep it on direct MCP and out of code-mode invocation until a separate bounded HTTP/CLI projection is intentionally designed."}},"safety":{"sensitiveOutput":"none","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"help","discoveryGroup":"guidance","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[guidance] Return the quickstart or topic-specific, copy-paste guidance.\n\nUse when: You need anonymous, topic-scoped navigation and copy-paste guidance for the existing SproutPad agent surface.\nDo not use when: You need tenant data, a REST or CLI result, a code-mode invocation, raw caller-topic echoing, an authority decision, or a mutation; use the appropriate governed operation instead.\nErrors: input:invalid_request, internal:tool_failure"}}},{"operationId":"launch_service","title":"Launch a service","description":"Deploy or converge a named service, preview, optional domain, DNS, and TLS under policy and budget controls.","useWhen":["You own a project and have an explicit canonical domain, target, immutable Fly image or staged Cloudflare bundle, and recorded justification for one governed service launch."],"doNotUseWhen":["You need a server default target, a template selector, a mutable image tag, a source-plus-bundle hybrid, raw provider/workflow state, a code-mode call, or a claim that the provider launch has completed."],"examples":[{"kind":"positive","title":"Admit one immutable Fly service launch","expectedOutcome":"Returns one working launch task, zero-cost admission evidence, and one typed get_task poll action; provider completion remains task state."},{"kind":"negative","title":"Reject template selectors and implicit targets","expectedOutcome":"Every launch names target explicitly. Fly accepts source only; Cloudflare accepts a staged bundleId only. Unknown fields, including template, are rejected."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.launch_service.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.launch_service.output.v2"}},"errors":["input:invalid_request","input:idempotency_required","idempotency:key_reuse","auth:required","auth:insufficient_scope","auth:not_your_project","auth:rung_required","target:edge_unavailable","input:unknown_bundle","billing:delinquent","billing:disputed","org:frozen","policy:denied","rate:limited","capacity:launch","hostname:busy","hostname:claimed","internal:launch_service_output_invalid","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false},"behavior":{"effect":"write","openWorld":true,"destructive":true,"destructiveException":"A launch may create or replace external resources and consume a quoted reservation; it is classified destructive even though its primary effect is write.","idempotency":"client_key","taskMode":"returns_handle","execution":{"mode":"direct_only","reason":"Launching a service admits a policy-, budget-, approval-, and provider-governed durable task. It is never a programmatic read or code-mode operation; the reviewed direct SDK/CLI projection requires a caller-owned replay key and makes one admission request."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"launch_service","discoveryGroup":"deployments","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"description":"[deployments] Deploy or converge a named service, preview, optional domain, DNS, and TLS under policy and budget controls.\n\nUse when: You own a project and have an explicit canonical domain, target, immutable Fly image or staged Cloudflare bundle, and recorded justification for one governed service launch.\nDo not use when: You need a server default target, a template selector, a mutable image tag, a source-plus-bundle hybrid, raw provider/workflow state, a code-mode call, or a claim that the provider launch has completed.\nErrors: input:invalid_request, input:idempotency_required, idempotency:key_reuse, auth:required, auth:insufficient_scope, auth:not_your_project, auth:rung_required, target:edge_unavailable, input:unknown_bundle, billing:delinquent, billing:disputed, org:frozen, policy:denied, rate:limited, capacity:launch, hostname:busy, hostname:claimed, internal:launch_service_output_invalid, internal:tool_failure"},"rest":{"method":"post","path":"/v1/projects/{id}/launch","successEncoding":"json_envelope_data"},"cli":{"path":["invoke","launch_service"],"invocation":"mutation"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"list_dns_records","title":"List DNS records","description":"Read current provider DNS records and concurrency versions for an org-owned domain.","useWhen":["You need the current records and concurrency versions for one organization-owned domain before proposing a separately governed DNS action."],"doNotUseWhen":["You need a bounded or cacheable code-mode result, provider zone authority, ledger receipt identity, arbitrary provider metadata, another organization's domain, or authority to mutate DNS."],"examples":[{"kind":"positive","title":"Inspect safe owned-domain DNS records","expectedOutcome":"Returns the current provider record fields needed for a later version-fenced DNS operation, without provider references, ledger entry IDs, or mutation authority."},{"kind":"negative","title":"Do not use DNS listing as provider control data","expectedOutcome":"Use the direct governed DNS mutation surface with its own version and policy controls; this read never reveals provider authority or grants a mutation capability."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.list_dns_records.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.list_dns_records.output.v1"}},"errors":["auth:required","auth:insufficient_scope","input:invalid_request","input:invalid_domain","auth:not_your_domain","org:frozen","rate:limited","config:not_supported","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false},"behavior":{"effect":"read","openWorld":true,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"The existing provider-backed DNS inventory is tenant-sensitive, open-world, and has neither a cursor nor a server-enforced serialized-byte cap; use direct MCP/REST and do not expose it to programmatic execution or cached CLI output."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"list_dns_records","discoveryGroup":"domains","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[domains] Read current provider DNS records and concurrency versions for an org-owned domain.\n\nUse when: You need the current records and concurrency versions for one organization-owned domain before proposing a separately governed DNS action.\nDo not use when: You need a bounded or cacheable code-mode result, provider zone authority, ledger receipt identity, arbitrary provider metadata, another organization's domain, or authority to mutate DNS.\nErrors: auth:required, auth:insufficient_scope, input:invalid_request, input:invalid_domain, auth:not_your_domain, org:frozen, rate:limited, config:not_supported, internal:tool_failure"},"rest":{"method":"get","path":"/v1/domains/{domain}/dns","successEncoding":"json_envelope_data"}}},{"operationId":"list_domains","title":"List your human's domains","description":"List org-owned registered and connected domains, renewal state, custody, and binding versions.","useWhen":["You need the direct caller's organization-owned domain inventory, including renewal, custody, and project-binding state, before selecting a domain action."],"doNotUseWhen":["You need a bounded or cacheable code-mode result, a domain outside the caller's organization, registrar transfer authority, raw provider data, or authority to change domain state."],"examples":[{"kind":"positive","title":"Inspect organization-owned domains","expectedOutcome":"Returns ledger-derived domains with their project binding and safe renewal/custody summary without contacting a registrar or changing ownership."},{"kind":"negative","title":"Do not cache an organization domain inventory","expectedOutcome":"Call the direct transport only when needed; it is an unbounded, tenant-sensitive inventory rather than a programmatic discovery primitive."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.list_domains.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.list_domains.output.v1"}},"errors":["auth:required","auth:insufficient_scope","input:invalid_request","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false},"behavior":{"effect":"read","openWorld":false,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"The existing organization-wide domain inventory is tenant-sensitive and has no server-enforced serialized-byte cap or cursor; use direct MCP/REST and do not expose it to programmatic execution or cached CLI output."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"list_domains","discoveryGroup":"domains","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[domains] List org-owned registered and connected domains, renewal state, custody, and binding versions.\n\nUse when: You need the direct caller's organization-owned domain inventory, including renewal, custody, and project-binding state, before selecting a domain action.\nDo not use when: You need a bounded or cacheable code-mode result, a domain outside the caller's organization, registrar transfer authority, raw provider data, or authority to change domain state.\nErrors: auth:required, auth:insufficient_scope, input:invalid_request, internal:tool_failure"},"rest":{"method":"get","path":"/v1/domains","successEncoding":"json_envelope_data"}}},{"operationId":"list_drift","title":"List desired-state drift","description":"Return the latest tenant-scoped drift coverage and open findings without provider references, spec fingerprints, authority details, or raw errors.","useWhen":["You need the latest persisted desired-versus-observed coverage and public findings for one owned project without triggering a provider scan."],"doNotUseWhen":["You need fresh provider observation, repair authority, a bounded code-mode result, or a decision to mutate infrastructure."],"examples":[{"kind":"positive","title":"Read persisted project drift","expectedOutcome":"Returns the current tenant-scoped coverage and public findings without contacting a provider or starting a scan."},{"kind":"negative","title":"Do not treat drift listing as a scan or repair","expectedOutcome":"Use the separately governed scan flow only when a human requests fresh detect-only observation; never infer repair authority from a finding."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.list_drift.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.list_drift.output.v2"}},"errors":["input:invalid_request","auth:required","auth:insufficient_scope","auth:not_your_project","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-21"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false},"behavior":{"effect":"read","openWorld":false,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"The existing direct list permits a large bounded-cardinality result without a server-enforced serialized-byte cap or cursor; use direct MCP/REST until a narrower drift observation is available."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"list_drift","discoveryGroup":"governance","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[governance] Return the latest tenant-scoped drift coverage and open findings without provider references, spec fingerprints, authority details, or raw errors.\n\nUse when: You need the latest persisted desired-versus-observed coverage and public findings for one owned project without triggering a provider scan.\nDo not use when: You need fresh provider observation, repair authority, a bounded code-mode result, or a decision to mutate infrastructure.\nErrors: input:invalid_request, auth:required, auth:insufficient_scope, auth:not_your_project, internal:tool_failure"},"rest":{"method":"get","path":"/v1/projects/{id}/drift","successEncoding":"json_envelope_data"}}},{"operationId":"list_emails","title":"List cached email snapshots","description":"List bounded inbound-email snapshots already captured for an owned project. This never refreshes or queries the mail provider.","useWhen":["You need a small, timestamped inbox snapshot to identify a message for a later read without asking the provider for current state."],"doNotUseWhen":["You need provider-fresh mail, an arbitrary inbox search, HTML, attachment bytes or filenames, raw provider message identifiers, a message mutation, an SDK/CLI invocation, or programmatic code-mode execution."],"examples":[{"kind":"positive","title":"List a bounded mailbox snapshot","expectedOutcome":"Returns at most 20 timestamped cached inbound envelopes, each with typed untrusted preview content and an opaque emailId."},{"kind":"negative","title":"Do not treat an inbox list as provider truth","expectedOutcome":"Use the capturedAt value as the snapshot timestamp; do not infer that no newer email exists at the provider."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.list_emails.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.list_emails.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:rung_required","auth:not_your_project","input:invalid_body","input:invalid_email_snapshot_cursor","email:snapshot_not_ready","internal:email_snapshot_output_invalid","internal:email_snapshot_unavailable"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false,"minimumTrustRung":2},"behavior":{"effect":"read","openWorld":true,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"Inbound email is tenant-sensitive, attacker-controlled material. These operations return only bounded, timestamped cached snapshots with typed untrusted-content markers; they never imply provider freshness and are excluded from SDK, CLI, response caches, and programmatic code mode."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"list_emails","discoveryGroup":"mail","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[mail] List bounded inbound-email snapshots already captured for an owned project. This never refreshes or queries the mail provider.\n\nUse when: You need a small, timestamped inbox snapshot to identify a message for a later read without asking the provider for current state.\nDo not use when: You need provider-fresh mail, an arbitrary inbox search, HTML, attachment bytes or filenames, raw provider message identifiers, a message mutation, an SDK/CLI invocation, or programmatic code-mode execution.\nErrors: auth:required, auth:insufficient_scope, auth:rung_required, auth:not_your_project, input:invalid_body, input:invalid_email_snapshot_cursor, email:snapshot_not_ready, internal:email_snapshot_output_invalid, internal:email_snapshot_unavailable"},"rest":{"method":"get","path":"/v1/projects/{id}/emails","successEncoding":"json_envelope_data"}}},{"operationId":"list_resources","title":"List what's running in a project","description":"Return the teardown-aware resource inventory, services, URLs, parked domains, and costs.","useWhen":["You need a direct safe inventory of one owned project's resource kinds, costs, service grouping, public URLs, and parked domains before choosing a governed operation."],"doNotUseWhen":["You need a bounded or cacheable code-mode result, a provider resource identifier, raw ledger tags, another project's inventory, or authority to change infrastructure."],"examples":[{"kind":"positive","title":"Inspect a safe project resource inventory","expectedOutcome":"Returns a teardown-aware public inventory without provider IDs, arbitrary ledger tags, or a provider refresh."},{"kind":"negative","title":"Do not use resource inventory as provider control data","expectedOutcome":"Use direct governed domain, service, or teardown operations with their own inputs; this read never grants raw provider references or mutation authority."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.list_resources.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.list_resources.output.v1"}},"errors":["input:invalid_request","auth:required","auth:insufficient_scope","auth:not_your_project","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false},"behavior":{"effect":"read","openWorld":false,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"The existing project inventory returns unbounded resource and service arrays without a server-enforced serialized-byte cap or cursor; use direct MCP/REST and do not expose it to programmatic execution or cached CLI output."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"list_resources","discoveryGroup":"projects","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[projects] Return the teardown-aware resource inventory, services, URLs, parked domains, and costs.\n\nUse when: You need a direct safe inventory of one owned project's resource kinds, costs, service grouping, public URLs, and parked domains before choosing a governed operation.\nDo not use when: You need a bounded or cacheable code-mode result, a provider resource identifier, raw ledger tags, another project's inventory, or authority to change infrastructure.\nErrors: input:invalid_request, auth:required, auth:insufficient_scope, auth:not_your_project, internal:tool_failure"},"rest":{"method":"get","path":"/v1/projects/{id}/resources","successEncoding":"json_envelope_data"}}},{"operationId":"list_webhooks","title":"List webhook endpoints (read-only)","description":"List webhook destinations and health metadata without secrets or mutation authority.","useWhen":["You need a safe overview of where one organization's human-owned audit or task notifications are routed, without receiving endpoint paths, query strings, credentials, or mutation authority."],"doNotUseWhen":["You need a raw endpoint URL, endpoint path/query/fragment, credential, webhook secret, unfiltered subscription value, webhook mutation, bounded programmatic result, or authority to redirect an audit stream."],"examples":[{"kind":"positive","title":"Inspect redacted webhook delivery configuration","expectedOutcome":"Returns opaque endpoint IDs, redacted HTTPS origins, known delivery families, and health state without revealing secrets or allowing changes."},{"kind":"negative","title":"Do not use a webhook inventory to redirect notifications","expectedOutcome":"Stop at the read-only summary and direct the human to the signed-in dashboard for every create, rotate, disable, or delete action."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.list_webhooks.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.list_webhooks.output.v1"}},"errors":["input:invalid_request","auth:required","auth:insufficient_scope","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false,"minimumTrustRung":1},"behavior":{"effect":"read","openWorld":false,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"Webhook routing and health are tenant-sensitive human-owned egress configuration, and the existing inventory has neither cursor pagination nor a server-enforced serialized-byte cap; use direct MCP only and never expose raw destinations to programmatic execution, CLI caching, or SDK REST invocation."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"list_webhooks","discoveryGroup":"integrations","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[integrations] List webhook destinations and health metadata without secrets or mutation authority.\n\nUse when: You need a safe overview of where one organization's human-owned audit or task notifications are routed, without receiving endpoint paths, query strings, credentials, or mutation authority.\nDo not use when: You need a raw endpoint URL, endpoint path/query/fragment, credential, webhook secret, unfiltered subscription value, webhook mutation, bounded programmatic result, or authority to redirect an audit stream.\nErrors: input:invalid_request, auth:required, auth:insufficient_scope, internal:tool_failure"}}},{"operationId":"move_service","title":"Move a service to a different project","description":"Reassign one named service's billing and ledger custody between two owned projects without changing its provider deployment or public routing. The destination must have headroom for the service's current monthly attribution.","useWhen":["You own both projects and need to move exactly one named service into a different budget group."],"doNotUseWhen":["You need a provider redeployment, want to move mail routes, cannot state both project IDs exactly, or need a generated SDK, CLI, or code-mode call."],"examples":[{"kind":"positive","title":"Move one service between owned projects","expectedOutcome":"Returns bounded custody, zero-cost receipt, explicit reverse-operation posture, and destination budget evidence."},{"kind":"negative","title":"Do not probe a destination project","expectedOutcome":"An unavailable source or destination returns the same bounded not-your-project result."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.move_service.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.move_service.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","input:invalid_request","input:same_project","input:invalid_service","input:idempotency_required","idempotency:key_reuse","billing:delinquent","billing:disputed","budget:insufficient","org:frozen","policy:denied","hostname:reassignment_required","rate:limited","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false},"behavior":{"effect":"write","openWorld":false,"destructive":false,"idempotency":"client_key","taskMode":"none","execution":{"mode":"direct_only","reason":"Moving a service transfers governed billing and ledger custody between two tenant-owned budget groups. It stays on direct MCP/REST with a required replay key; it is never a programmatic read, code-mode operation, generated SDK call, or CLI command."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"explicit_operation","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"move_service","discoveryGroup":"deployments","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[deployments] Reassign one named service's billing and ledger custody between two owned projects without changing its provider deployment or public routing. The destination must have headroom for the service's current monthly attribution.\n\nUse when: You own both projects and need to move exactly one named service into a different budget group.\nDo not use when: You need a provider redeployment, want to move mail routes, cannot state both project IDs exactly, or need a generated SDK, CLI, or code-mode call.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, input:invalid_request, input:same_project, input:invalid_service, input:idempotency_required, idempotency:key_reuse, billing:delinquent, billing:disputed, budget:insufficient, org:frozen, policy:denied, hostname:reassignment_required, rate:limited, internal:tool_failure"},"rest":{"method":"post","path":"/v1/services/move","successEncoding":"json_envelope_data"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"poll_billing","title":"Finish billing setup","description":"Poll an org-bound setup session and attach the human-authorized payment method once settled.","useWhen":["Your human has completed the card-on-file handoff created for this organization and you need to resume that exact billing session."],"doNotUseWhen":["You need to create a checkout link, disclose card data, use a session from another organization, bypass the human Stripe handoff, cache a response, or run a programmatic/CLI operation."],"examples":[{"kind":"positive","title":"Resume one human billing handoff","expectedOutcome":"Returns a fixed pending retry state until checkout settles, then one bounded attachment outcome without returning the session or payment details."},{"kind":"negative","title":"Do not treat an idempotent billing poll as a read","expectedOutcome":"Stop before code-mode or CLI execution because a settled retry mutates billing and entitlement state."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.poll_billing.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.poll_billing.output.v1"}},"errors":["auth:required","auth:insufficient_scope","rate:limited","input:invalid_request","input:unsupported_content_type","config:no_payment_provider","billing:unknown_or_ineligible_session","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false,"minimumTrustRung":1},"behavior":{"effect":"credential","openWorld":true,"destructive":false,"idempotency":"state_machine","taskMode":"none","execution":{"mode":"direct_only","reason":"A completed poll can attach a payment method, raise the organization trust rung, append billing evidence, and trigger entitlement recovery. Allow only the reviewed MCP and JSON REST transports, never a generated CLI command or programmatic code mode."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"poll_billing","discoveryGroup":"onboarding","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[onboarding] Poll an org-bound setup session and attach the human-authorized payment method once settled.\n\nUse when: Your human has completed the card-on-file handoff created for this organization and you need to resume that exact billing session.\nDo not use when: You need to create a checkout link, disclose card data, use a session from another organization, bypass the human Stripe handoff, cache a response, or run a programmatic/CLI operation.\nErrors: auth:required, auth:insufficient_scope, rate:limited, input:invalid_request, input:unsupported_content_type, config:no_payment_provider, billing:unknown_or_ineligible_session, internal:tool_failure"},"rest":{"method":"post","path":"/v1/billing/poll","successEncoding":"json_envelope_data"}}},{"operationId":"poll_signup","title":"Finish signup and get your agent key","description":"Poll a signup capability after human checkout and disclose the agent key exactly once.","useWhen":["Your human has completed the first-party review and Stripe checkout from get_started, and you still hold that exact paired claim capability."],"doNotUseWhen":["You do not hold both values returned by get_started, need to probe a public session URL, need a second key disclosure, want to bypass the human checkout, or need CLI/code mode."],"examples":[{"kind":"positive","title":"Resume one explicit account-opening handoff","expectedOutcome":"Returns the fixed pending continuation until checkout settles, then the newly issued bearer and first project exactly once."},{"kind":"negative","title":"Do not treat signup polling as a read","expectedOutcome":"Stop before programmatic or CLI execution because settlement can provision an account and disclose a credential."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.poll_signup.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.poll_signup.output.v1"}},"errors":["signup:already_authenticated","rate:limited","input:invalid_request","input:unsupported_content_type","config:no_payment_provider","signup:claim_forbidden","billing:no_card_on_file","billing:wrong_flow","signup:email_in_use","signup:already_claimed","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"anonymous","scopes":[],"humanOnly":false,"minimumTrustRung":0},"behavior":{"effect":"credential","openWorld":true,"destructive":false,"idempotency":"state_machine","taskMode":"none","execution":{"mode":"direct_only","reason":"A settled signup poll verifies a one-shot claim capability, can provision a new account and key, and discloses that key once; allow only the reviewed MCP and JSON REST transports, never a generated CLI command or programmatic code-mode operation."}},"safety":{"sensitiveOutput":"one_shot_secret","receipt":"zero_cost","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"poll_signup","discoveryGroup":"onboarding","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[onboarding] Poll a signup capability after human checkout and disclose the agent key exactly once.\n\nUse when: Your human has completed the first-party review and Stripe checkout from get_started, and you still hold that exact paired claim capability.\nDo not use when: You do not hold both values returned by get_started, need to probe a public session URL, need a second key disclosure, want to bypass the human checkout, or need CLI/code mode.\nErrors: signup:already_authenticated, rate:limited, input:invalid_request, input:unsupported_content_type, config:no_payment_provider, signup:claim_forbidden, billing:no_card_on_file, billing:wrong_flow, signup:email_in_use, signup:already_claimed, internal:tool_failure"},"rest":{"method":"post","path":"/v1/signup/poll","successEncoding":"json_envelope_data"}}},{"operationId":"quote","title":"Quote a launch","description":"Evaluate project policy and provider prices; an ALLOW result creates a 15-minute budget reservation. Quote does not accept a ledger justification; supply justification to launch_service.","useWhen":["You are ready to price one explicit service/target launch for an owned project and obtain a short-lived budget reservation if policy allows it."],"doNotUseWhen":["You need anonymous planning, a code-mode or safe-read SDK call, a reusable price cache, a launch without a fresh reservation, or an unbounded provider quote/result."],"examples":[{"kind":"positive","title":"Reserve budget for one explicit Fly launch","expectedOutcome":"An ALLOW response records one budget reservation and returns the exact launch_service continuation with source and justification still required."},{"kind":"negative","title":"Do not use a quote as a programmatic price read","expectedOutcome":"Use estimate for anonymous planning; quote evaluates live policy and can change budget headroom, so a host must call it directly with a stable idempotency key."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.quote.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.quote.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","input:invalid_body","input:invalid_domain","input:invalid_service","input:idempotency_key_required","idempotency:key_reuse","rate:limited","policy:denied","scope:buy_domain","policy:budget_cap","billing:automated_charge_ceiling","billing:delinquent","billing:disputed","org:frozen","org:closing","org:closed","plan:max_projects","plan:org_budget_ceiling","trust:rung_too_low","internal:quote_output_invalid","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false},"behavior":{"effect":"reserve","openWorld":true,"destructive":false,"idempotency":"client_key","taskMode":"none","execution":{"mode":"direct_only","reason":"An ALLOW quote creates a short-lived budget reservation from live provider pricing and policy. It is never a programmatic read or code-mode operation; the reviewed direct SDK/CLI projection sends one explicit request with a caller-owned replay key and no response caching."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"conditional","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"quote","discoveryGroup":"planning","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[planning] Evaluate project policy and provider prices; an ALLOW result creates a 15-minute budget reservation. Quote does not accept a ledger justification; supply justification to launch_service.\n\nUse when: You are ready to price one explicit service/target launch for an owned project and obtain a short-lived budget reservation if policy allows it.\nDo not use when: You need anonymous planning, a code-mode or safe-read SDK call, a reusable price cache, a launch without a fresh reservation, or an unbounded provider quote/result.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, input:invalid_body, input:invalid_domain, input:invalid_service, input:idempotency_key_required, idempotency:key_reuse, rate:limited, policy:denied, scope:buy_domain, policy:budget_cap, billing:automated_charge_ceiling, billing:delinquent, billing:disputed, org:frozen, org:closing, org:closed, plan:max_projects, plan:org_budget_ceiling, trust:rung_too_low, internal:quote_output_invalid, internal:tool_failure"},"rest":{"method":"post","path":"/v1/quotes","successEncoding":"json_envelope_data"},"cli":{"path":["invoke","quote"],"invocation":"mutation"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"read_email","title":"Read a cached email snapshot","description":"Read one bounded, timestamped inbound-email snapshot by its opaque emailId. This never fetches from the mail provider.","useWhen":["You have an emailId from list_emails and need the bounded plain-text snapshot captured for that owned project."],"doNotUseWhen":["You need current provider data, a raw message id, a mailbox selector, HTML, attachment content or filenames, a raw source header, a reply action, an SDK/CLI invocation, or programmatic code-mode execution."],"examples":[{"kind":"positive","title":"Read one bounded snapshot","expectedOutcome":"Returns typed untrusted plain text, truncation state, and a capturedAt timestamp without a provider call."},{"kind":"negative","title":"Do not render or fetch HTML attachments","expectedOutcome":"The public read contains no HTML, file bytes, filename, MIME parameter, or live-provider fallback."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.read_email.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.read_email.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:rung_required","auth:not_your_project","input:invalid_body","input:unknown_email","email:snapshot_not_ready","internal:email_snapshot_output_invalid","internal:email_snapshot_unavailable"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false,"minimumTrustRung":2},"behavior":{"effect":"read","openWorld":true,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"Inbound email is tenant-sensitive, attacker-controlled material. These operations return only bounded, timestamped cached snapshots with typed untrusted-content markers; they never imply provider freshness and are excluded from SDK, CLI, response caches, and programmatic code mode."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"read_email","discoveryGroup":"mail","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[mail] Read one bounded, timestamped inbound-email snapshot by its opaque emailId. This never fetches from the mail provider.\n\nUse when: You have an emailId from list_emails and need the bounded plain-text snapshot captured for that owned project.\nDo not use when: You need current provider data, a raw message id, a mailbox selector, HTML, attachment content or filenames, a raw source header, a reply action, an SDK/CLI invocation, or programmatic code-mode execution.\nErrors: auth:required, auth:insufficient_scope, auth:rung_required, auth:not_your_project, input:invalid_body, input:unknown_email, email:snapshot_not_ready, internal:email_snapshot_output_invalid, internal:email_snapshot_unavailable"},"rest":{"method":"get","path":"/v1/projects/{id}/emails/{emailId}","successEncoding":"json_envelope_data"}}},{"operationId":"remove_service","title":"Remove one service from a project","description":"Plan or remove exactly one named service and its resources without touching sibling services or parked domains. State dryRun and a meaningful justification explicitly; use teardown for a whole-project cleanup.","useWhen":["You own a project and need a governed plan or durable destructive task for exactly one named service, with dryRun and a meaningful justification stated deliberately."],"doNotUseWhen":["You need to remove the whole project, a default dry run, an unscoped delete, raw provider deletion results, a generated SDK/CLI/code-mode call, or a way to cancel a task after durable admission."],"examples":[{"kind":"positive","title":"Plan removal of one named service","expectedOutcome":"Returns one owned working teardown task, a zero-cost plan-admission receipt, the requested service name, no provider deletion claim, and one typed get_task poll action."},{"kind":"negative","title":"Do not use service removal as a defaulted deletion shortcut","expectedOutcome":"service, dryRun, and a meaningful recorded justification are required. The response exposes neither raw workflow/provider state nor a replay body marker."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.remove_service.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.remove_service.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","input:invalid_request","input:idempotency_required","idempotency:key_reuse","org:frozen","policy:denied","rate:limited","hostname:busy","hostname:claimed","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["teardown"],"humanOnly":false},"behavior":{"effect":"delete","openWorld":true,"destructive":true,"idempotency":"client_key","taskMode":"returns_handle","execution":{"mode":"direct_only","reason":"Removing a service admits a governed destructive durable task that can release tenant infrastructure and alter billing state. It stays on direct MCP/REST with a required replay key; it is never a programmatic read, code-mode operation, generated SDK call, or CLI command."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"conditional","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"remove_service","discoveryGroup":"deployments","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"description":"[deployments] Plan or remove exactly one named service and its resources without touching sibling services or parked domains. State dryRun and a meaningful justification explicitly; use teardown for a whole-project cleanup.\n\nUse when: You own a project and need a governed plan or durable destructive task for exactly one named service, with dryRun and a meaningful justification stated deliberately.\nDo not use when: You need to remove the whole project, a default dry run, an unscoped delete, raw provider deletion results, a generated SDK/CLI/code-mode call, or a way to cancel a task after durable admission.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, input:invalid_request, input:idempotency_required, idempotency:key_reuse, org:frozen, policy:denied, rate:limited, hostname:busy, hostname:claimed, internal:tool_failure"},"rest":{"method":"post","path":"/v1/projects/{id}/services/remove","successEncoding":"json_envelope_data"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"request_budget_increase","title":"Ask your human to raise a budget","description":"Create a pending request and notify the human; the agent cannot approve or change the cap.","useWhen":["You need to ask a human to raise an exact whole-cent project or organization budget cap and can provide a bounded justification."],"doNotUseWhen":["You need to approve a budget request, change a cap directly, select a project by omission, use account scope, use a USD float amount, invoke generated SDK/CLI/code-mode, or receive an approval URL or token."],"examples":[{"kind":"positive","title":"Ask a human to raise one project cap","expectedOutcome":"Creates one pending human-decision request, records zero-cost audit evidence, and stops because no agent approval operation exists."},{"kind":"negative","title":"Do not use a budget request as approval authority","expectedOutcome":"Stop: the request does not change any cap and only a signed-in human can decide it."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.request_budget_increase.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.request_budget_increase.output.v2"}},"errors":["input:invalid_body","input:idempotency_key_required","idempotency:key_reuse","auth:required","auth:insufficient_scope","auth:not_your_project","input:not_higher","billing:delinquent","billing:disputed","org:frozen","plan:org_budget_ceiling","policy:denied","rate:limited","internal:tool_failure","internal:budget_request_output_invalid"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false,"minimumTrustRung":1},"behavior":{"effect":"write","openWorld":true,"destructive":false,"idempotency":"client_key","taskMode":"none","execution":{"mode":"direct_only","reason":"Requesting a budget increase creates a durable spend-governance request, notifies a human, and appends audit evidence. It is a direct MCP/REST mutation with a stable replay identity, never a programmatic read, code-mode operation, generated SDK call, or CLI command."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"request_budget_increase","discoveryGroup":"governance","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[governance] Create a pending request and notify the human; the agent cannot approve or change the cap.\n\nUse when: You need to ask a human to raise an exact whole-cent project or organization budget cap and can provide a bounded justification.\nDo not use when: You need to approve a budget request, change a cap directly, select a project by omission, use account scope, use a USD float amount, invoke generated SDK/CLI/code-mode, or receive an approval URL or token.\nErrors: input:invalid_body, input:idempotency_key_required, idempotency:key_reuse, auth:required, auth:insufficient_scope, auth:not_your_project, input:not_higher, billing:delinquent, billing:disputed, org:frozen, plan:org_budget_ceiling, policy:denied, rate:limited, internal:tool_failure, internal:budget_request_output_invalid"},"rest":{"method":"post","path":"/v1/budget-requests","successEncoding":"json_envelope_data"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"scan_drift","title":"Scan desired-state drift","description":"Run a detect-only provider observation pass. Results explicitly report the rollout mode and whether evidence was persisted: record persists version-fenced observations/findings, shadow is ephemeral, and off performs no provider reads. It never repairs or mutates provider resources.","useWhen":["You explicitly need a fresh detect-only provider observation for one owned project and can reuse one stable replay key for transport retries."],"doNotUseWhen":["You need a code-mode/CLI call, a repair, an arbitrary provider action, a provider reference, a raw observation, or a scan without a stable idempotency identity."],"examples":[{"kind":"positive","title":"Refresh detect-only drift evidence","expectedOutcome":"Runs the existing governed scanner once per stable key and returns the bounded public coverage, findings, and scan outcome."},{"kind":"negative","title":"Do not turn a scan into repair or code mode","expectedOutcome":"Stop before programmatic execution: scanning may read providers and persist evidence but never grants repair authority."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.scan_drift.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.scan_drift.output.v2"}},"errors":["input:invalid_request","auth:required","auth:insufficient_scope","auth:not_your_project","input:idempotency_key_required","idempotency:key_reuse","rate:resource_drift","org:closing","org:closed","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["read"],"humanOnly":false},"behavior":{"effect":"write","openWorld":true,"destructive":false,"idempotency":"client_key","taskMode":"none","execution":{"mode":"direct_only","reason":"A manual drift scan performs governed provider observation and may persist reconciliation evidence; keep it on direct MCP/REST while its side-effecting scan boundary has no separately reviewed programmatic source/read model."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"scan_drift","discoveryGroup":"governance","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[governance] Run a detect-only provider observation pass. Results explicitly report the rollout mode and whether evidence was persisted: record persists version-fenced observations/findings, shadow is ephemeral, and off performs no provider reads. It never repairs or mutates provider resources.\n\nUse when: You explicitly need a fresh detect-only provider observation for one owned project and can reuse one stable replay key for transport retries.\nDo not use when: You need a code-mode/CLI call, a repair, an arbitrary provider action, a provider reference, a raw observation, or a scan without a stable idempotency identity.\nErrors: input:invalid_request, auth:required, auth:insufficient_scope, auth:not_your_project, input:idempotency_key_required, idempotency:key_reuse, rate:resource_drift, org:closing, org:closed, internal:tool_failure"},"rest":{"method":"post","path":"/v1/projects/{id}/drift/scan","successEncoding":"json_envelope_data"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"search_domains","title":"Search domains","description":"Search live registrar availability and pricing without authentication or a budget hold.","useWhen":["You need a small, anonymous set of current domain alternatives and indicative registrar prices before any quote, reservation, or purchase."],"doNotUseWhen":["You need an unbounded provider inventory, tenant domain state, an exact project-bound purchase quote, a cached price, or authority to register a domain."],"examples":[{"kind":"positive","title":"Compare bounded anonymous domain alternatives","expectedOutcome":"Returns up to twelve safe availability rows with truthful truncation and no budget hold, provider mutation, tenant lookup, or registration authority."},{"kind":"negative","title":"Do not treat discovery prices as a purchase commitment","expectedOutcome":"Use the authenticated governed quote flow before registering a chosen domain; this read is anonymous planning data only."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.search_domains.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.search_domains.output.v1"}},"errors":["input:invalid_query","input:missing_query","input:query_too_long","rate:limited","provider:domain_search_unavailable"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"anonymous","scopes":[],"humanOnly":false,"minimumTrustRung":0},"behavior":{"effect":"read","openWorld":true,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"programmatic_read","limits":{"maxCalls":4,"maxParallel":1,"maxRuntimeMs":30000,"maxResultBytes":16384}}},"safety":{"sensitiveOutput":"none","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"search_domains","discoveryGroup":"planning","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[planning] Search live registrar availability and pricing without authentication or a budget hold.\n\nUse when: You need a small, anonymous set of current domain alternatives and indicative registrar prices before any quote, reservation, or purchase.\nDo not use when: You need an unbounded provider inventory, tenant domain state, an exact project-bound purchase quote, a cached price, or authority to register a domain.\nLimits: maxCalls=4, maxParallel=1, maxRuntimeMs=30000, maxResultBytes=16384.\nErrors: input:invalid_query, input:missing_query, input:query_too_long, rate:limited, provider:domain_search_unavailable"},"rest":{"method":"get","path":"/v1/domains/search","successEncoding":"json_envelope_data"},"cli":{"path":["invoke","search_domains"],"invocation":"read"}}},{"operationId":"send_email","title":"Send an email","description":"Send irreversible outbound email under policy, screening, quota, intent, and ledger controls.","useWhen":["You have a rung-2, provision-authorized owned project mailbox and need to send one bounded plain-text message with an explicit stable replay key."],"doNotUseWhen":["You need a programmatic/code-mode call, HTML or attachment delivery, a default sender/recipient/body, an automatic retry or recall, a provider message identifier, or proof that a recipient delivered/read the email."],"examples":[{"kind":"positive","title":"Send one bounded plain-text update","expectedOutcome":"Returns only provider acceptance, recipient count, remaining quota, zero-cost receipt, irreversible undo semantics, and a typed non-delivery observation."},{"kind":"negative","title":"Do not retry an uncertain send","expectedOutcome":"A provider:outcome_unknown error explicitly stops. Reusing the same key preserves the durable reconciliation boundary; a fresh key must not blindly resend the message."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.send_email.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.send_email.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","trust:rung_too_low","input:invalid_body","input:invalid_address","input:subject_too_long","input:body_too_long","input:unknown_mailbox","input:missing_idempotency_key","idempotency:key_reuse","config:no_email_provider","abuse:header_injection","abuse:content_screen","org:frozen","billing:disputed","policy:denied","policy:verdict_invalid","quota:daily_send_cap","provider:outcome_unknown","internal:send_email_output_invalid"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false,"minimumTrustRung":2},"behavior":{"effect":"write","openWorld":true,"destructive":true,"destructiveException":"An outbound email is an irreversible external communication and is marked destructive, but remains a write because it creates a message rather than deleting a resource.","idempotency":"client_key","taskMode":"none","execution":{"mode":"direct_only","reason":"Sending email is an irreversible external communication guarded by rung-2 admission, policy, frozen/dispute walls, content screening, recipient quota, and a durable provider-intent replay boundary. Provider acceptance is not recipient delivery, and an ambiguous outcome must not be blindly retried. It is never a programmatic read or code-mode operation; the reviewed direct SDK/CLI projection requires a caller-owned replay key and makes one request."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"send_email","discoveryGroup":"mail","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"description":"[mail] Send irreversible outbound email under policy, screening, quota, intent, and ledger controls.\n\nUse when: You have a rung-2, provision-authorized owned project mailbox and need to send one bounded plain-text message with an explicit stable replay key.\nDo not use when: You need a programmatic/code-mode call, HTML or attachment delivery, a default sender/recipient/body, an automatic retry or recall, a provider message identifier, or proof that a recipient delivered/read the email.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, trust:rung_too_low, input:invalid_body, input:invalid_address, input:subject_too_long, input:body_too_long, input:unknown_mailbox, input:missing_idempotency_key, idempotency:key_reuse, config:no_email_provider, abuse:header_injection, abuse:content_screen, org:frozen, billing:disputed, policy:denied, policy:verdict_invalid, quota:daily_send_cap, provider:outcome_unknown, internal:send_email_output_invalid"},"rest":{"method":"post","path":"/v1/projects/{id}/emails/send","successEncoding":"json_envelope_data"},"cli":{"path":["invoke","send_email"],"invocation":"mutation"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"set_project_purpose","title":"Set or clear a project's purpose","description":"Set one canonical plain-English project-purpose note, or clear it with null, under an explicit observed version. The update is ledgered and does not alter infrastructure or budget.","useWhen":["You own a project and need to change its descriptive purpose using the exact version returned by a project read."],"doNotUseWhen":["You need to create a project with hidden side effects, mutate infrastructure, omit the version precondition, use whitespace/control-character normalization, or use a generated SDK/CLI/code-mode call."],"examples":[{"kind":"positive","title":"Set a versioned purpose","expectedOutcome":"Returns the canonical persisted purpose, next version, zero-cost receipt, and a typed terminal action."},{"kind":"negative","title":"Do not retry a version conflict unchanged","expectedOutcome":"A version conflict returns only the current version. Refresh state and make a new explicit request with a new idempotency key if the intent still applies."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.set_project_purpose.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.set_project_purpose.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","input:invalid_request","input:idempotency_required","idempotency:key_reuse","resource:version_conflict","org:frozen","policy:denied","rate:limited","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false},"behavior":{"effect":"write","openWorld":false,"destructive":false,"idempotency":"client_key","taskMode":"none","execution":{"mode":"direct_only","reason":"Changing a project purpose writes tenant configuration and an audit receipt under version preconditions. It remains a direct MCP/REST mutation with required replay identity, never a programmatic read, code-mode operation, generated SDK call, or CLI command."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"explicit_operation","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"set_project_purpose","discoveryGroup":"projects","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[projects] Set one canonical plain-English project-purpose note, or clear it with null, under an explicit observed version. The update is ledgered and does not alter infrastructure or budget.\n\nUse when: You own a project and need to change its descriptive purpose using the exact version returned by a project read.\nDo not use when: You need to create a project with hidden side effects, mutate infrastructure, omit the version precondition, use whitespace/control-character normalization, or use a generated SDK/CLI/code-mode call.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, input:invalid_request, input:idempotency_required, idempotency:key_reuse, resource:version_conflict, org:frozen, policy:denied, rate:limited, internal:tool_failure"},"rest":{"method":"post","path":"/v1/projects/{id}/purpose","successEncoding":"json_envelope_data"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"set_registrant_contact","title":"Configure registrant contact (human action)","description":"Return the signed-in dashboard path for human-only registrant PII entry; the agent call changes nothing.","useWhen":["Your human wants to opt in to customer-owned ICANN registrant identity and needs the signed-in dashboard boundary where legal details can be entered privately."],"doNotUseWhen":["You need to collect, inspect, write, apply, export, cache, or programmatically act on a legal name, address, email, phone number, registrant binding, registrar state, or a human approval."],"examples":[{"kind":"positive","title":"Hand a human off to private registrant configuration","expectedOutcome":"Returns one fixed dashboard path and makes no provider, ledger, policy, or registrant-contact mutation."},{"kind":"negative","title":"Do not send legal contact details through agent credentials","expectedOutcome":"Stop at the human dashboard handoff; legal identity stays outside the agent-facing surface."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.set_registrant_contact.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.set_registrant_contact.output.v1"}},"errors":["auth:required","auth:insufficient_scope","input:invalid_request","policy:human_required","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false,"minimumTrustRung":1},"behavior":{"effect":"read","openWorld":false,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"This is a fixed human-ownership handoff, not a registrant-contact read or mutation; keep it on direct MCP with no REST descriptor binding, generated CLI command, or code-mode invocation."}},"safety":{"sensitiveOutput":"none","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"set_registrant_contact","discoveryGroup":"domains","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[domains] Return the signed-in dashboard path for human-only registrant PII entry; the agent call changes nothing.\n\nUse when: Your human wants to opt in to customer-owned ICANN registrant identity and needs the signed-in dashboard boundary where legal details can be entered privately.\nDo not use when: You need to collect, inspect, write, apply, export, cache, or programmatically act on a legal name, address, email, phone number, registrant binding, registrar state, or a human approval.\nErrors: auth:required, auth:insufficient_scope, input:invalid_request, policy:human_required, internal:tool_failure"}}},{"operationId":"setup_billing","title":"Add or update your card on file","description":"Create an org-bound, no-charge Stripe card-setup handoff for the human.","useWhen":["Your authenticated human has explicitly asked to add or replace the card for this organization and needs a Stripe browser handoff."],"doNotUseWhen":["You need to collect card data, disclose or cache this handoff, change another organization, bypass the human Stripe step, use CLI/code mode, or infer billing attachment before poll_billing settles."],"examples":[{"kind":"positive","title":"Create one human card-on-file handoff","expectedOutcome":"Returns the allowlisted Stripe checkout URL and the one exact continuation needed by poll_billing; the human controls the card step."},{"kind":"negative","title":"Do not treat a billing handoff as a read or browser automation","expectedOutcome":"Stop before CLI/code-mode execution and do not visit the URL or synthesize the human's card decision."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.setup_billing.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.setup_billing.output.v1"}},"errors":["input:invalid_request","input:unsupported_content_type","auth:required","auth:insufficient_scope","rate:limited","config:no_payment_provider","provider:stripe","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false,"minimumTrustRung":1},"behavior":{"effect":"credential","openWorld":true,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"This creates a one-shot human billing handoff and continuation capability; allow only the reviewed MCP and JSON REST transports, never a generated CLI command or programmatic code-mode operation."}},"safety":{"sensitiveOutput":"one_shot_secret","receipt":"zero_cost","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"setup_billing","discoveryGroup":"onboarding","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"description":"[onboarding] Create an org-bound, no-charge Stripe card-setup handoff for the human.\n\nUse when: Your authenticated human has explicitly asked to add or replace the card for this organization and needs a Stripe browser handoff.\nDo not use when: You need to collect card data, disclose or cache this handoff, change another organization, bypass the human Stripe step, use CLI/code mode, or infer billing attachment before poll_billing settles.\nErrors: input:invalid_request, input:unsupported_content_type, auth:required, auth:insufficient_scope, rate:limited, config:no_payment_provider, provider:stripe, internal:tool_failure"},"rest":{"method":"post","path":"/v1/billing/setup-link","successEncoding":"json_envelope_data"}}},{"operationId":"teardown","title":"Tear down an entire project","description":"Dry-run or clean up all project services, DNS, and compute while retaining owned domains.","useWhen":["You own a project and explicitly need either a governed teardown plan or a durable destructive teardown task, with dryRun and justification stated deliberately."],"doNotUseWhen":["You need to remove one service, a default dry run, an unscoped delete, raw provider deletion results, a code-mode call, or a way to cancel a task after durable admission."],"examples":[{"kind":"positive","title":"Plan a whole-project teardown","expectedOutcome":"Returns one owned working teardown task, a zero-cost plan-admission receipt, no provider deletion claim, and one typed get_task poll action."},{"kind":"negative","title":"Do not use teardown as a defaulted deletion shortcut","expectedOutcome":"Both dryRun and a meaningful recorded justification are required. The response exposes neither raw workflow/provider state nor a replay body marker."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.teardown.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.teardown.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","input:invalid_request","input:idempotency_required","idempotency:key_reuse","org:frozen","policy:denied","rate:limited","hostname:busy","hostname:claimed","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["teardown"],"humanOnly":false},"behavior":{"effect":"delete","openWorld":true,"destructive":true,"idempotency":"client_key","taskMode":"returns_handle","execution":{"mode":"direct_only","reason":"Teardown admits a governed destructive durable task that can release tenant infrastructure and alter billing state. It is never a programmatic read or code-mode operation; the reviewed direct SDK/CLI projection requires a caller-owned replay key and makes one admission request."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"conditional","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"teardown","discoveryGroup":"deployments","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"description":"[deployments] Dry-run or clean up all project services, DNS, and compute while retaining owned domains.\n\nUse when: You own a project and explicitly need either a governed teardown plan or a durable destructive teardown task, with dryRun and justification stated deliberately.\nDo not use when: You need to remove one service, a default dry run, an unscoped delete, raw provider deletion results, a code-mode call, or a way to cancel a task after durable admission.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, input:invalid_request, input:idempotency_required, idempotency:key_reuse, org:frozen, policy:denied, rate:limited, hostname:busy, hostname:claimed, internal:tool_failure"},"rest":{"method":"post","path":"/v1/projects/{id}/teardown","successEncoding":"json_envelope_data"},"cli":{"path":["invoke","teardown"],"invocation":"mutation"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"upload_assets","title":"Upload static site assets","description":"Stage a validated static bundle for 24 hours so launch_service can deploy it to the edge.","useWhen":["You have already created a bounded static site for an owned project and need an immutable staged bundle for a later cloudflare launch_service call."],"doNotUseWhen":["You need a generated SDK/CLI/code-mode call, a mutable file store, server-side code execution, a bundle without a root index.html, an unbounded archive, or a launch without a separate domain and justification."],"examples":[{"kind":"positive","title":"Stage one static edge bundle","expectedOutcome":"Returns bounded bundle metadata, a zero-cost asset_bundle.staged receipt, expiry-only undo guidance, and one typed cloudflare launch_service continuation."},{"kind":"negative","title":"Do not use staging as a programmatic file upload primitive","expectedOutcome":"Call it directly with a fresh stable idempotency key; it persists tenant bytes and consumes a shared staging quota even though its dollar receipt is zero."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.upload_assets.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.upload_assets.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_project","auth:rung_required","input:invalid_request","input:invalid_assets","input:idempotency_required","idempotency:key_reuse","rate:limited","quota:asset_storage","billing:delinquent","billing:disputed","org:frozen","plan:limit","policy:denied","internal:asset_upload_output_invalid","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false},"behavior":{"effect":"write","openWorld":false,"destructive":false,"idempotency":"client_key","taskMode":"none","execution":{"mode":"direct_only","reason":"Asset staging accepts tenant-provided bytes, consumes a shared quota, creates durable state, and relies on a body-bound replay identity. Keep it on direct MCP/REST; it is not a programmatic read, code-mode operation, generated SDK call, or CLI command."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"conditional","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"upload_assets","discoveryGroup":"deployments","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[deployments] Stage a validated static bundle for 24 hours so launch_service can deploy it to the edge.\n\nUse when: You have already created a bounded static site for an owned project and need an immutable staged bundle for a later cloudflare launch_service call.\nDo not use when: You need a generated SDK/CLI/code-mode call, a mutable file store, server-side code execution, a bundle without a root index.html, an unbounded archive, or a launch without a separate domain and justification.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_project, auth:rung_required, input:invalid_request, input:invalid_assets, input:idempotency_required, idempotency:key_reuse, rate:limited, quota:asset_storage, billing:delinquent, billing:disputed, org:frozen, plan:limit, policy:denied, internal:asset_upload_output_invalid, internal:tool_failure"},"rest":{"method":"post","path":"/v1/projects/{id}/assets","successEncoding":"json_envelope_data"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"upsert_dns_record","title":"Add or update a DNS record","description":"Create or replace public DNS under Cedar policy, optimistic concurrency, and ledger controls.","useWhen":["You have an owned domain, its current DNS concurrency version, and one exact A, AAAA, CNAME, TXT, or MX record to create or update under the domain's existing policy."],"doNotUseWhen":["You need a programmatic/code-mode/SDK/CLI call, a default priority or version, a non-canonical/foreign DNS owner name, a provider reference, a generic rollback, or a DNS deletion."],"examples":[{"kind":"positive","title":"Create one TXT verification record","expectedOutcome":"Returns a bounded created-record receipt, irreversible undo semantics, and one typed fresh-list action before any later DNS mutation."},{"kind":"negative","title":"Do not guess a DNS concurrency version","expectedOutcome":"Read the owned domain's records first and supply the returned version; a conflict returns only a bounded refresh action, never an automatic retry or rollback."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.upsert_dns_record.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.upsert_dns_record.output.v2"}},"errors":["auth:required","auth:insufficient_scope","auth:not_your_domain","auth:not_your_project","input:invalid_domain","input:invalid_record","input:invalid_justification","input:expected_version_required","input:idempotency_key_required","idempotency:key_reuse","rate:limited","domain:reverification_required","billing:delinquent","billing:disputed","org:frozen","config:not_supported","policy:denied","resource:version_conflict","provider:reconciliation_required","provider:dns_failed","internal:tool_failure","internal:upsert_dns_record_output_invalid"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"required","scopes":["provision"],"humanOnly":false},"behavior":{"effect":"write","openWorld":true,"destructive":true,"destructiveException":"An upsert can replace public DNS resolution and is therefore marked destructive, but it remains a write because it can also create or revise one record rather than remove a resource.","idempotency":"client_key","taskMode":"none","execution":{"mode":"direct_only","reason":"A DNS upsert changes externally served public resolution through a policy-gated, versioned provider-intent mutation and client replay identity. It has no safe generic rollback because the prior record value is not returned. Keep it on direct MCP/REST; it is not a programmatic read, code-mode operation, generated SDK call, or CLI command."}},"safety":{"sensitiveOutput":"bounded","receipt":"zero_cost","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"upsert_dns_record","discoveryGroup":"domains","annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"description":"[domains] Create or replace public DNS under Cedar policy, optimistic concurrency, and ledger controls.\n\nUse when: You have an owned domain, its current DNS concurrency version, and one exact A, AAAA, CNAME, TXT, or MX record to create or update under the domain's existing policy.\nDo not use when: You need a programmatic/code-mode/SDK/CLI call, a default priority or version, a non-canonical/foreign DNS owner name, a provider reference, a generic rollback, or a DNS deletion.\nErrors: auth:required, auth:insufficient_scope, auth:not_your_domain, auth:not_your_project, input:invalid_domain, input:invalid_record, input:invalid_justification, input:expected_version_required, input:idempotency_key_required, idempotency:key_reuse, rate:limited, domain:reverification_required, billing:delinquent, billing:disputed, org:frozen, config:not_supported, policy:denied, resource:version_conflict, provider:reconciliation_required, provider:dns_failed, internal:tool_failure, internal:upsert_dns_record_output_invalid"},"rest":{"method":"post","path":"/v1/domains/{domain}/dns","successEncoding":"json_envelope_data"},"idempotency":{"stable":true,"schemaId":"sproutpad.agent-idempotency-key.v1","enforcement":"enforced","mcp":{"location":"input","name":"idempotencyKey","required":true},"rest":{"location":"header","name":"Idempotency-Key","required":true}}}},{"operationId":"verify_architecture","title":"Verify SproutPad's public architecture evidence","description":"Verify the fixed signed public conformance evidence against published keys, build identity, probes, and freshness rules through a bounded verification matrix.","useWhen":["You need an independently computed, bounded answer about whether the fixed anonymous and governed public conformance evidence verifies."],"doNotUseWhen":["You need a raw report, run/build identifier, digest, external link, source error, an arbitrary origin, or mutation guidance; this verifier exposes only the closed verification matrix."],"examples":[{"kind":"positive","title":"Verify the fixed public architecture evidence","expectedOutcome":"Returns two ordered profile verification states and fixed checks without disclosing retained report internals."},{"kind":"negative","title":"Do not use this as a conformance report download","expectedOutcome":"Stops at the bounded matrix; raw reports, IDs, digests, errors, links, and continuation fields are unavailable."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.verify_architecture.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.verify_architecture.output.v2"}},"errors":["input:invalid_request","evidence:unavailable","internal:verify_architecture_output_invalid","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"anonymous","scopes":[],"humanOnly":false,"minimumTrustRung":0},"behavior":{"effect":"read","openWorld":true,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"This anonymous direct-MCP peer independently verifies one fixed public architecture-evidence origin. The bounded REST/CLI observation is the programmatic-read transport; direct MCP retains the same closed result without reports, identifiers, digests, raw errors, links, or continuation aliases."}},"safety":{"sensitiveOutput":"none","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"verify_architecture","discoveryGroup":"governance","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[governance] Verify the fixed signed public conformance evidence against published keys, build identity, probes, and freshness rules through a bounded verification matrix.\n\nUse when: You need an independently computed, bounded answer about whether the fixed anonymous and governed public conformance evidence verifies.\nDo not use when: You need a raw report, run/build identifier, digest, external link, source error, an arbitrary origin, or mutation guidance; this verifier exposes only the closed verification matrix.\nErrors: input:invalid_request, evidence:unavailable, internal:verify_architecture_output_invalid, internal:tool_failure"}}},{"operationId":"verify_sandbox_proof","title":"Verify the reviewed public fake-money sandbox proof","description":"Verify only the fixed reviewed shared sandbox reference proof when it is available; unavailable is not evidence and arbitrary proof lookup is outside this bounded operation.","useWhen":["You need an independently computed, bounded answer about whether the reviewed shared fake-money sandbox proof verifies."],"doNotUseWhen":["You need a caller-selected proof, artifact download, proof identifier, subject, steps, AP2 content, provenance, raw failure, link, or sandbox mutation; this verifier exposes only the fixed closed matrix."],"examples":[{"kind":"positive","title":"Verify the reviewed shared sandbox reference","expectedOutcome":"Returns fixed verification checks and safe labels only after local cryptographic and reference validation."},{"kind":"negative","title":"Do not use this for arbitrary proof lookup","expectedOutcome":"The strict input accepts no proof id or artifact flag and never returns proof internals or generic continuation guidance."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.verify_sandbox_proof.input.v2"},"output":{"state":"complete","schemaId":"sproutpad.verify_sandbox_proof.output.v3"}},"errors":["input:invalid_request","evidence:unavailable","evidence:reference_unavailable","internal:verify_sandbox_proof_output_invalid","internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-23"},"authorization":{"auth":"anonymous","scopes":[],"humanOnly":false,"minimumTrustRung":0},"behavior":{"effect":"read","openWorld":true,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"This anonymous direct-MCP peer verifies only the fixed reviewed sandbox reference. The bounded REST/CLI observation is the programmatic-read transport; caller-selected proof IDs, artifact inclusion, raw AP2/provenance material, source errors, links, and continuation aliases are intentionally absent."}},"safety":{"sensitiveOutput":"none","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"verify_sandbox_proof","discoveryGroup":"governance","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"description":"[governance] Verify only the fixed reviewed shared sandbox reference proof when it is available; unavailable is not evidence and arbitrary proof lookup is outside this bounded operation.\n\nUse when: You need an independently computed, bounded answer about whether the reviewed shared fake-money sandbox proof verifies.\nDo not use when: You need a caller-selected proof, artifact download, proof identifier, subject, steps, AP2 content, provenance, raw failure, link, or sandbox mutation; this verifier exposes only the fixed closed matrix.\nErrors: input:invalid_request, evidence:unavailable, evidence:reference_unavailable, internal:verify_sandbox_proof_output_invalid, internal:tool_failure"}}},{"operationId":"whoami","title":"Who am I? (org, scopes, projects)","description":"Describe the principal, scopes, trust rung, and accessible project identifiers; project details require read, and anonymous callers receive onboarding guidance.","useWhen":["You need to discover the current direct caller's authenticated standing, scopes, and accessible project identifiers before choosing a governed operation."],"doNotUseWhen":["You need a bounded or cacheable code-mode result, another tenant's identity, descriptive project data without read scope, or authority to mutate a project."],"examples":[{"kind":"positive","title":"Discover a narrow key's project identifiers","expectedOutcome":"Returns the caller's org, key, scopes, and project ids while withholding read-gated project details."},{"kind":"negative","title":"Do not use identity discovery as a cached tenant inventory","expectedOutcome":"Call the direct transport only when needed; project inventory is scope-dependent and not a programmatic or cacheable discovery surface."}],"schemas":{"input":{"state":"complete","schemaId":"sproutpad.whoami.input.v1"},"output":{"state":"complete","schemaId":"sproutpad.whoami.output.v1"}},"errors":["internal:tool_failure"],"lifecycle":{"status":"active","since":"2026-07-22"},"authorization":{"auth":"optional","scopes":[],"humanOnly":false,"minimumTrustRung":0},"behavior":{"effect":"read","openWorld":false,"destructive":false,"idempotency":"none","taskMode":"none","execution":{"mode":"direct_only","reason":"The existing identity route returns scope-dependent tenant identity and an unbounded project inventory without a server-enforced serialized-byte cap or cursor. It remains outside programmatic execution and response caching; the reviewed direct SDK/CLI projection makes one explicit request with a client-side output guard."}},"safety":{"sensitiveOutput":"bounded","receipt":"none","undo":"none","cliResponseCache":"forbid"},"bindings":{"mcp":{"toolName":"whoami","discoveryGroup":"onboarding","annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"[onboarding] Describe the principal, scopes, trust rung, and accessible project identifiers; project details require read, and anonymous callers receive onboarding guidance.\n\nUse when: You need to discover the current direct caller's authenticated standing, scopes, and accessible project identifiers before choosing a governed operation.\nDo not use when: You need a bounded or cacheable code-mode result, another tenant's identity, descriptive project data without read scope, or authority to mutate a project.\nErrors: internal:tool_failure"},"rest":{"method":"get","path":"/v1/whoami","successEncoding":"json_envelope_data"},"cli":{"path":["invoke","whoami"],"invocation":"read"}}}]}