[{"data":1,"prerenderedAt":863},["ShallowReactive",2],{"navigation":3,"page:\u002Fai-in-your-app":45},{"title":4,"links":5},"API reference",[6,9,12,15,18,21,24,27,30,33,36,39,42],{"title":7,"href":8},"Accounts","\u002Freference\u002Faccounts",{"title":10,"href":11},"Connect","\u002Freference\u002Fconnect",{"title":13,"href":14},"Calendars","\u002Freference\u002Fcalendars",{"title":16,"href":17},"Events","\u002Freference\u002Fevents",{"title":19,"href":20},"Availability","\u002Freference\u002Favailability",{"title":22,"href":23},"Meetings","\u002Freference\u002Fmeetings",{"title":25,"href":26},"Invitations","\u002Freference\u002Finvitations",{"title":28,"href":29},"Webhooks","\u002Freference\u002Fwebhooks",{"title":31,"href":32},"Metadata","\u002Freference\u002Fmetadata",{"title":34,"href":35},"Keys","\u002Freference\u002Fkeys",{"title":37,"href":38},"Sandbox","\u002Freference\u002Fsandbox",{"title":40,"href":41},"Usage","\u002Freference\u002Fusage",{"title":43,"href":44},"Logs","\u002Freference\u002Flogs",{"kind":46,"title":47,"description":48,"sections":49,"doc":77},"guide","AI in your app","Give the AI assistant in your app calendar tools for your users' Google, Outlook and Zoom calendars. Each person's assistant has access to their calendars only, and asks them before it changes anything.",[50,53,56,59,62,65,68,71,74],{"id":51,"title":52},"how-it-works","How it works",{"id":54,"title":55},"choose-your-framework","Choose your framework",{"id":57,"title":58},"the-tools","The tools",{"id":60,"title":61},"one-persons-calendars","One person's calendars",{"id":63,"title":64},"changes-wait-for-approval","Changes wait for approval",{"id":66,"title":67},"offer-fewer-tools","Offer fewer tools",{"id":69,"title":70},"when-a-tool-fails","When a tool fails",{"id":72,"title":73},"try-it-in-the-sandbox","Try it in the sandbox",{"id":75,"title":76},"go-live","Go live",{"id":78,"title":47,"body":79,"description":48,"extension":854,"hero":855,"meta":856,"navigation":857,"path":858,"rawbody":859,"sections":49,"seo":860,"stem":85,"v1":861,"__hash__":862},"docs\u002Fai-in-your-app.md",{"type":80,"value":81,"toc":842},"minimark",[82,86,98,107,110,131,134,136,139,176,179,204,210,212,368,371,373,379,405,411,414,423,433,437,440,443,475,482,484,487,495,516,522,524,527,629,662,664,667,764,774,776,784,812,814,835,838],[83,84,47],"h1",{"id":85},"ai-in-your-app",[87,88,89],"lead",{},[90,91,92,93,97],"p",{},"If your app has an AI assistant, ",[94,95,96],"code",{},"@pyas\u002Fai-tools"," gives it calendar tools. It can see what's on someone's week, find a time when they're free, and book, move or cancel a meeting. Each person's assistant works only with their own calendars, and it asks them before it changes anything.",[90,99,100,101,106],{},"This page is about the assistant inside your product, used by your customers. To use Pyas yourself, from ChatGPT, Claude or your code editor, see ",[102,103,105],"a",{"href":104},"\u002Fai-tools","AI tools",".",[108,109,52],"h2",{"id":51},[111,112,113,125,128],"ol",{},[114,115,116,117,121,122,106],"li",{},"Your users connect their Google or Outlook calendars with ",[102,118,120],{"href":119},"\u002Fconnect","PyasConnect",". Pyas saves each account under your own ID for that user, the ",[94,123,124],{},"subjectId",[114,126,127],{},"On your server, you give the model Pyas's tools for the signed-in user. The tools have access to that user's accounts only.",[114,129,130],{},"The model reads the calendars when it needs to. When it wants to change something, your app shows the person the change, and the change runs only if they approve it.",[90,132,133],{},"You bring the model. Pyas provides the calendar data and the actions, and your API key stays on your server.",[108,135,55],{"id":54},[90,137,138],{},"Each guide has the complete code for a chat that books meetings, tested with a real model:",[140,141,142,152,159,166],"ul",{},[114,143,144,148,149],{},[102,145,147],{"href":146},"\u002Fvercel-ai-sdk","Vercel AI SDK",": a Next.js chat with ",[94,150,151],{},"useChat",[114,153,154,158],{},[102,155,157],{"href":156},"\u002Fopenai","OpenAI",": Chat Completions or the Responses API",[114,160,161,165],{},[102,162,164],{"href":163},"\u002Fclaude-api","Claude API",": the Messages API",[114,167,168,172,173],{},[102,169,171],{"href":170},"\u002Flangchain","LangChain",": an agent made with ",[94,174,175],{},"createAgent",[90,177,178],{},"Install the package in your server code:",[180,181,186],"pre",{"className":182,"code":183,"language":184,"meta":185,"style":185},"language-bash shiki shiki-themes css-variables","npm install @pyas\u002Fai-tools\n","bash","",[94,187,188],{"__ignoreMap":185},[189,190,193,197,201],"span",{"class":191,"line":192},"line",1,[189,194,196],{"class":195},"s1IGz","npm",[189,198,200],{"class":199},"sTB4W"," install",[189,202,203],{"class":199}," @pyas\u002Fai-tools\n",[90,205,206,207,106],{},"The tools read your key from ",[94,208,209],{},"PYAS_API_KEY",[108,211,58],{"id":57},[213,214,215,231],"table",{},[216,217,218],"thead",{},[219,220,221,225,228],"tr",{},[222,223,224],"th",{},"Tool",[222,226,227],{},"What it does",[222,229,230],{},"Asks the person first",[232,233,234,247,259,271,283,295,307,319,332,344,356],"tbody",{},[219,235,236,242,245],{},[237,238,239],"td",{},[94,240,241],{},"list_accounts",[237,243,244],{},"Lists the person's connected calendars",[237,246],{},[219,248,249,254,257],{},[237,250,251],{},[94,252,253],{},"account_status",[237,255,256],{},"Checks whether a calendar is working",[237,258],{},[219,260,261,266,269],{},[237,262,263],{},[94,264,265],{},"list_calendars",[237,267,268],{},"Lists the calendars in an account",[237,270],{},[219,272,273,278,281],{},[237,274,275],{},[94,276,277],{},"list_events",[237,279,280],{},"Lists events between two times",[237,282],{},[219,284,285,290,293],{},[237,286,287],{},[94,288,289],{},"get_event",[237,291,292],{},"Gets one event with all its details",[237,294],{},[219,296,297,302,305],{},[237,298,299],{},[94,300,301],{},"find_meeting_times",[237,303,304],{},"Finds times when everyone is free",[237,306],{},[219,308,309,314,317],{},[237,310,311],{},[94,312,313],{},"get_free_busy",[237,315,316],{},"Gets busy times",[237,318],{},[219,320,321,326,329],{},[237,322,323],{},[94,324,325],{},"create_event",[237,327,328],{},"Creates an event, with a Meet, Teams or Zoom link if asked",[237,330,331],{},"Yes",[219,333,334,339,342],{},[237,335,336],{},[94,337,338],{},"update_event",[237,340,341],{},"Changes an event, or adds or removes its video meeting",[237,343,331],{},[219,345,346,351,354],{},[237,347,348],{},[94,349,350],{},"delete_event",[237,352,353],{},"Deletes an event",[237,355,331],{},[219,357,358,363,366],{},[237,359,360],{},[94,361,362],{},"respond_to_event",[237,364,365],{},"Accepts or declines an invitation",[237,367,331],{},[90,369,370],{},"Events come back with their local times, so the model tells people the time where the event is.",[108,372,61],{"id":60},[90,374,375,376,378],{},"Pass the signed-in person's ID as ",[94,377,124],{},":",[180,380,384],{"className":381,"code":382,"language":383,"meta":185,"style":185},"language-ts shiki shiki-themes css-variables","pyasTools({ subjectId: user.id })\n","ts",[94,385,386],{"__ignoreMap":185},[189,387,388,391,395,398,402],{"class":191,"line":192},[189,389,390],{"class":195},"pyasTools",[189,392,394],{"class":393},"sqSWz","({ subjectId",[189,396,378],{"class":397},"sag7P",[189,399,401],{"class":400},"sXULy"," user",[189,403,404],{"class":393},".id })\n",[90,406,407,408,106],{},"The tools then have access to that person's accounts only. The model can't choose another person. A request for anyone else's account is refused before it reaches Pyas, with the code ",[94,409,410],{},"TOOL_ACCOUNT_NOT_ALLOWED",[90,412,413],{},"Take the ID from your own sign-in, on your server. Never take it from the request body or from what the model says.",[90,415,416,418,419,422],{},[94,417,124],{}," is required. Without it, or with an empty one (a session with no user), building the tools throws an error instead of quietly giving the model every user's calendars. For an agent that works for you rather than for one of your users, such as an internal assistant, pass ",[94,420,421],{},"allAccounts: true"," to reach every account in the app. Never use it for a chat your users open.",[90,424,425,426,429,430,432],{},"When a change waits for approval, the tools also return the calendar it lands on (",[94,427,428],{},"calendar",": its email, provider and ",[94,431,124],{},"). Show it on the approval card, so someone with several calendars sees which one.",[108,434,436],{"id":435},"which-calendar","Which calendar",[90,438,439],{},"People often connect more than one calendar, such as work Google and personal Outlook. When they ask for a new event without saying where, the model asks them which calendar to use.",[90,441,442],{},"If your app lets people choose a default calendar, pass it on each request as a hint:",[180,444,446],{"className":381,"code":445,"language":383,"meta":185,"style":185},"pyasTools({ subjectId: user.id, defaultAccountId: user.defaultCalendarId })\n",[94,447,448],{"__ignoreMap":185},[189,449,450,452,454,456,458,461,465,468,470,472],{"class":191,"line":192},[189,451,390],{"class":195},[189,453,394],{"class":393},[189,455,378],{"class":397},[189,457,401],{"class":400},[189,459,460],{"class":393},".id",[189,462,464],{"class":463},"sEEt9",",",[189,466,467],{"class":393}," defaultAccountId",[189,469,378],{"class":397},[189,471,401],{"class":400},[189,473,474],{"class":393},".defaultCalendarId })\n",[90,476,477,478,481],{},"Pyas doesn't store it: keep it with the user in your own database. With it, the model uses that calendar without asking unless the person names another, and it's checked like any other account, so one that isn't the person's is refused. Empty or ",[94,479,480],{},"null"," means no default.",[108,483,64],{"id":63},[90,485,486],{},"A tool that changes a calendar doesn't run when the model asks for it. Your app shows the person the change, and they approve or decline it:",[488,489],"screenshot",{"alt":490,"caption":491,"height":492,"src":493,"width":494},"A chat in an app. The person asked to move their 1:1 with Sarah to 4:30 PM. A card shows the event's current time and the new time, with Approve and Decline buttons.","An approval in a chat built with the Vercel AI SDK guide. The event's title and current time come from Pyas.",399,"\u002Fimages\u002Fapproval-in-your-app.png",660,[140,496,497,504,510],{},[114,498,499,503],{},[500,501,502],"strong",{},"What the person sees comes from Pyas."," For a change to an existing event, you get the event as it is now in Pyas, not as the model describes it. Show it. If text in an invitation tricks the model into picking another event, the person sees which event it really is.",[114,505,506,509],{},[500,507,508],{},"A change that can never run isn't put to the person."," A change to someone else's account, or to an event that doesn't exist, is refused immediately, and the model is told why.",[114,511,512,515],{},[500,513,514],{},"A declined change is never sent to Pyas."," The model is told that the person declined, and it leaves the calendar as it is.",[90,517,518,519,106],{},"Each guide shows how this works in its framework. If your app already asks the person before a change, turn this off with ",[94,520,521],{},"requireApproval: false",[108,523,67],{"id":66},[90,525,526],{},"Give the assistant only what it needs:",[180,528,530],{"className":381,"code":529,"language":383,"meta":185,"style":185},"pyasTools({ subjectId: user.id, readOnly: true })                   \u002F\u002F reads only: nothing can change\npyasTools({ subjectId: user.id, scope: ['events', 'availability'] }) \u002F\u002F events and free times only\npyasTools({ subjectId: user.id, scope: ['events:read'] })            \u002F\u002F list and get events only\n",[94,531,532,561,599],{"__ignoreMap":185},[189,533,534,536,538,540,542,544,546,549,551,554,557],{"class":191,"line":192},[189,535,390],{"class":195},[189,537,394],{"class":393},[189,539,378],{"class":397},[189,541,401],{"class":400},[189,543,460],{"class":393},[189,545,464],{"class":463},[189,547,548],{"class":393}," readOnly",[189,550,378],{"class":397},[189,552,553],{"class":400}," true",[189,555,556],{"class":393}," })                   ",[189,558,560],{"class":559},"ssgCD","\u002F\u002F reads only: nothing can change\n",[189,562,564,566,568,570,572,574,576,579,581,584,588,590,593,596],{"class":191,"line":563},2,[189,565,390],{"class":195},[189,567,394],{"class":393},[189,569,378],{"class":397},[189,571,401],{"class":400},[189,573,460],{"class":393},[189,575,464],{"class":463},[189,577,578],{"class":393}," scope",[189,580,378],{"class":397},[189,582,583],{"class":393}," [",[189,585,587],{"class":586},"sk1Au","'events'",[189,589,464],{"class":463},[189,591,592],{"class":586}," 'availability'",[189,594,595],{"class":393},"] }) ",[189,597,598],{"class":559},"\u002F\u002F events and free times only\n",[189,600,602,604,606,608,610,612,614,616,618,620,623,626],{"class":191,"line":601},3,[189,603,390],{"class":195},[189,605,394],{"class":393},[189,607,378],{"class":397},[189,609,401],{"class":400},[189,611,460],{"class":393},[189,613,464],{"class":463},[189,615,578],{"class":393},[189,617,378],{"class":397},[189,619,583],{"class":393},[189,621,622],{"class":586},"'events:read'",[189,624,625],{"class":393},"] })            ",[189,627,628],{"class":559},"\u002F\u002F list and get events only\n",[90,630,631,632,635,636,635,639,642,643,646,647,650,651,654,655,642,658,661],{},"The scopes are ",[94,633,634],{},"accounts",", ",[94,637,638],{},"calendars",[94,640,641],{},"events"," and ",[94,644,645],{},"availability",", each alone or with ",[94,648,649],{},":read"," or ",[94,652,653],{},":write",". ",[94,656,657],{},"read",[94,659,660],{},"write"," alone apply to all four. A scope that doesn't exist throws an error, so a typo never gives the assistant more than you meant.",[108,663,70],{"id":69},[90,665,666],{},"A tool never throws an error at the model. It returns the error with its code, a hint and the request's trace ID, so the model can fix its request or tell the person what to do:",[180,668,672],{"className":669,"code":670,"language":671,"meta":185,"style":185},"language-json shiki shiki-themes css-variables","{\n  \"error\": {\n    \"code\": \"RESOURCE_NOT_FOUND\",\n    \"message\": \"The calendar or event was not found.\",\n    \"hint\": \"Check the ID. The item may have been deleted, or it may not be visible to this account.\",\n    \"docs\": \"https:\u002F\u002Fdeveloper.pyas.io\u002Ferrors\u002FRESOURCE_NOT_FOUND\",\n    \"traceId\": \"trc_...\"\n  }\n}\n","json",[94,673,674,679,689,702,715,728,741,752,758],{"__ignoreMap":185},[189,675,676],{"class":191,"line":192},[189,677,678],{"class":393},"{\n",[189,680,681,684,686],{"class":191,"line":563},[189,682,683],{"class":397},"  \"error\"",[189,685,378],{"class":463},[189,687,688],{"class":393}," {\n",[189,690,691,694,696,699],{"class":191,"line":601},[189,692,693],{"class":397},"    \"code\"",[189,695,378],{"class":463},[189,697,698],{"class":586}," \"RESOURCE_NOT_FOUND\"",[189,700,701],{"class":463},",\n",[189,703,705,708,710,713],{"class":191,"line":704},4,[189,706,707],{"class":397},"    \"message\"",[189,709,378],{"class":463},[189,711,712],{"class":586}," \"The calendar or event was not found.\"",[189,714,701],{"class":463},[189,716,718,721,723,726],{"class":191,"line":717},5,[189,719,720],{"class":397},"    \"hint\"",[189,722,378],{"class":463},[189,724,725],{"class":586}," \"Check the ID. The item may have been deleted, or it may not be visible to this account.\"",[189,727,701],{"class":463},[189,729,731,734,736,739],{"class":191,"line":730},6,[189,732,733],{"class":397},"    \"docs\"",[189,735,378],{"class":463},[189,737,738],{"class":586}," \"https:\u002F\u002Fdeveloper.pyas.io\u002Ferrors\u002FRESOURCE_NOT_FOUND\"",[189,740,701],{"class":463},[189,742,744,747,749],{"class":191,"line":743},7,[189,745,746],{"class":397},"    \"traceId\"",[189,748,378],{"class":463},[189,750,751],{"class":586}," \"trc_...\"\n",[189,753,755],{"class":191,"line":754},8,[189,756,757],{"class":393},"  }\n",[189,759,761],{"class":191,"line":760},9,[189,762,763],{"class":393},"}\n",[90,765,766,767,769,770,773],{},"Every request the tools make is on the ",[500,768,43],{}," page in the dashboard, marked ",[500,771,772],{},"By @pyas\u002Fai-tools",". To see what a request did, open its trace ID there.",[108,775,73],{"id":72},[90,777,778,779,783],{},"Start with a test key. A test key works only in your app's ",[102,780,782],{"href":781},"\u002Fsandbox","sandbox",", where sample people have a week of events, and nothing reaches a real calendar.",[111,785,786,803,809],{},[114,787,788,789,792,793,799,800,802],{},"Copy a test key from ",[500,790,791],{},"API keys"," in the ",[102,794,798],{"href":795,"rel":796},"https:\u002F\u002Fapp.pyas.io",[797],"nofollow","dashboard",", and set it as ",[94,801,209],{}," on your server.",[114,804,805,806,106],{},"Use the sandbox person Avery Brooks as the signed-in user: ",[94,807,808],{},"subjectId: 'sandbox_user_1'",[114,810,811],{},"Ask: \"What's on my calendar tomorrow?\", then \"Book a 30 minute meeting tomorrow afternoon when I'm free.\"",[108,813,76],{"id":75},[111,815,816,824,830],{},[114,817,818,819,821,822,106],{},"Add ",[102,820,120],{"href":119}," to your app, so people can connect their calendars. Save each account under your user's ID as ",[94,823,124],{},[114,825,826,827,829],{},"Pass the signed-in user's ID as ",[94,828,124],{},", in place of the sandbox person.",[114,831,832,833,802],{},"Create a live key in the dashboard, and set it as ",[94,834,209],{},[90,836,837],{},"With a live key, the assistant reads and changes real calendars, and every change still waits for the person's approval.",[839,840,841],"style",{},"html pre.shiki code .s1IGz, html code.shiki .s1IGz{--shiki-default:var(--shiki-token-function)}html pre.shiki code .sTB4W, html code.shiki .sTB4W{--shiki-default:var(--shiki-token-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sqSWz, html code.shiki .sqSWz{--shiki-default:var(--shiki-foreground)}html pre.shiki code .sag7P, html code.shiki .sag7P{--shiki-default:var(--shiki-token-keyword)}html pre.shiki code .sXULy, html code.shiki .sXULy{--shiki-default:var(--shiki-token-constant)}html pre.shiki code .sEEt9, html code.shiki .sEEt9{--shiki-default:var(--shiki-token-punctuation)}html pre.shiki code .ssgCD, html code.shiki .ssgCD{--shiki-default:var(--shiki-token-comment)}html pre.shiki code .sk1Au, html code.shiki .sk1Au{--shiki-default:var(--shiki-token-string-expression)}",{"title":185,"searchDepth":601,"depth":601,"links":843},[844,845,846,847,848,849,850,851,852,853],{"id":51,"depth":563,"text":52},{"id":54,"depth":563,"text":55},{"id":57,"depth":563,"text":58},{"id":60,"depth":563,"text":61},{"id":435,"depth":563,"text":436},{"id":63,"depth":563,"text":64},{"id":66,"depth":563,"text":67},{"id":69,"depth":563,"text":70},{"id":72,"depth":563,"text":73},{"id":75,"depth":563,"text":76},"md",false,{},true,"\u002Fai-in-your-app","---\ntitle: AI in your app\ndescription: Give the AI assistant in your app calendar tools for your users' Google, Outlook and Zoom calendars. Each person's assistant has access to their calendars only, and asks them before it changes anything.\nsections:\n  - { id: how-it-works, title: How it works }\n  - { id: choose-your-framework, title: Choose your framework }\n  - { id: the-tools, title: The tools }\n  - { id: one-persons-calendars, title: One person's calendars }\n  - { id: changes-wait-for-approval, title: Changes wait for approval }\n  - { id: offer-fewer-tools, title: Offer fewer tools }\n  - { id: when-a-tool-fails, title: When a tool fails }\n  - { id: try-it-in-the-sandbox, title: Try it in the sandbox }\n  - { id: go-live, title: Go live }\n---\n\n# AI in your app\n\n::lead\nIf your app has an AI assistant, `@pyas\u002Fai-tools` gives it calendar tools. It can see what's on someone's week, find a time when they're free, and book, move or cancel a meeting. Each person's assistant works only with their own calendars, and it asks them before it changes anything.\n::\n\nThis page is about the assistant inside your product, used by your customers. To use Pyas yourself, from ChatGPT, Claude or your code editor, see [AI tools](\u002Fai-tools).\n\n## How it works\n\n1. Your users connect their Google or Outlook calendars with [PyasConnect](\u002Fconnect). Pyas saves each account under your own ID for that user, the `subjectId`.\n2. On your server, you give the model Pyas's tools for the signed-in user. The tools have access to that user's accounts only.\n3. The model reads the calendars when it needs to. When it wants to change something, your app shows the person the change, and the change runs only if they approve it.\n\nYou bring the model. Pyas provides the calendar data and the actions, and your API key stays on your server.\n\n## Choose your framework\n\nEach guide has the complete code for a chat that books meetings, tested with a real model:\n\n- [Vercel AI SDK](\u002Fvercel-ai-sdk): a Next.js chat with `useChat`\n- [OpenAI](\u002Fopenai): Chat Completions or the Responses API\n- [Claude API](\u002Fclaude-api): the Messages API\n- [LangChain](\u002Flangchain): an agent made with `createAgent`\n\nInstall the package in your server code:\n\n```bash\nnpm install @pyas\u002Fai-tools\n```\n\nThe tools read your key from `PYAS_API_KEY`.\n\n## The tools\n\n| Tool | What it does | Asks the person first |\n|---|---|---|\n| `list_accounts` | Lists the person's connected calendars | |\n| `account_status` | Checks whether a calendar is working | |\n| `list_calendars` | Lists the calendars in an account | |\n| `list_events` | Lists events between two times | |\n| `get_event` | Gets one event with all its details | |\n| `find_meeting_times` | Finds times when everyone is free | |\n| `get_free_busy` | Gets busy times | |\n| `create_event` | Creates an event, with a Meet, Teams or Zoom link if asked | Yes |\n| `update_event` | Changes an event, or adds or removes its video meeting | Yes |\n| `delete_event` | Deletes an event | Yes |\n| `respond_to_event` | Accepts or declines an invitation | Yes |\n\nEvents come back with their local times, so the model tells people the time where the event is.\n\n## One person's calendars\n\nPass the signed-in person's ID as `subjectId`:\n\n```ts\npyasTools({ subjectId: user.id })\n```\n\nThe tools then have access to that person's accounts only. The model can't choose another person. A request for anyone else's account is refused before it reaches Pyas, with the code `TOOL_ACCOUNT_NOT_ALLOWED`.\n\nTake the ID from your own sign-in, on your server. Never take it from the request body or from what the model says.\n\n`subjectId` is required. Without it, or with an empty one (a session with no user), building the tools throws an error instead of quietly giving the model every user's calendars. For an agent that works for you rather than for one of your users, such as an internal assistant, pass `allAccounts: true` to reach every account in the app. Never use it for a chat your users open.\n\nWhen a change waits for approval, the tools also return the calendar it lands on (`calendar`: its email, provider and `subjectId`). Show it on the approval card, so someone with several calendars sees which one.\n\n## Which calendar\n\nPeople often connect more than one calendar, such as work Google and personal Outlook. When they ask for a new event without saying where, the model asks them which calendar to use.\n\nIf your app lets people choose a default calendar, pass it on each request as a hint:\n\n```ts\npyasTools({ subjectId: user.id, defaultAccountId: user.defaultCalendarId })\n```\n\nPyas doesn't store it: keep it with the user in your own database. With it, the model uses that calendar without asking unless the person names another, and it's checked like any other account, so one that isn't the person's is refused. Empty or `null` means no default.\n\n## Changes wait for approval\n\nA tool that changes a calendar doesn't run when the model asks for it. Your app shows the person the change, and they approve or decline it:\n\n::screenshot{src=\"\u002Fimages\u002Fapproval-in-your-app.png\" alt=\"A chat in an app. The person asked to move their 1:1 with Sarah to 4:30 PM. A card shows the event's current time and the new time, with Approve and Decline buttons.\" width=\"660\" height=\"399\" caption=\"An approval in a chat built with the Vercel AI SDK guide. The event's title and current time come from Pyas.\"}\n::\n\n- **What the person sees comes from Pyas.** For a change to an existing event, you get the event as it is now in Pyas, not as the model describes it. Show it. If text in an invitation tricks the model into picking another event, the person sees which event it really is.\n- **A change that can never run isn't put to the person.** A change to someone else's account, or to an event that doesn't exist, is refused immediately, and the model is told why.\n- **A declined change is never sent to Pyas.** The model is told that the person declined, and it leaves the calendar as it is.\n\nEach guide shows how this works in its framework. If your app already asks the person before a change, turn this off with `requireApproval: false`.\n\n## Offer fewer tools\n\nGive the assistant only what it needs:\n\n```ts\npyasTools({ subjectId: user.id, readOnly: true })                   \u002F\u002F reads only: nothing can change\npyasTools({ subjectId: user.id, scope: ['events', 'availability'] }) \u002F\u002F events and free times only\npyasTools({ subjectId: user.id, scope: ['events:read'] })            \u002F\u002F list and get events only\n```\n\nThe scopes are `accounts`, `calendars`, `events` and `availability`, each alone or with `:read` or `:write`. `read` and `write` alone apply to all four. A scope that doesn't exist throws an error, so a typo never gives the assistant more than you meant.\n\n## When a tool fails\n\nA tool never throws an error at the model. It returns the error with its code, a hint and the request's trace ID, so the model can fix its request or tell the person what to do:\n\n```json\n{\n  \"error\": {\n    \"code\": \"RESOURCE_NOT_FOUND\",\n    \"message\": \"The calendar or event was not found.\",\n    \"hint\": \"Check the ID. The item may have been deleted, or it may not be visible to this account.\",\n    \"docs\": \"https:\u002F\u002Fdeveloper.pyas.io\u002Ferrors\u002FRESOURCE_NOT_FOUND\",\n    \"traceId\": \"trc_...\"\n  }\n}\n```\n\nEvery request the tools make is on the **Logs** page in the dashboard, marked **By @pyas\u002Fai-tools**. To see what a request did, open its trace ID there.\n\n## Try it in the sandbox\n\nStart with a test key. A test key works only in your app's [sandbox](\u002Fsandbox), where sample people have a week of events, and nothing reaches a real calendar.\n\n1. Copy a test key from **API keys** in the [dashboard](https:\u002F\u002Fapp.pyas.io), and set it as `PYAS_API_KEY` on your server.\n2. Use the sandbox person Avery Brooks as the signed-in user: `subjectId: 'sandbox_user_1'`.\n3. Ask: \"What's on my calendar tomorrow?\", then \"Book a 30 minute meeting tomorrow afternoon when I'm free.\"\n\n## Go live\n\n1. Add [PyasConnect](\u002Fconnect) to your app, so people can connect their calendars. Save each account under your user's ID as `subjectId`.\n2. Pass the signed-in user's ID as `subjectId`, in place of the sandbox person.\n3. Create a live key in the dashboard, and set it as `PYAS_API_KEY` on your server.\n\nWith a live key, the assistant reads and changes real calendars, and every change still waits for the person's approval.\n",{"title":47,"description":48},null,"o6Md1RTiRoO7Uc-c52vJLRtBE7YvVrS--fIOLiGEBko",1794697271132]