Institution-level integration - the MCP server is hosted by DeanDesk and authorized per institution. Once connected, the tools exposed to your AI client can read and write data across the institution and any selected school, subject to the scopes and roles you grant.
How authorization works
The MCP server uses OAuth 2.1 with PKCE and supports dynamic client registration. When you add DeanDesk to an MCP client, the client opens a DeanDesk authorization page in your browser. You sign in with an institution Identity, review the requested scopes, choose a school context when school tools are needed, and approve the client. The tools that appear in your AI client depend on four things:- The scopes you approve
- Your institution role
- Your school role, when a selected school is required
- The environment, because some migration tools are sandbox-only
offline_access, so long-running clients can stay connected without asking you to sign in again.
How to set it up
Use the MCP endpoint provided by DeanDesk support or your institution administrator. For hosted DeanDesk environments, the endpoint is usually:https://fath.ummahspot.com/agent/mcp
1
Add DeanDesk as a remote MCP server
In your MCP client’s settings, create a new server named
DeanDesk and enter the DeanDesk MCP endpoint. Some clients call this a remote server URL, connector URL, or streamable HTTP endpoint.2
Authorize the client
The client opens a browser window to DeanDesk. Sign in with your institution Identity, review the requested scopes, and approve only the access the client needs.
3
Select a school when needed
School-level tools require a selected school context. Choose the school during authorization when you want the client to manage school users, courses, course materials, curricula, rosters, products, assignments, surveys, pages, email drafts, or school settings.
4
Confirm the tools loaded
Return to the MCP client and refresh its tool list if needed. The DeanDesk tools that match your approved scopes and roles should now appear under the DeanDesk server.
Start with read-only scopes when testing a new client. Add write scopes only when the client needs to create, update, delete, or send data.
Available scopes
Scopes control which tools the AI client can call.| Scope | What it grants |
|---|---|
institution:read | Read institution profile, subscription, branding, and school list |
institution:write | Update institution-level settings such as public portal visibility |
membership:read | Read institution membership plans, memberships, and pending imports |
membership:write | Create, update, import, link, or sync institution memberships |
school:read | Read the selected school’s profile and basic setup |
school.settings:read | Read selected school settings, onboarding policies, metadata fields, and custom pages |
school.settings:write | Update selected school settings, onboarding policies, metadata fields, and custom pages |
students:read | Read selected school users, course students, rosters, and roster candidates |
students:write | Import users and add or remove roster students |
courses:read | Read courses, course materials, curricula, course sidebar visibility, and course bulk-edit data |
courses:write | Import, create, or update courses, course enrollments, course sidebar visibility, course materials, and curricula |
assignments:read | Read assignments, quizzes, surveys, submissions, and analytics |
assignments:write | Create, update, or delete assignments, quizzes, and surveys, and draft quizzes for chat review |
calendar:read | Read academic years, terms, and calendar events |
calendar:write | Create, update, or delete academic years, terms, and calendar events |
communications:write | Draft school emails for review and sending |
products:read | Read products, prices, checkout settings, product details, and program enrollments |
products:write | Create, import, update, or add prices to products |
payroll:read | Read payroll settings, staff and course rates, payroll records, and audit history |
payroll:write | Update payroll settings, rates, record statuses, overrides, and ad-hoc hours |
files:read | List school file-library records, read metadata, and create temporary view links |
files:write | Upload files, update file metadata, or delete file-library records |
migration:read | Read migration workspaces |
migration:write | Create or update sandbox migration workspaces |
memory:read | Browse and read institution or school Shared Drive files |
memory:write | Create, overwrite, organize, or delete Shared Drive files and folders |
offline_access | Issue refresh tokens so the client can stay connected without re-authentication |
Available tools
Your client only sees tools allowed by your approved scopes, role, selected school, and environment.Institution and memberships
| Tool | Scope | What it does |
|---|---|---|
get_institution_info | institution:read | Shows institution context, branding, subscription state, public portal visibility settings, and selected school context. |
update_public_portal_settings | institution:write | Updates public institution home page tab visibility and which schools appear publicly. Hiding the Membership or Donate tab also disables those public pages. |
list_institution_schools | institution:read | Lists schools in the institution and shows whether the token can act on them. |
list_membership_plans | membership:read | Lists institution membership plans and term settings. |
list_institution_memberships | membership:read | Lists memberships with filters for status, tier type, payment source, and pagination. |
get_institution_membership | membership:read | Reads one membership record with editable details and metadata. |
update_institution_membership | membership:write | Updates membership status, contact details, spouse/household details, manual dates, payment source, notes, and metadata. |
create_manual_membership | membership:write | Creates a manual membership (with contact and spouse/household details) or queues it when the identity does not exist yet. |
bulk_import_memberships | membership:write | Imports manual memberships in bulk (rows support contact and spouse/household details) and queues unknown identities for follow-up. |
list_pending_membership_imports | membership:read | Lists pending membership imports that still need identity linking or sync resolution. |
link_pending_membership_import | membership:write | Links a pending import record to an identity and creates or reuses the membership. |
sync_pending_membership_imports | membership:write | Runs a sync pass over pending membership imports. |
Migration
| Tool | Scope | What it does |
|---|---|---|
list_migration_workspaces | migration:read | Lists migration workspaces for the institution. |
create_migration_workspace | migration:write | Creates a sandbox migration workspace for an institution or selected school. Sandbox only. |
update_migration_workspace | migration:write | Updates migration staging data, mappings, validation summaries, or status. Sandbox only. |
Shared Drive and documentation
| Tool | Scope | What it does |
|---|---|---|
list_shared_drive_directory | memory:read | Browses institution or school Shared Drive folders and files. |
read_shared_drive_file | memory:read | Reads a Shared Drive file by ID and returns text when available plus a signed download URL. |
write_shared_drive_file | memory:write | Creates or overwrites a text file in the Shared Drive. |
create_shared_drive_folder | memory:write | Creates a Shared Drive folder, including intermediate folders when needed. |
delete_shared_drive_entry | memory:write | Deletes a Shared Drive file or folder. Root institution and school folders cannot be deleted. |
list_docs | No scope required | Lists DeanDesk documentation pages from the configured docs repository. |
read_doc | No scope required | Reads one documentation page by slug, such as finance/stripe. |
search_docs | No scope required | Searches documentation titles, slugs, and content. |
School users, courses, materials, curricula, and rosters
| Tool | Scope | What it does |
|---|---|---|
get_school_info | school:read | Reads the selected school’s profile, hostname, timezone, subscription plan, fixed sales currency, and registration policies. |
get_school_stats | school:read | Reads school usage statistics: storage usage by category and active, inactive, and total student counts. |
list_users | students:read | Lists selected school users with search plus role, adult/child type, active status, grade, roster, course-enrollment, product-purchase, and metadata filters. |
import_users | students:write | Bulk-imports regular user accounts or child student accounts with parent linking and welcome email content. |
list_courses | courses:read | Lists all selected school courses with instructor, assistant, and term details. |
import_courses | courses:write | Bulk-imports courses, including instructors, assistants, terms, schedules, capacity, and tags. |
create_course_from_template | courses:write | Creates a live course from a school course template with optional overrides for title, instructor, assistants, term, dates, schedule, capacity, enrollment code, and copied content. Admin role required. |
get_course_bulk_edit_data | courses:read | Loads the course spreadsheet dataset and valid edit options. |
bulk_edit_courses | courses:write | Bulk creates or updates courses using the same spreadsheet save flow as the web app. |
update_course_sidebar_visibility | courses:write | Shows or hides course sidebar items such as curriculum, assignments, materials, surveys, students, attendance, and gradebook. |
get_course_students | students:read | Lists enrolled students for a course with enrollment IDs, status, sections, and basic profile fields. |
bulk_add_course_students | courses:write | Enrolls existing active student users in a course by student ID, optionally scoped to course section IDs. |
bulk_remove_course_students | courses:write | Removes students from a course by deleting their enrollment records. |
update_course_student_enrollment | courses:write | Updates one enrolled student’s course enrollment status. |
list_course_waitlist | courses:read | Lists waitlist entries for capacity-limited courses with filters for search, course, and product. |
update_course_waitlist_capacity | courses:write | Adjusts or removes a course’s capacity limit. Raising capacity automatically notifies waitlisted users. |
manual_enroll_waitlisted_user | courses:write | Enrolls a waitlisted user directly without payment and clears their waitlist entry. Confirm before use. |
upload_course_image | courses:write | Uploads or replaces a course image from base64-encoded JPEG, PNG, or WebP bytes. |
get_course_image | courses:read | Returns a temporary signed URL for a course image. |
delete_course_image | courses:write | Deletes a course image. |
list_course_materials | courses:read | Lists course materials grouped by course-wide and section-specific groups. |
get_course_material | courses:read | Reads one course material with content and attached file metadata. |
create_course_material | courses:write | Creates one text material or one file material that uses an existing DeanDesk file. |
upload_course_material_file | courses:write | Uploads a small base64-encoded file and creates a file material for a course. |
create_course_material_with_upload | courses:write | Uploads a file from a URL or enabled server-local path and creates a file material for a course in one call. |
update_course_material | courses:write | Updates a course material title, sections, content, file, order, publication state, or availability date. |
delete_course_material | courses:write | Deletes a course material record. The uploaded file record is not deleted automatically. |
reorder_course_materials | courses:write | Replaces the material order for a course. |
preview_youtube_playlist | courses:read | Resolves a YouTube playlist and shows the public videos that would be imported. |
import_youtube_playlist_materials | courses:write | Imports a YouTube playlist as one text material per public video. |
get_course_curriculum | courses:read | Reads the complete course curriculum with units, modules, material references, and assessment references. |
replace_course_curriculum | courses:write | Replaces the entire course curriculum structure. |
create_curriculum_unit | courses:write | Creates a curriculum unit. |
bulk_create_curriculum_units | courses:write | Creates multiple curriculum units in one tool call. |
update_curriculum_unit | courses:write | Updates a curriculum unit. |
delete_curriculum_unit | courses:write | Deletes a curriculum unit and its nested curriculum content. |
reorder_curriculum_units | courses:write | Reorders curriculum units. |
create_curriculum_module | courses:write | Creates a curriculum module inside a unit. |
bulk_create_curriculum_modules | courses:write | Creates multiple curriculum modules or lessons in one tool call. |
update_curriculum_module | courses:write | Updates a curriculum module. |
delete_curriculum_module | courses:write | Deletes a curriculum module from a unit. |
reorder_curriculum_modules | courses:write | Reorders modules within a unit. |
add_curriculum_material | courses:write | Adds an existing course material reference to a unit or module. |
bulk_add_curriculum_materials | courses:write | Adds multiple existing course material references to units or modules in one tool call. |
update_curriculum_material | courses:write | Updates a curriculum material reference, such as order or required status. |
remove_curriculum_material | courses:write | Removes a material reference from the curriculum without deleting the underlying material. |
reorder_curriculum_materials | courses:write | Reorders material references within a unit or module. |
add_curriculum_assessment | courses:write | Adds an existing assignment as an assessment reference in a unit or module. |
bulk_add_curriculum_assessments | courses:write | Adds multiple existing assignments or quizzes as curriculum assessment references in one tool call. |
update_curriculum_assessment | courses:write | Updates a curriculum assessment reference, such as order or required status. |
remove_curriculum_assessment | courses:write | Removes an assessment reference from the curriculum without deleting the assignment. |
reorder_curriculum_assessments | courses:write | Reorders assessment references within a unit or module. |
move_curriculum_item | courses:write | Moves a module, material reference, or assessment reference to another curriculum parent. |
bulk_move_curriculum_items | courses:write | Moves multiple modules, material references, or assessment references in one tool call. |
duplicate_curriculum_item | courses:write | Duplicates a curriculum unit or module with nested curriculum references. |
list_curriculum_map_presets | courses:read | Lists configurable starter structures and their supported options. |
list_curriculum_maps | courses:read | Searches reusable curriculum maps owned by the selected school. |
get_curriculum_map | courses:read | Reads a curriculum map with its arbitrary hierarchy and template summaries. |
create_curriculum_map | courses:write | Creates a school curriculum map, optionally from a configurable starter structure. Admin role required. |
update_curriculum_map | courses:write | Updates curriculum map metadata, version, or status. Admin role required. |
duplicate_curriculum_map | courses:write | Creates an independent copy of a map, hierarchy, and templates. Admin role required. |
archive_curriculum_map | courses:write | Archives a curriculum map without changing live courses created from it. Admin role required. |
create_curriculum_node | courses:write | Adds a typed hierarchy node to a map. Admin role required. |
update_curriculum_node | courses:write | Updates a hierarchy node’s reusable metadata. Admin role required. |
move_curriculum_node | courses:write | Moves a node while enforcing same-map ownership and cycle prevention. Admin role required. |
reorder_curriculum_nodes | courses:write | Reorders a complete set of sibling nodes. Admin role required. |
delete_curriculum_node | courses:write | Deletes a node with explicit recursive-delete or child-move behavior. Admin role required. |
list_course_templates | courses:read | Searches reusable course-template summaries. |
get_course_template | courses:read | Reads one template, optionally including its complete reusable curriculum snapshot. |
create_course_template | courses:write | Creates and places a reusable course template in a curriculum map. Admin role required. |
update_course_template | courses:write | Updates reusable template metadata or curriculum content. Admin role required. |
archive_course_template | courses:write | Archives a template without affecting previously instantiated courses. Admin role required. |
create_course_template_from_course | courses:write | Copies reusable academic content from a live course into a new template. Admin role required. |
instantiate_course_template | courses:write | Creates an independently editable live course snapshot from a template. Admin role required. |
browse_curriculum_library | courses:read | Searches published DeanDesk Library curriculum. |
import_library_curriculum | courses:write | Imports a public map, subtree, or template as an independent school copy. Admin role required. |
build_curriculum_map | courses:write | Builds a complete nested map and its course templates in one workflow; archives the partial map if a step fails. Admin role required. |
list_rosters | students:read | Lists rosters so the client can find roster IDs. |
get_roster | students:read | Reads one roster, including associated courses and current students. |
list_roster_student_candidates | students:read | Finds students that can be added to a roster using roster panel filters. |
bulk_add_roster_students | students:write | Adds existing students to a roster by student ID. |
bulk_remove_roster_students | students:write | Removes students from a roster by student ID. |
list_course_groups | courses:read | Lists course groups (named sets of courses) with their member courses. Teacher or admin role required. |
create_course_group | courses:write | Creates a course group with a unique name and optional member courses. Admin role required. |
update_course_group | courses:write | Updates a course group’s name, description, active status, or full course list. Admin role required. |
delete_course_group | courses:write | Deletes a course group without deleting its courses. Admin role required. |
For large course videos or files, use
create_course_material_with_upload with a public or signed fileUrl. filePath is only for trusted local MCP deployments where MCP_ENABLE_SERVER_FILE_UPLOADS=true; hosted DeanDesk MCP servers cannot read files from an agent’s local disk.File library
| Tool | Scope | What it does |
|---|---|---|
upload_file | files:write | Uploads a base64-encoded file into the selected school’s file library and returns its file ID. |
list_files | files:read | Lists file-library records with optional filename, author, MIME type, tag, category, date, size, and full-text filters. |
get_file_metadata | files:read | Reads metadata for one file-library record. |
get_file_view_link | files:read | Creates a temporary signed URL for viewing a file. |
update_file_metadata | files:write | Updates editable file metadata such as filename, author, tags, and categories. |
delete_file | files:write | Deletes one file-library record and its stored file object. Admin role required. |
School settings and pages
| Tool | Scope | What it does |
|---|---|---|
get_school_config | school.settings:read | Reads branding, display settings, navigation visibility, homepage settings, timezone, and address. |
update_school_config | school.settings:write | Updates selected school configuration fields while leaving omitted fields unchanged. |
get_onboarding_policies | school.settings:read | Reads registration, onboarding, parent-child enrollment, program policy Markdown, application fee, admissions workflow, and application experience settings. |
update_onboarding_policies | school.settings:write | Updates onboarding and registration policies, including program policy Markdown, single-page application layout, terms and conditions, and the post-submission donation prompt. Fee amounts are provided in cents. |
get_metadata_schema | school.settings:read | Reads custom user metadata field definitions. |
add_metadata_field | school.settings:write | Adds a custom user metadata field. |
remove_metadata_field | school.settings:write | Removes a custom user metadata field definition. Existing profile data is not automatically removed. |
list_pages | school.settings:read | Lists custom school pages with metadata, editor paths, and preview paths. |
read_page | school.settings:read | Reads a custom page with full HTML, CSS, blocks, typography, and attributes. |
update_page | school.settings:write | Updates an existing custom page by page ID. |
draft_page | school.settings:write | Creates or replaces a custom HTML/CSS page draft for review and publishing. |
get_page_manager_settings | school.settings:read | Reads Page Manager state: header links, footer links, home page selection, global page theme, and published page candidates. |
update_header_links | school.settings:write | Replaces public header navigation links, including nested dropdowns. |
update_footer_links | school.settings:write | Replaces public footer links and optionally updates footer copyright text. |
configure_home_page | school.settings:write | Sets the public home page to the default, programs landing page, or a published custom page. |
get_global_page_theme | school.settings:read | Reads the global custom page CSS, uploaded global page fonts, and typography selections. |
update_global_page_theme | school.settings:write | Updates global custom page CSS and typography selections using existing uploaded font IDs. |
upload_global_page_theme_font | school.settings:write | Uploads a base64-encoded WOFF2, WOFF, TTF, or OTF font into the global page theme. |
delete_global_page_theme_font | school.settings:write | Deletes an uploaded global page theme font and clears any typography selection that used it. |
Products, discounts, communications, assignments, and surveys
| Tool | Scope | What it does |
|---|---|---|
list_products | products:read | Lists products with IDs, slugs, public URLs, type, status, prices, tuition settings, and course or program associations. |
get_product_details | products:read | Reads a complete single-product view by database ID or URL slug. |
import_products | products:write | Bulk-imports products and creates Stripe products and prices automatically. Price amounts are provided in cents. |
create_product | products:write | Creates one product with full control over product fields, including donation campaign settings such as goal, deadline, donor wall, form headline, intro text, and button color. Add prices separately with add_product_price. |
add_product_price | products:write | Adds a new price to an existing product. Unit amount is provided in dollars. Currency must match the school’s fixed sales currency; omit it to use it automatically. |
update_product | products:write | Updates product fields such as name, description, type, tags, images, metadata (including donation campaign settings), and associations. |
list_product_membership_plans | products:read | Lists active membership plan IDs available for members-only tuition product restrictions. |
bulk_update_products | products:write | Updates product-page bulk edit fields for multiple existing products. |
update_product_price | products:write | Updates editable price details such as nickname, billing start mode, active status, and metadata. |
archive_product_price | products:write | Archives/deactivates an existing product price. |
archive_product | products:write | Archives/deactivates a product and its prices, with optional subscription handling. |
reactivate_product | products:write | Reactivates an archived product and can optionally reactivate its prices. |
list_product_donations | products:read | Lists recent donations for a donation product with pagination. |
list_program_enrollments | products:read | Lists enrollments across one or more products with status, user type, and pagination filters. |
list_discounts | products:read | Lists discounts with IDs, active status, application type, amount, code/rule/sibling/bundle summary, subscription duration, and usage counts. |
get_discount | products:read | Reads one discount’s full configuration, including rules, product/price scope, usage constraints, subscription duration, and metadata. |
get_discount_rule_fields | products:read | Lists user, identity, and derived fields available for rule-based discount conditions. |
create_discount | products:write | Creates code, rule-based, sibling, or bundle discounts with product/price scope and usage constraints. |
update_discount | products:write | Updates an existing discount. Product and price arrays replace the existing scope when provided. |
delete_discount | products:write | Permanently deletes a discount after confirmation. |
validate_discount_code | products:read | Validates a manual discount code for a user and optional product or price. |
check_discount_eligibility | products:read | Checks active rule-based discounts a user qualifies for, optionally scoped to a product or price. |
check_user_discount | products:read | Checks whether a user qualifies for one specific discount and returns the reason. |
calculate_discount | products:read | Calculates the discount amount and final price for a specific discount and price. |
draft_email | communications:write | Drafts an email for admin review, including subject, HTML body, and audience selection. |
list_assignments | assignments:read | Lists assignments for a course with type, due date, points, and submission counts. |
create_assignment | assignments:write | Creates a standard non-quiz assignment for a course and notifies enrolled students. |
update_assignment | assignments:write | Updates an existing standard assignment. |
delete_assignment | assignments:write | Deletes an assignment or quiz, including its submissions and curriculum assessment references. |
create_quiz_assignment | assignments:write | Creates and saves a quiz assignment directly, including questions, options, correct answers, quiz settings, and optional multiple submissions. |
bulk_create_quiz_assignments | assignments:write | Creates multiple quiz assignments in one tool call and can place each quiz into a curriculum unit or module. |
update_quiz_assignment | assignments:write | Updates an existing quiz assignment directly, including multiple-submission settings, and recalculates total points when questions change. |
draft_quiz | assignments:write | Prepares quiz data for the DeanDesk chat interactive editor; non-chat MCP clients should use the direct quiz tools. |
get_assignment_submissions | assignments:read | Reads staff-facing assignment or quiz submissions with class performance, question analytics, and per-student attempt history. |
list_surveys | assignments:read | Lists course surveys with due dates, question counts, anonymous-response setting, and multiple-submission setting. |
get_survey | assignments:read | Reads one survey and its questions; teachers and admins also receive staff-facing analytics when available. |
create_survey | assignments:write | Creates a course survey with multiple choice, rating, or short-answer questions and no grades or correct answers. |
update_survey | assignments:write | Updates survey metadata, questions, Keep Responses Anonymous, due date, section, or multiple-submission settings. |
get_survey_analytics | assignments:read | Reads survey submissions and analytics, including class aggregates, rating distributions, answer summaries, and per-user timelines. |
Academic calendar
| Tool | Scope | What it does |
|---|---|---|
list_academic_years | calendar:read | Lists academic years with names and date ranges, with an optional name filter. |
create_academic_year | calendar:write | Creates an academic year with a name, start date, and end date. Teacher or admin role required. |
update_academic_year | calendar:write | Updates an academic year’s name or date range. Teacher or admin role required. |
delete_academic_year | calendar:write | Deletes an academic year, cascading to its terms and related course events. Admin role required. |
list_terms | calendar:read | Lists terms, optionally filtered by academic year or name. |
create_term | calendar:write | Creates a term inside an academic year’s date range. Teacher or admin role required. |
update_term | calendar:write | Updates a term’s name or dates within its academic year. Teacher or admin role required. |
delete_term | calendar:write | Deletes a term, cascading to events for courses in that term. Admin role required. |
list_calendar_events | calendar:read | Lists calendar events with date, type, course, calendar, visibility, and free-text filters. |
create_calendar_event | calendar:write | Creates a calendar event, including all-day events like holidays. Teacher or admin role required. |
update_calendar_event | calendar:write | Updates an existing calendar event’s details, dates, or attendees. Teacher or admin role required. |
delete_calendar_event | calendar:write | Deletes a calendar event. Teacher or admin role required. |
Time and payroll
Payroll tools require an institution Identity in addition to the selected school: Super Admin identities can read and write, and Admin Read-Only identities can read. School-only sessions do not see these tools. See Time and payroll.
| Tool | Scope | What it does |
|---|---|---|
get_payroll_settings | payroll:read | Reads pay period type, processed day, default teacher attendance, and the additional-hours logging setting. |
update_payroll_settings | payroll:write | Updates payroll settings. Changes affect how future pay periods are computed. |
list_payroll_rates | payroll:read | Lists staff rates: hourly rate and per-period pay per staff member and role. |
update_payroll_rates | payroll:write | Updates a staff member’s hourly rate and per-period pay for a role. |
list_payroll_course_rates | payroll:read | Lists per-course pay settings, including unpaid courses and per-staff overrides. |
update_payroll_course_rate | payroll:write | Marks a course paid or unpaid and sets per-staff overrides for hours, rate, and notes. |
list_payroll_records | payroll:read | Lists payroll records for a pay period with status, hours, and gross pay per staff member. |
update_payroll_record_status | payroll:write | Verifies or marks paid one or more payroll records, with an optional note. |
update_payroll_record_override | payroll:write | Overrides a record’s hours, rate, or total with a note. Omitted values reset to calculated amounts. |
add_payroll_ad_hoc_hours | payroll:write | Adds ad-hoc hours to a payroll record: date, hours, optional rate, and description. |
get_payroll_record_audit | payroll:read | Reads the audit history for a payroll record. |
Payroll write tools change staff compensation data. Review the intended change carefully before approving a write.
Best practices
- Grant the minimum scopes needed for the task.
- Use a dedicated client name for each MCP client so you can revoke clients independently.
- Review write actions in your institution audit history.
- Read existing records before asking the client to update or delete them.
MCP tools that send communications or modify DeanDesk data act on your institution’s behalf. Treat client authorizations like API keys and grant write scopes only to clients you trust.