Scope catalog
Scopes gate ctx.tools.call(...) access. Declare every scope your app
needs in manifest.required_scopes; the tenant must grant them on
install. The platform enforces scope at every call site — your code
only runs against tenants where the grant exists.
The list below is the single source of truth from
packages/core/src/apps/scope_registry.py. Tool names are case-sensitive and must match exactly when you call them viactx.tools.call("name", ...).
Mail (Microsoft Graph + triage)
Section titled “Mail (Microsoft Graph + triage)”| Scope | Tools |
|---|---|
mail.read | email_search, email_read, email_check_inbox, email_get_attachments, email_triage_run |
mail.send | email_send, email_reply, email_draft_create, email_draft_reply, email_draft_send |
mail.manage | reserved — no tools wired today |
Calendar
Section titled “Calendar”| Scope | Tools |
|---|---|
calendar.read | calendar_list_events, calendar_get_event |
calendar.write | calendar_create_event, calendar_update_event, calendar_delete_event |
Files & artifacts (platform-internal)
Section titled “Files & artifacts (platform-internal)”| Scope | Tools |
|---|---|
files.read | deliver_file, linkworld_file_download |
files.write | user_app_dashboard_create |
files.share | create_public_share_link |
Note:
deliver_fileis a platform built-in (handled intool_handlers.py, not the skill bridge). It’s listed here so the scope check still applies the day it migrates through the bridge.
App records — chat-built apps only
Section titled “App records — chat-built apps only”read_app_records, write_app_records, and update_app_record exist
only for the chat-built no-code apps (linkworld_user_apps —
created in chat, persisted as a UI schema + records table per app).
They look up the app in linkworld_user_apps; calling them from a
deployed marketplace app returns
{"code": "marketplace_app_wrong_tool", "use_instead": "ctx.kv"} and
never writes anything.
Marketplace apps must use ctx.kv for app-private state —
see the KV reference. Records keyed as
{record_type}:{uuid} with ctx.kv.list(prefix="record_type:", ...)
gives back the same shape read_app_records would have returned for
the chat-built case. The inbox-manager example
is the canonical ~900-LOC reference.
linkworld lint rule LWP006 flags any
ctx.tools.call("read_app_records"|"write_app_records"|"update_app_record")
in a marketplace-app codebase (manifest with app_id not declared as
user_app: true) and points at ctx.kv as the fix.
| Scope | Tools |
|---|---|
whatsapp.send | whatsapp_send, whatsapp_send_document |
Microsoft Teams
Section titled “Microsoft Teams”Posts and sends use the tenant’s delegated M365 OAuth — messages appear
as the authenticated user (no bot identity). An app that needs to
broadcast to a channel should declare teams.send and document the
public-visibility risk to the installing tenant.
| Scope | Tools |
|---|---|
teams.read | teams_list_teams, teams_list_channels, teams_list_chats, teams_list_chat_messages, teams_find_user |
teams.send | teams_send_chat_message, teams_post_channel_message, teams_reply_to_channel_message |
Platform-mediated. Partner apps cannot call api.linkedin.com
directly — the platform resolves the tenant’s stored OAuth credential
and calls on the app’s behalf. A read-only app declares only
linkedin.read and the bridge blocks any attempt to post.
| Scope | Tools |
|---|---|
linkedin.read | linkedin_list_comments, linkedin_get_analytics |
linkedin.write | linkedin_post, linkedin_reply_comment |
No general search API. LinkedIn does not expose a public people/post search endpoint. If your app needs lead discovery, use a dedicated data provider (ProxyCurl, People Data Labs, etc.) via its own scope-less API key — there is no platform tool for this today.
Documents — render & draft
Section titled “Documents — render & draft”| Scope | Tools |
|---|---|
document.render | render_document, create_pdf, create_letter, create_report, create_document, create_gutachten, business_render_pdf, excel_create_report |
document.draft | business_create_document, business_update_document, business_get_document, business_list_documents, business_list_clients, business_lookup_price, business_transition_document, business_convert_quote_to_invoice, doc_templates_install_defaults |
pricelist.read | business_lookup_price |
pricelist.readvsdocument.draft.business_lookup_priceis covered by both. Apps that ONLY do pricing lookups (e.g. an inbox classifier checking whether a quote request is in scope) should grant the narrowerpricelist.read— declaringdocument.draftwould also grant write privileges they don’t need.
ERP — Lexoffice + Odoo + Business Central
Section titled “ERP — Lexoffice + Odoo + Business Central”erp.read covers all three providers’ read paths; erp.write covers
all three write paths. Pick the provider whose tools you call — the
platform routes per-tenant connection state automatically.
erp.read
Section titled “erp.read”Lexoffice: lexoffice_list_contacts, lexoffice_get_contact,
lexoffice_list_invoices, lexoffice_get_invoice,
lexoffice_get_invoice_pdf, lexoffice_list_quotations,
lexoffice_get_quotation_pdf, lexoffice_get_payments
Odoo: odoo_search_read, odoo_get_record, odoo_list_models,
odoo_list_reports, odoo_get_report
Business Central: bc_query, bc_get_record, bc_list_companies,
bc_download_document
erp.write
Section titled “erp.write”Lexoffice: lexoffice_create_contact, lexoffice_create_invoice,
lexoffice_create_quotation
Odoo: odoo_create_record, odoo_update_record,
odoo_delete_record, odoo_copy_record, odoo_execute_action
Business Central: bc_create_record, bc_update_record,
bc_delete_record
Vision / damage assessment
Section titled “Vision / damage assessment”| Scope | Tools |
|---|---|
vision.analyze | damage_analyze_photos, damage_estimate_cost, damage_generate_report |
Technical drawings (DXF + PDF)
Section titled “Technical drawings (DXF + PDF)”Used by quote-drafting flows that need to read a customer-supplied
technical drawing — operations, title block, dimensions. The dxf_render
tool produces a derivative PNG and is part of the same analysis flow,
so it lives under the same scope.
| Scope | Tools |
|---|---|
drawings.analyze | dxf_analyze, dxf_find_operations, dxf_measure, dxf_render, dxf_interpret |
| Scope | Tools |
|---|---|
voice.transcribe | reserved — voice transcription happens via the realtime relay today, not the skill bridge |
Tasks (platform scheduler)
Section titled “Tasks (platform scheduler)”| Scope | Tools |
|---|---|
tasks.create | task_create, project_goal_create, project_goal_start |
Platform built-ins (no scope required)
Section titled “Platform built-ins (no scope required)”These tools are available to every installed app without declaring
anything in required_scopes. They cover storage and personalisation
primitives that aren’t tenant-data-bearing — apps need them to function
at all, so the platform grants them by default.
| Tool family | Tools | Reference |
|---|---|---|
| App KV store | app_kv_set, app_kv_get, app_kv_list, app_kv_delete | KV reference |
| App RAG store | rag_query, rag_list_documents, rag_get_document, rag_upsert_document, rag_delete_document | RAG reference |
| User profile | user_profile_get | see best-practices |
If you see a
ToolCallErrorwithdecision: "scope_denied"on one of these, that’s a platform bug — please file an issue. Built-ins should never trip the scope gate.
Tools that look callable but aren’t
Section titled “Tools that look callable but aren’t”Some tools surface in lint warnings or autocomplete because they
exist inside the platform — but they are not callable from
ctx.tools.call(...):
web_search— a Claude-native LLM tool. Available to the LLM inside your Chat handler’s reasoning loop (when the model decides to use it), not to your app code directly. Don’t declare it as a scope; don’t try to call it.web_browser— a built-in compute skill used internally by the assistant for headless-browser flows. Same shape: LLM-only.assistant_execute_code— the assistant’s own code-execution tool. Same shape: LLM-only.
If your lead-research, market-scan, or scraping flow needs web data, bring your own data provider (Brave Search, SerpAPI, ProxyCurl, etc.) and call it from your handler with your own API key — there is no platform scope for this today.
Pattern: scope-deny graceful handling
Section titled “Pattern: scope-deny graceful handling”A tenant might install your app without granting every scope. Code defensively:
import { ToolCallError } from '@linkworld_ai/sdk'
try { await ctx.tools.call('odoo_create_record', { model, values })} catch (err) { if (err instanceof ToolCallError && err.decision === 'scope_denied') { console.log('[my-app] erp.write not granted; skipping ERP write') return } throw err}from linkworld_sdk import ToolCallError
try: await ctx.tools.call("odoo_create_record", model=model, values=values)except ToolCallError as exc: if exc.decision == "scope_denied": return raiseAdding a new scope or tool
Section titled “Adding a new scope or tool”Scopes and tool names ship with the platform — partners pick from the list above and cannot invent their own. Two common cases:
- The tool exists but no scope grants it. That’s a platform bug: the tool is silently un-gated. File an issue.
- The tool doesn’t exist yet. Open an issue at
github.com/linkworld/linkworld
describing the integration and use case. New scopes/tools get added
to
packages/core/src/apps/scope_registry.pyas part of a platform release.
For workloads the platform doesn’t cover today (web search, generic HTTP scraping, niche SaaS APIs), call the third-party service from your own handler with your own API key — that’s fully supported and needs no platform scope.