diff --git a/app/en/home/auth-providers/airtable/page.mdx b/app/en/home/auth-providers/airtable/page.mdx
new file mode 100644
index 000000000..7687ce499
--- /dev/null
+++ b/app/en/home/auth-providers/airtable/page.mdx
@@ -0,0 +1,270 @@
+import { Tabs, Callout, Steps } from "nextra/components";
+
+# Airtable
+
+The Airtable auth provider enables tools and agents to call Airtable APIs on behalf of a user.
+
+## What's documented here
+
+This page describes how to use and configure Airtable auth with Arcade.
+
+This auth provider is used by:
+
+- Your [app code](#using-airtable-auth-in-app-code) that needs to call Airtable APIs
+- Or, your [custom tools](#using-airtable-auth-in-custom-tools) that need to call Airtable APIs
+
+## Use Arcade's Default Airtable Auth Provider
+
+Arcade offers a default Airtable auth provider that you can use in the Arcade Cloud Platform. In this case, your users will see `Arcade` as the name of the application that's requesting permission.
+
+If you choose to use Arcade's Airtable auth, you don't need to configure anything.
+
+## Use Your Own Airtable App Credentials
+
+
+ When using your own app credentials, make sure you configure your project to
+ use a [custom user
+ verifier](/home/auth/secure-auth-production#build-a-custom-user-verifier).
+ Without this, your end-users will not be able to use your app or agent in
+ production.
+
+
+In a production environment, you will most likely want to use your own Airtable app credentials. This way, your users will see your application's name requesting permission.
+
+Before showing how to configure your Airtable app credentials, let's go through the steps to create an Airtable app.
+
+## Create an Airtable App
+
+Follow the documentation on [Airtable Web API](https://airtable.com/developers/web/api/introduction). You will need to create an OAuth integration in your Airtable account.
+
+When creating your app, use the following settings:
+
+- Set an appropriate App name and description. This will be visible to your users authorizing access to your app.
+- In the **OAuth Settings** section:
+ - Take note of the **Client ID** and **Client Secret**.
+ - Add the redirect URL generated by Arcade (see below) to the **Redirect URLs**.
+- Configure the appropriate **Scopes** for your application. Common scopes include:
+ - `data.records:read` - Read records from bases
+ - `data.records:write` - Create and update records in bases
+ - `schema.bases:read` - Read base schemas
+ - `schema.bases:write` - Modify base schemas
+
+## Configuring your own Airtable Auth Provider in Arcade
+
+
+
+
+### Configure Airtable Auth Using the Arcade Dashboard GUI
+
+
+
+#### Access the Arcade Dashboard
+
+To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard). If you are self-hosting, by default the dashboard will be available at http://localhost:9099/dashboard. Adjust the host and port number to match your environment.
+
+#### Navigate to the OAuth Providers page
+
+- Under the **OAuth** section of the Arcade Dashboard left-side menu, click **Providers**.
+- Click **Add OAuth Provider** in the top right corner.
+- Select the **Included Providers** tab at the top.
+- In the **Provider** dropdown, select **Airtable**.
+
+#### Enter the provider details
+
+- Choose a unique **ID** for your provider (e.g. "my-airtable-provider").
+- Optionally enter a **Description**.
+- Enter the **Client ID** and **Client Secret** from your Airtable OAuth integration.
+- Note the **Redirect URL** generated by Arcade. This must be added to your Airtable OAuth integration's Redirect URLs.
+
+#### Create the provider
+
+Hit the **Create** button and the provider will be ready to be used.
+
+
+
+When you use tools that require Airtable auth using your Arcade account credentials, Arcade will automatically use this Airtable OAuth provider. If you have multiple Airtable providers, see [using multiple auth providers of the same type](/home/auth-providers#using-multiple-providers-of-the-same-type) for more information.
+
+
+
+
+## Using Airtable auth in app code
+
+Use the Airtable auth provider in your own agents and AI apps to get a user-scoped token for the Airtable API. See [authorizing agents with Arcade](/home/auth/how-arcade-helps) to understand how this works.
+
+Use `client.auth.start()` to get a user token for the Airtable API:
+
+
+
+
+```python {21-22}
+from arcadepy import Arcade
+
+client = Arcade(base_url="https://api.arcade.dev") # Automatically finds the `ARCADE_API_KEY` env variable
+
+user_id = "{arcade_user_id}"
+
+# Start the authorization process
+auth_response = client.auth.start(
+ user_id=user_id,
+ provider="airtable",
+ scopes=["data.records:read", "data.records:write"],
+)
+
+if auth_response.status != "completed":
+ print("Please complete the authorization challenge in your browser:")
+ print(auth_response.url)
+
+# Wait for the authorization to complete
+auth_response = client.auth.wait_for_completion(auth_response)
+
+# Do something interesting with the token...
+auth_token = auth_response.context.token
+```
+
+
+
+
+
+```javascript {20-21}
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade({ baseURL: "https://api.arcade.dev" }); // Automatically finds the `ARCADE_API_KEY` env variable
+
+const userId = "{arcade_user_id}";
+
+// Start the authorization process
+const authResponse = await client.auth.start(userId, "airtable", {
+ scopes: ["data.records:read", "data.records:write"],
+});
+
+if (authResponse.status !== "completed") {
+ console.log("Please complete the authorization challenge in your browser:");
+ console.log(authResponse.url);
+}
+
+// Wait for the authorization to complete
+const response = await client.auth.waitForCompletion(authResponse);
+
+// Do something interesting with the token...
+const auth_token = response.context.token;
+```
+
+
+
+
+You can use the auth token to call the [Airtable Web API](https://airtable.com/developers/web/api/introduction) endpoints to interact with bases, tables, and records.
+
+## Using Airtable auth in custom tools
+
+You can author your own [custom tools](/home/build-tools/create-a-mcp-server) that interact with Airtable API.
+
+Use the `OAuth2()` auth class to specify that a tool requires authorization with Airtable. The authentication token needed to call the Airtable API is available in the tool context through the `context.authorization.token` property.
+
+```python {9,17}
+from typing import Annotated
+
+import httpx
+
+from arcade_tdk import ToolContext, tool
+from arcade_tdk.auth import OAuth2
+
+
+@tool(requires_auth=OAuth2(provider_id="airtable", scopes=["data.records:read"]))
+async def list_records(
+ context: ToolContext,
+ base_id: Annotated[str, "The ID of the base."],
+ table_id: Annotated[str, "The ID or name of the table."],
+) -> Annotated[dict, "List of records"]:
+ """Retrieves records from an Airtable table."""
+ url = f"https://api.airtable.com/v0/{base_id}/{table_id}"
+ headers = {
+ "Authorization": f"Bearer {context.authorization.token}",
+ "Accept": "application/json",
+ }
+
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url, headers=headers)
+ response.raise_for_status()
+ return response.json()
+```
+
+Your new tool can be called like demonstrated below:
+
+
+
+
+ If you are self-hosting, change the `base_url` parameter in the `Arcade` constructor to match your Arcade Engine URL. By default, the Engine will be available at `http://localhost:9099`.
+
+
+```python
+from arcadepy import Arcade
+
+client = Arcade(base_url="https://api.arcade.dev") # Automatically finds the `ARCADE_API_KEY` env variable
+
+USER_ID = "{arcade_user_id}"
+TOOL_NAME = "Airtable.ListRecords"
+
+auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID)
+
+if auth_response.status != "completed":
+ print(f"Click this link to authorize: {auth_response.url}")
+
+# Wait for the authorization to complete
+client.auth.wait_for_completion(auth_response)
+
+tool_input = {
+ "base_id": "appXXXXXXXXXXXXXX",
+ "table_id": "tblXXXXXXXXXXXXXX",
+}
+
+response = client.tools.execute(
+ tool_name=TOOL_NAME,
+ input=tool_input,
+ user_id=USER_ID,
+)
+print(response.output.value)
+```
+
+
+
+
+ If you are self-hosting, change the `baseURL` parameter in the `Arcade` constructor to match your Arcade Engine URL. By default, the Engine will be available at `http://localhost:9099`.
+
+
+```javascript
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade({ baseURL: "https://api.arcade.dev" }); // Automatically finds the `ARCADE_API_KEY` env variable
+
+const USER_ID = "{arcade_user_id}";
+const TOOL_NAME = "Airtable.ListRecords";
+
+// Start the authorization process
+const authResponse = await client.tools.authorize({
+ tool_name: TOOL_NAME,
+ user_id: USER_ID,
+});
+
+if (authResponse.status !== "completed") {
+ console.log(`Click this link to authorize: ${authResponse.url}`);
+}
+
+// Wait for the authorization to complete
+await client.auth.waitForCompletion(authResponse);
+
+const toolInput = {
+ base_id: "appXXXXXXXXXXXXXX",
+ table_id: "tblXXXXXXXXXXXXXX",
+};
+
+const response = await client.tools.execute({
+ tool_name: TOOL_NAME,
+ input: toolInput,
+ user_id: USER_ID,
+});
+
+console.log(response.output.value);
+```
+
+
+
diff --git a/app/en/home/auth-providers/calendly/page.mdx b/app/en/home/auth-providers/calendly/page.mdx
new file mode 100644
index 000000000..aeba5cfec
--- /dev/null
+++ b/app/en/home/auth-providers/calendly/page.mdx
@@ -0,0 +1,266 @@
+import { Tabs, Callout, Steps } from "nextra/components";
+
+# Calendly
+
+The Calendly auth provider enables tools and agents to call Calendly APIs on behalf of a user.
+
+## What's documented here
+
+This page describes how to use and configure Calendly auth with Arcade.
+
+This auth provider is used by:
+
+- Your [app code](#using-calendly-auth-in-app-code) that needs to call Calendly APIs
+- Or, your [custom tools](#using-calendly-auth-in-custom-tools) that need to call Calendly APIs
+
+## Use Arcade's Default Calendly Auth Provider
+
+Arcade offers a default Calendly auth provider that you can use in the Arcade Cloud Platform. In this case, your users will see `Arcade` as the name of the application that's requesting permission.
+
+If you choose to use Arcade's Calendly auth, you don't need to configure anything.
+
+## Use Your Own Calendly App Credentials
+
+
+ When using your own app credentials, make sure you configure your project to
+ use a [custom user
+ verifier](/home/auth/secure-auth-production#build-a-custom-user-verifier).
+ Without this, your end-users will not be able to use your app or agent in
+ production.
+
+
+In a production environment, you will most likely want to use your own Calendly app credentials. This way, your users will see your application's name requesting permission.
+
+Before showing how to configure your Calendly app credentials, let's go through the steps to create a Calendly app.
+
+## Create a Calendly App
+
+Follow the documentation on [Getting Started with Calendly API](https://developer.calendly.com/getting-started). You will need to register for a developer account.
+
+When creating your app, use the following settings:
+
+- Set an appropriate App name and description. This will be visible to your users authorizing access to your app.
+- In the **OAuth Settings** section:
+ - Take note of the **Client ID** and **Client Secret**.
+ - Add the redirect URL generated by Arcade (see below) to the **Redirect URIs**.
+- Configure the appropriate **Scopes** for your application. Common scopes include:
+ - `default` - Basic access to user information
+ - Additional scopes as needed for your use case
+
+## Configuring your own Calendly Auth Provider in Arcade
+
+
+
+
+### Configure Calendly Auth Using the Arcade Dashboard GUI
+
+
+
+#### Access the Arcade Dashboard
+
+To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard). If you are self-hosting, by default the dashboard will be available at http://localhost:9099/dashboard. Adjust the host and port number to match your environment.
+
+#### Navigate to the OAuth Providers page
+
+- Under the **OAuth** section of the Arcade Dashboard left-side menu, click **Providers**.
+- Click **Add OAuth Provider** in the top right corner.
+- Select the **Included Providers** tab at the top.
+- In the **Provider** dropdown, select **Calendly**.
+
+#### Enter the provider details
+
+- Choose a unique **ID** for your provider (e.g. "my-calendly-provider").
+- Optionally enter a **Description**.
+- Enter the **Client ID** and **Client Secret** from your Calendly app.
+- Note the **Redirect URL** generated by Arcade. This must be added to your Calendly app's Redirect URIs.
+
+#### Create the provider
+
+Hit the **Create** button and the provider will be ready to be used.
+
+
+
+When you use tools that require Calendly auth using your Arcade account credentials, Arcade will automatically use this Calendly OAuth provider. If you have multiple Calendly providers, see [using multiple auth providers of the same type](/home/auth-providers#using-multiple-providers-of-the-same-type) for more information.
+
+
+
+
+## Using Calendly auth in app code
+
+Use the Calendly auth provider in your own agents and AI apps to get a user-scoped token for the Calendly API. See [authorizing agents with Arcade](/home/auth/how-arcade-helps) to understand how this works.
+
+Use `client.auth.start()` to get a user token for the Calendly API:
+
+
+
+
+```python {21-22}
+from arcadepy import Arcade
+
+client = Arcade(base_url="https://api.arcade.dev") # Automatically finds the `ARCADE_API_KEY` env variable
+
+user_id = "{arcade_user_id}"
+
+# Start the authorization process
+auth_response = client.auth.start(
+ user_id=user_id,
+ provider="calendly",
+ scopes=["default"],
+)
+
+if auth_response.status != "completed":
+ print("Please complete the authorization challenge in your browser:")
+ print(auth_response.url)
+
+# Wait for the authorization to complete
+auth_response = client.auth.wait_for_completion(auth_response)
+
+# Do something interesting with the token...
+auth_token = auth_response.context.token
+```
+
+
+
+
+
+```javascript {20-21}
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade({ baseURL: "https://api.arcade.dev" }); // Automatically finds the `ARCADE_API_KEY` env variable
+
+const userId = "{arcade_user_id}";
+
+// Start the authorization process
+const authResponse = await client.auth.start(userId, "calendly", {
+ scopes: ["default"],
+});
+
+if (authResponse.status !== "completed") {
+ console.log("Please complete the authorization challenge in your browser:");
+ console.log(authResponse.url);
+}
+
+// Wait for the authorization to complete
+const response = await client.auth.waitForCompletion(authResponse);
+
+// Do something interesting with the token...
+const auth_token = response.context.token;
+```
+
+
+
+
+You can use the auth token to call the [Calendly API](https://developer.calendly.com/api-docs) endpoints to manage events, scheduling links, and user information.
+
+## Using Calendly auth in custom tools
+
+You can author your own [custom tools](/home/build-tools/create-a-mcp-server) that interact with Calendly API.
+
+Use the `OAuth2()` auth class to specify that a tool requires authorization with Calendly. The authentication token needed to call the Calendly API is available in the tool context through the `context.authorization.token` property.
+
+```python {9,17}
+from typing import Annotated
+
+import httpx
+
+from arcade_tdk import ToolContext, tool
+from arcade_tdk.auth import OAuth2
+
+
+@tool(requires_auth=OAuth2(provider_id="calendly", scopes=["default"]))
+async def get_user_events(
+ context: ToolContext,
+ user_uri: Annotated[str, "The URI of the user whose events to retrieve."],
+) -> Annotated[dict, "User events information"]:
+ """Retrieves scheduled events for a Calendly user."""
+ url = "https://api.calendly.com/scheduled_events"
+ headers = {
+ "Authorization": f"Bearer {context.authorization.token}",
+ "Accept": "application/json",
+ }
+ params = {"user": user_uri}
+
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url, headers=headers, params=params)
+ response.raise_for_status()
+ return response.json()
+```
+
+Your new tool can be called like demonstrated below:
+
+
+
+
+ If you are self-hosting, change the `base_url` parameter in the `Arcade` constructor to match your Arcade Engine URL. By default, the Engine will be available at `http://localhost:9099`.
+
+
+```python
+from arcadepy import Arcade
+
+client = Arcade(base_url="https://api.arcade.dev") # Automatically finds the `ARCADE_API_KEY` env variable
+
+USER_ID = "{arcade_user_id}"
+TOOL_NAME = "Calendly.GetUserEvents"
+
+auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID)
+
+if auth_response.status != "completed":
+ print(f"Click this link to authorize: {auth_response.url}")
+
+# Wait for the authorization to complete
+client.auth.wait_for_completion(auth_response)
+
+tool_input = {
+ "user_uri": "https://api.calendly.com/users/AAAAAAAAAAAAAAAA",
+}
+
+response = client.tools.execute(
+ tool_name=TOOL_NAME,
+ input=tool_input,
+ user_id=USER_ID,
+)
+print(response.output.value)
+```
+
+
+
+
+ If you are self-hosting, change the `baseURL` parameter in the `Arcade` constructor to match your Arcade Engine URL. By default, the Engine will be available at `http://localhost:9099`.
+
+
+```javascript
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade({ baseURL: "https://api.arcade.dev" }); // Automatically finds the `ARCADE_API_KEY` env variable
+
+const USER_ID = "{arcade_user_id}";
+const TOOL_NAME = "Calendly.GetUserEvents";
+
+// Start the authorization process
+const authResponse = await client.tools.authorize({
+ tool_name: TOOL_NAME,
+ user_id: USER_ID,
+});
+
+if (authResponse.status !== "completed") {
+ console.log(`Click this link to authorize: ${authResponse.url}`);
+}
+
+// Wait for the authorization to complete
+await client.auth.waitForCompletion(authResponse);
+
+const toolInput = {
+ user_uri: "https://api.calendly.com/users/AAAAAAAAAAAAAAAA",
+};
+
+const response = await client.tools.execute({
+ tool_name: TOOL_NAME,
+ input: toolInput,
+ user_id: USER_ID,
+});
+
+console.log(response.output.value);
+```
+
+
+
diff --git a/app/en/home/auth-providers/miro/page.mdx b/app/en/home/auth-providers/miro/page.mdx
new file mode 100644
index 000000000..ee67e149f
--- /dev/null
+++ b/app/en/home/auth-providers/miro/page.mdx
@@ -0,0 +1,268 @@
+import { Tabs, Callout, Steps } from "nextra/components";
+
+# Miro
+
+The Miro auth provider enables tools and agents to call Miro APIs on behalf of a user.
+
+## What's documented here
+
+This page describes how to use and configure Miro auth with Arcade.
+
+This auth provider is used by:
+
+- Your [app code](#using-miro-auth-in-app-code) that needs to call Miro APIs
+- Or, your [custom tools](#using-miro-auth-in-custom-tools) that need to call Miro APIs
+
+## Use Arcade's Default Miro Auth Provider
+
+Arcade offers a default Miro auth provider that you can use in the Arcade Cloud Platform. In this case, your users will see `Arcade` as the name of the application that's requesting permission.
+
+If you choose to use Arcade's Miro auth, you don't need to configure anything.
+
+## Use Your Own Miro App Credentials
+
+
+ When using your own app credentials, make sure you configure your project to
+ use a [custom user
+ verifier](/home/auth/secure-auth-production#build-a-custom-user-verifier).
+ Without this, your end-users will not be able to use your app or agent in
+ production.
+
+
+In a production environment, you will most likely want to use your own Miro app credentials. This way, your users will see your application's name requesting permission.
+
+Before showing how to configure your Miro app credentials, let's go through the steps to create a Miro app.
+
+## Create a Miro App
+
+Follow the documentation on [Building Apps for Miro](https://developers.miro.com/docs/build-your-first-hello-world-app). You will need to create a developer team to build apps.
+
+When creating your app, use the following settings:
+
+- Set an appropriate App name and description. This will be visible to your users authorizing access to your app.
+- In the **App Credentials** section:
+ - Take note of the **Client ID** and **Client Secret**.
+- In the **Redirect URI for OAuth2.0** section:
+ - Add the redirect URL generated by Arcade (see below).
+- In the **Permissions** section:
+ - Select the appropriate permissions/scopes for your app. Common scopes include:
+ - `boards:read` - Read access to boards
+ - `boards:write` - Write access to boards
+- Install the app to your team to test it.
+
+## Configuring your own Miro Auth Provider in Arcade
+
+
+
+
+### Configure Miro Auth Using the Arcade Dashboard GUI
+
+
+
+#### Access the Arcade Dashboard
+
+To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard). If you are self-hosting, by default the dashboard will be available at http://localhost:9099/dashboard. Adjust the host and port number to match your environment.
+
+#### Navigate to the OAuth Providers page
+
+- Under the **OAuth** section of the Arcade Dashboard left-side menu, click **Providers**.
+- Click **Add OAuth Provider** in the top right corner.
+- Select the **Included Providers** tab at the top.
+- In the **Provider** dropdown, select **Miro**.
+
+#### Enter the provider details
+
+- Choose a unique **ID** for your provider (e.g. "my-miro-provider").
+- Optionally enter a **Description**.
+- Enter the **Client ID** and **Client Secret** from your Miro app.
+- Note the **Redirect URL** generated by Arcade. This must be added to your Miro app's Redirect URIs.
+
+#### Create the provider
+
+Hit the **Create** button and the provider will be ready to be used.
+
+
+
+When you use tools that require Miro auth using your Arcade account credentials, Arcade will automatically use this Miro OAuth provider. If you have multiple Miro providers, see [using multiple auth providers of the same type](/home/auth-providers#using-multiple-providers-of-the-same-type) for more information.
+
+
+
+
+## Using Miro auth in app code
+
+Use the Miro auth provider in your own agents and AI apps to get a user-scoped token for the Miro API. See [authorizing agents with Arcade](/home/auth/how-arcade-helps) to understand how this works.
+
+Use `client.auth.start()` to get a user token for the Miro API:
+
+
+
+
+```python {21-22}
+from arcadepy import Arcade
+
+client = Arcade(base_url="https://api.arcade.dev") # Automatically finds the `ARCADE_API_KEY` env variable
+
+user_id = "{arcade_user_id}"
+
+# Start the authorization process
+auth_response = client.auth.start(
+ user_id=user_id,
+ provider="miro",
+ scopes=["boards:read", "boards:write"],
+)
+
+if auth_response.status != "completed":
+ print("Please complete the authorization challenge in your browser:")
+ print(auth_response.url)
+
+# Wait for the authorization to complete
+auth_response = client.auth.wait_for_completion(auth_response)
+
+# Do something interesting with the token...
+auth_token = auth_response.context.token
+```
+
+
+
+
+
+```javascript {20-21}
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade({ baseURL: "https://api.arcade.dev" }); // Automatically finds the `ARCADE_API_KEY` env variable
+
+const userId = "{arcade_user_id}";
+
+// Start the authorization process
+const authResponse = await client.auth.start(userId, "miro", {
+ scopes: ["boards:read", "boards:write"],
+});
+
+if (authResponse.status !== "completed") {
+ console.log("Please complete the authorization challenge in your browser:");
+ console.log(authResponse.url);
+}
+
+// Wait for the authorization to complete
+const response = await client.auth.waitForCompletion(authResponse);
+
+// Do something interesting with the token...
+const auth_token = response.context.token;
+```
+
+
+
+
+You can use the auth token to call the [Miro REST API](https://developers.miro.com/reference/api-reference) endpoints to interact with boards, widgets, and other Miro resources.
+
+## Using Miro auth in custom tools
+
+You can author your own [custom tools](/home/build-tools/create-a-mcp-server) that interact with Miro API.
+
+Use the `OAuth2()` auth class to specify that a tool requires authorization with Miro. The authentication token needed to call the Miro API is available in the tool context through the `context.authorization.token` property.
+
+```python {9,17}
+from typing import Annotated
+
+import httpx
+
+from arcade_tdk import ToolContext, tool
+from arcade_tdk.auth import OAuth2
+
+
+@tool(requires_auth=OAuth2(provider_id="miro", scopes=["boards:read"]))
+async def get_board(
+ context: ToolContext,
+ board_id: Annotated[str, "The ID of the board to retrieve."],
+) -> Annotated[dict, "Board information"]:
+ """Retrieves information about a Miro board."""
+ url = f"https://api.miro.com/v2/boards/{board_id}"
+ headers = {
+ "Authorization": f"Bearer {context.authorization.token}",
+ "Accept": "application/json",
+ }
+
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url, headers=headers)
+ response.raise_for_status()
+ return response.json()
+```
+
+Your new tool can be called like demonstrated below:
+
+
+
+
+ If you are self-hosting, change the `base_url` parameter in the `Arcade` constructor to match your Arcade Engine URL. By default, the Engine will be available at `http://localhost:9099`.
+
+
+```python
+from arcadepy import Arcade
+
+client = Arcade(base_url="https://api.arcade.dev") # Automatically finds the `ARCADE_API_KEY` env variable
+
+USER_ID = "{arcade_user_id}"
+TOOL_NAME = "Miro.GetBoard"
+
+auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID)
+
+if auth_response.status != "completed":
+ print(f"Click this link to authorize: {auth_response.url}")
+
+# Wait for the authorization to complete
+client.auth.wait_for_completion(auth_response)
+
+tool_input = {
+ "board_id": "uXjVK1234567",
+}
+
+response = client.tools.execute(
+ tool_name=TOOL_NAME,
+ input=tool_input,
+ user_id=USER_ID,
+)
+print(response.output.value)
+```
+
+
+
+
+ If you are self-hosting, change the `baseURL` parameter in the `Arcade` constructor to match your Arcade Engine URL. By default, the Engine will be available at `http://localhost:9099`.
+
+
+```javascript
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade({ baseURL: "https://api.arcade.dev" }); // Automatically finds the `ARCADE_API_KEY` env variable
+
+const USER_ID = "{arcade_user_id}";
+const TOOL_NAME = "Miro.GetBoard";
+
+// Start the authorization process
+const authResponse = await client.tools.authorize({
+ tool_name: TOOL_NAME,
+ user_id: USER_ID,
+});
+
+if (authResponse.status !== "completed") {
+ console.log(`Click this link to authorize: ${authResponse.url}`);
+}
+
+// Wait for the authorization to complete
+await client.auth.waitForCompletion(authResponse);
+
+const toolInput = {
+ board_id: "uXjVK1234567",
+};
+
+const response = await client.tools.execute({
+ tool_name: TOOL_NAME,
+ input: toolInput,
+ user_id: USER_ID,
+});
+
+console.log(response.output.value);
+```
+
+
+
diff --git a/app/en/home/auth-providers/pagerduty/page.mdx b/app/en/home/auth-providers/pagerduty/page.mdx
new file mode 100644
index 000000000..44f91b387
--- /dev/null
+++ b/app/en/home/auth-providers/pagerduty/page.mdx
@@ -0,0 +1,266 @@
+import { Tabs, Callout, Steps } from "nextra/components";
+
+# PagerDuty
+
+The PagerDuty auth provider enables tools and agents to call PagerDuty APIs on behalf of a user.
+
+## What's documented here
+
+This page describes how to use and configure PagerDuty auth with Arcade.
+
+This auth provider is used by:
+
+- Your [app code](#using-pagerduty-auth-in-app-code) that needs to call PagerDuty APIs
+- Or, your [custom tools](#using-pagerduty-auth-in-custom-tools) that need to call PagerDuty APIs
+
+## Use Arcade's Default PagerDuty Auth Provider
+
+Arcade offers a default PagerDuty auth provider that you can use in the Arcade Cloud Platform. In this case, your users will see `Arcade` as the name of the application that's requesting permission.
+
+If you choose to use Arcade's PagerDuty auth, you don't need to configure anything.
+
+## Use Your Own PagerDuty App Credentials
+
+
+ When using your own app credentials, make sure you configure your project to
+ use a [custom user
+ verifier](/home/auth/secure-auth-production#build-a-custom-user-verifier).
+ Without this, your end-users will not be able to use your app or agent in
+ production.
+
+
+In a production environment, you will most likely want to use your own PagerDuty app credentials. This way, your users will see your application's name requesting permission.
+
+Before showing how to configure your PagerDuty app credentials, let's go through the steps to create a PagerDuty app.
+
+## Create a PagerDuty App
+
+Follow the documentation on [PagerDuty API Documentation](https://developer.pagerduty.com/docs/). You will need to create an app in your PagerDuty account.
+
+When creating your app, use the following settings:
+
+- Set an appropriate App name and description. This will be visible to your users authorizing access to your app.
+- In the **App Registration** section:
+ - Take note of the **Client ID** and **Client Secret**.
+ - Add the redirect URL generated by Arcade (see below) to the **Redirect URLs**.
+- Configure the appropriate **Scopes** for your application. Common scopes include:
+ - `read` - Read access to PagerDuty resources
+ - `write` - Write access to PagerDuty resources
+
+## Configuring your own PagerDuty Auth Provider in Arcade
+
+
+
+
+### Configure PagerDuty Auth Using the Arcade Dashboard GUI
+
+
+
+#### Access the Arcade Dashboard
+
+To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard). If you are self-hosting, by default the dashboard will be available at http://localhost:9099/dashboard. Adjust the host and port number to match your environment.
+
+#### Navigate to the OAuth Providers page
+
+- Under the **OAuth** section of the Arcade Dashboard left-side menu, click **Providers**.
+- Click **Add OAuth Provider** in the top right corner.
+- Select the **Included Providers** tab at the top.
+- In the **Provider** dropdown, select **PagerDuty**.
+
+#### Enter the provider details
+
+- Choose a unique **ID** for your provider (e.g. "my-pagerduty-provider").
+- Optionally enter a **Description**.
+- Enter the **Client ID** and **Client Secret** from your PagerDuty app.
+- Note the **Redirect URL** generated by Arcade. This must be added to your PagerDuty app's Redirect URLs.
+
+#### Create the provider
+
+Hit the **Create** button and the provider will be ready to be used.
+
+
+
+When you use tools that require PagerDuty auth using your Arcade account credentials, Arcade will automatically use this PagerDuty OAuth provider. If you have multiple PagerDuty providers, see [using multiple auth providers of the same type](/home/auth-providers#using-multiple-providers-of-the-same-type) for more information.
+
+
+
+
+## Using PagerDuty auth in app code
+
+Use the PagerDuty auth provider in your own agents and AI apps to get a user-scoped token for the PagerDuty API. See [authorizing agents with Arcade](/home/auth/how-arcade-helps) to understand how this works.
+
+Use `client.auth.start()` to get a user token for the PagerDuty API:
+
+
+
+
+```python {21-22}
+from arcadepy import Arcade
+
+client = Arcade(base_url="https://api.arcade.dev") # Automatically finds the `ARCADE_API_KEY` env variable
+
+user_id = "{arcade_user_id}"
+
+# Start the authorization process
+auth_response = client.auth.start(
+ user_id=user_id,
+ provider="pagerduty",
+ scopes=["read", "write"],
+)
+
+if auth_response.status != "completed":
+ print("Please complete the authorization challenge in your browser:")
+ print(auth_response.url)
+
+# Wait for the authorization to complete
+auth_response = client.auth.wait_for_completion(auth_response)
+
+# Do something interesting with the token...
+auth_token = auth_response.context.token
+```
+
+
+
+
+
+```javascript {20-21}
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade({ baseURL: "https://api.arcade.dev" }); // Automatically finds the `ARCADE_API_KEY` env variable
+
+const userId = "{arcade_user_id}";
+
+// Start the authorization process
+const authResponse = await client.auth.start(userId, "pagerduty", {
+ scopes: ["read", "write"],
+});
+
+if (authResponse.status !== "completed") {
+ console.log("Please complete the authorization challenge in your browser:");
+ console.log(authResponse.url);
+}
+
+// Wait for the authorization to complete
+const response = await client.auth.waitForCompletion(authResponse);
+
+// Do something interesting with the token...
+const auth_token = response.context.token;
+```
+
+
+
+
+You can use the auth token to call the [PagerDuty REST API](https://developer.pagerduty.com/api-reference/) endpoints to manage incidents, services, schedules, and more.
+
+## Using PagerDuty auth in custom tools
+
+You can author your own [custom tools](/home/build-tools/create-a-mcp-server) that interact with PagerDuty API.
+
+Use the `OAuth2()` auth class to specify that a tool requires authorization with PagerDuty. The authentication token needed to call the PagerDuty API is available in the tool context through the `context.authorization.token` property.
+
+```python {9,17}
+from typing import Annotated
+
+import httpx
+
+from arcade_tdk import ToolContext, tool
+from arcade_tdk.auth import OAuth2
+
+
+@tool(requires_auth=OAuth2(provider_id="pagerduty", scopes=["read"]))
+async def get_incidents(
+ context: ToolContext,
+ status: Annotated[str, "Filter by incident status (triggered, acknowledged, resolved)."] = "triggered",
+) -> Annotated[dict, "List of incidents"]:
+ """Retrieves PagerDuty incidents."""
+ url = "https://api.pagerduty.com/incidents"
+ headers = {
+ "Authorization": f"Bearer {context.authorization.token}",
+ "Accept": "application/vnd.pagerduty+json;version=2",
+ }
+ params = {"statuses[]": status}
+
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url, headers=headers, params=params)
+ response.raise_for_status()
+ return response.json()
+```
+
+Your new tool can be called like demonstrated below:
+
+
+
+
+ If you are self-hosting, change the `base_url` parameter in the `Arcade` constructor to match your Arcade Engine URL. By default, the Engine will be available at `http://localhost:9099`.
+
+
+```python
+from arcadepy import Arcade
+
+client = Arcade(base_url="https://api.arcade.dev") # Automatically finds the `ARCADE_API_KEY` env variable
+
+USER_ID = "{arcade_user_id}"
+TOOL_NAME = "PagerDuty.GetIncidents"
+
+auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID)
+
+if auth_response.status != "completed":
+ print(f"Click this link to authorize: {auth_response.url}")
+
+# Wait for the authorization to complete
+client.auth.wait_for_completion(auth_response)
+
+tool_input = {
+ "status": "triggered",
+}
+
+response = client.tools.execute(
+ tool_name=TOOL_NAME,
+ input=tool_input,
+ user_id=USER_ID,
+)
+print(response.output.value)
+```
+
+
+
+
+ If you are self-hosting, change the `baseURL` parameter in the `Arcade` constructor to match your Arcade Engine URL. By default, the Engine will be available at `http://localhost:9099`.
+
+
+```javascript
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade({ baseURL: "https://api.arcade.dev" }); // Automatically finds the `ARCADE_API_KEY` env variable
+
+const USER_ID = "{arcade_user_id}";
+const TOOL_NAME = "PagerDuty.GetIncidents";
+
+// Start the authorization process
+const authResponse = await client.tools.authorize({
+ tool_name: TOOL_NAME,
+ user_id: USER_ID,
+});
+
+if (authResponse.status !== "completed") {
+ console.log(`Click this link to authorize: ${authResponse.url}`);
+}
+
+// Wait for the authorization to complete
+await client.auth.waitForCompletion(authResponse);
+
+const toolInput = {
+ status: "triggered",
+};
+
+const response = await client.tools.execute({
+ tool_name: TOOL_NAME,
+ input: toolInput,
+ user_id: USER_ID,
+});
+
+console.log(response.output.value);
+```
+
+
+
diff --git a/app/en/home/auth-providers/squareup/page.mdx b/app/en/home/auth-providers/squareup/page.mdx
new file mode 100644
index 000000000..60f510715
--- /dev/null
+++ b/app/en/home/auth-providers/squareup/page.mdx
@@ -0,0 +1,271 @@
+import { Tabs, Callout, Steps } from "nextra/components";
+
+# Square
+
+The Square auth provider enables tools and agents to call Square APIs on behalf of a user.
+
+## What's documented here
+
+This page describes how to use and configure Square auth with Arcade.
+
+This auth provider is used by:
+
+- Your [app code](#using-square-auth-in-app-code) that needs to call Square APIs
+- Or, your [custom tools](#using-square-auth-in-custom-tools) that need to call Square APIs
+
+## Use Arcade's Default Square Auth Provider
+
+Arcade offers a default Square auth provider that you can use in the Arcade Cloud Platform. In this case, your users will see `Arcade` as the name of the application that's requesting permission.
+
+If you choose to use Arcade's Square auth, you don't need to configure anything.
+
+## Use Your Own Square App Credentials
+
+
+ When using your own app credentials, make sure you configure your project to
+ use a [custom user
+ verifier](/home/auth/secure-auth-production#build-a-custom-user-verifier).
+ Without this, your end-users will not be able to use your app or agent in
+ production.
+
+
+In a production environment, you will most likely want to use your own Square app credentials. This way, your users will see your application's name requesting permission.
+
+Before showing how to configure your Square app credentials, let's go through the steps to create a Square app.
+
+## Create a Square App
+
+Follow the documentation on [Square Developer Platform](https://developer.squareup.com/docs). You will need to create a Square developer account.
+
+When creating your app, use the following settings:
+
+- Set an appropriate App name and description. This will be visible to your users authorizing access to your app.
+- In the **OAuth** section:
+ - Take note of the **Application ID** (Client ID) and **Application Secret** (Client Secret).
+ - Add the redirect URL generated by Arcade (see below) to the **Redirect URL** field.
+- Configure the appropriate **Permissions** for your application. Common permissions include:
+ - `MERCHANT_PROFILE_READ` - Read merchant profile information
+ - `PAYMENTS_READ` - Read payment information
+ - `ORDERS_READ` - Read order information
+ - `ORDERS_WRITE` - Create and update orders
+ - `ITEMS_READ` - Read catalog items
+ - `ITEMS_WRITE` - Create and update catalog items
+
+## Configuring your own Square Auth Provider in Arcade
+
+
+
+
+### Configure Square Auth Using the Arcade Dashboard GUI
+
+
+
+#### Access the Arcade Dashboard
+
+To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard). If you are self-hosting, by default the dashboard will be available at http://localhost:9099/dashboard. Adjust the host and port number to match your environment.
+
+#### Navigate to the OAuth Providers page
+
+- Under the **OAuth** section of the Arcade Dashboard left-side menu, click **Providers**.
+- Click **Add OAuth Provider** in the top right corner.
+- Select the **Included Providers** tab at the top.
+- In the **Provider** dropdown, select **Square**.
+
+#### Enter the provider details
+
+- Choose a unique **ID** for your provider (e.g. "my-square-provider").
+- Optionally enter a **Description**.
+- Enter the **Client ID** (Application ID) and **Client Secret** (Application Secret) from your Square app.
+- Note the **Redirect URL** generated by Arcade. This must be added to your Square app's Redirect URL.
+
+#### Create the provider
+
+Hit the **Create** button and the provider will be ready to be used.
+
+
+
+When you use tools that require Square auth using your Arcade account credentials, Arcade will automatically use this Square OAuth provider. If you have multiple Square providers, see [using multiple auth providers of the same type](/home/auth-providers#using-multiple-providers-of-the-same-type) for more information.
+
+
+
+
+## Using Square auth in app code
+
+Use the Square auth provider in your own agents and AI apps to get a user-scoped token for the Square API. See [authorizing agents with Arcade](/home/auth/how-arcade-helps) to understand how this works.
+
+Use `client.auth.start()` to get a user token for the Square API:
+
+
+
+
+```python {21-22}
+from arcadepy import Arcade
+
+client = Arcade(base_url="https://api.arcade.dev") # Automatically finds the `ARCADE_API_KEY` env variable
+
+user_id = "{arcade_user_id}"
+
+# Start the authorization process
+auth_response = client.auth.start(
+ user_id=user_id,
+ provider="squareup",
+ scopes=["MERCHANT_PROFILE_READ", "PAYMENTS_READ"],
+)
+
+if auth_response.status != "completed":
+ print("Please complete the authorization challenge in your browser:")
+ print(auth_response.url)
+
+# Wait for the authorization to complete
+auth_response = client.auth.wait_for_completion(auth_response)
+
+# Do something interesting with the token...
+auth_token = auth_response.context.token
+```
+
+
+
+
+
+```javascript {20-21}
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade({ baseURL: "https://api.arcade.dev" }); // Automatically finds the `ARCADE_API_KEY` env variable
+
+const userId = "{arcade_user_id}";
+
+// Start the authorization process
+const authResponse = await client.auth.start(userId, "squareup", {
+ scopes: ["MERCHANT_PROFILE_READ", "PAYMENTS_READ"],
+});
+
+if (authResponse.status !== "completed") {
+ console.log("Please complete the authorization challenge in your browser:");
+ console.log(authResponse.url);
+}
+
+// Wait for the authorization to complete
+const response = await client.auth.waitForCompletion(authResponse);
+
+// Do something interesting with the token...
+const auth_token = response.context.token;
+```
+
+
+
+
+You can use the auth token to call the [Square API](https://developer.squareup.com/reference/square) endpoints to manage payments, orders, catalog items, and more.
+
+## Using Square auth in custom tools
+
+You can author your own [custom tools](/home/build-tools/create-a-mcp-server) that interact with Square API.
+
+Use the `OAuth2()` auth class to specify that a tool requires authorization with Square. The authentication token needed to call the Square API is available in the tool context through the `context.authorization.token` property.
+
+```python {9,17}
+from typing import Annotated
+
+import httpx
+
+from arcade_tdk import ToolContext, tool
+from arcade_tdk.auth import OAuth2
+
+
+@tool(requires_auth=OAuth2(provider_id="squareup", scopes=["PAYMENTS_READ"]))
+async def list_payments(
+ context: ToolContext,
+ location_id: Annotated[str, "The ID of the location to list payments for."],
+) -> Annotated[dict, "List of payments"]:
+ """Retrieves a list of payments for a Square location."""
+ url = "https://connect.squareup.com/v2/payments"
+ headers = {
+ "Authorization": f"Bearer {context.authorization.token}",
+ "Accept": "application/json",
+ "Square-Version": "2024-01-18",
+ }
+ params = {"location_id": location_id}
+
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url, headers=headers, params=params)
+ response.raise_for_status()
+ return response.json()
+```
+
+Your new tool can be called like demonstrated below:
+
+
+
+
+ If you are self-hosting, change the `base_url` parameter in the `Arcade` constructor to match your Arcade Engine URL. By default, the Engine will be available at `http://localhost:9099`.
+
+
+```python
+from arcadepy import Arcade
+
+client = Arcade(base_url="https://api.arcade.dev") # Automatically finds the `ARCADE_API_KEY` env variable
+
+USER_ID = "{arcade_user_id}"
+TOOL_NAME = "SquareUp.ListPayments"
+
+auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID)
+
+if auth_response.status != "completed":
+ print(f"Click this link to authorize: {auth_response.url}")
+
+# Wait for the authorization to complete
+client.auth.wait_for_completion(auth_response)
+
+tool_input = {
+ "location_id": "LOCATION123",
+}
+
+response = client.tools.execute(
+ tool_name=TOOL_NAME,
+ input=tool_input,
+ user_id=USER_ID,
+)
+print(response.output.value)
+```
+
+
+
+
+ If you are self-hosting, change the `baseURL` parameter in the `Arcade` constructor to match your Arcade Engine URL. By default, the Engine will be available at `http://localhost:9099`.
+
+
+```javascript
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade({ baseURL: "https://api.arcade.dev" }); // Automatically finds the `ARCADE_API_KEY` env variable
+
+const USER_ID = "{arcade_user_id}";
+const TOOL_NAME = "SquareUp.ListPayments";
+
+// Start the authorization process
+const authResponse = await client.tools.authorize({
+ tool_name: TOOL_NAME,
+ user_id: USER_ID,
+});
+
+if (authResponse.status !== "completed") {
+ console.log(`Click this link to authorize: ${authResponse.url}`);
+}
+
+// Wait for the authorization to complete
+await client.auth.waitForCompletion(authResponse);
+
+const toolInput = {
+ location_id: "LOCATION123",
+};
+
+const response = await client.tools.execute({
+ tool_name: TOOL_NAME,
+ input: toolInput,
+ user_id: USER_ID,
+});
+
+console.log(response.output.value);
+```
+
+
+
diff --git a/examples/code/integrations/airtable/config_provider.engine.yaml b/examples/code/integrations/airtable/config_provider.engine.yaml
new file mode 100644
index 000000000..88f39021f
--- /dev/null
+++ b/examples/code/integrations/airtable/config_provider.engine.yaml
@@ -0,0 +1,9 @@
+auth:
+ providers:
+ - id: arcade-airtable
+ description: "Arcade's default Airtable provider"
+ enabled: true
+ type: oauth2
+ provider_id: airtable
+ client_id: ${env:AIRTABLE_CLIENT_ID}
+ client_secret: ${env:AIRTABLE_CLIENT_SECRET}
diff --git a/examples/code/integrations/airtable/custom_auth.js b/examples/code/integrations/airtable/custom_auth.js
new file mode 100644
index 000000000..96e8d027a
--- /dev/null
+++ b/examples/code/integrations/airtable/custom_auth.js
@@ -0,0 +1,22 @@
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable
+
+const userId = "{arcade_user_id}";
+
+// Start the authorization process
+let authResponse = await client.auth.start(userId, "arcade-airtable", {
+ scopes: ["data.records:read", "data.records:write", "schema.bases:read"],
+});
+
+if (authResponse.status !== "completed") {
+ console.log("Please complete the authorization challenge in your browser:");
+ console.log(authResponse.url);
+}
+
+// Wait for the authorization to complete
+authResponse = await client.auth.waitForCompletion(authResponse);
+
+const token = authResponse.context.token;
+// TODO: Do something interesting with the token...
+
diff --git a/examples/code/integrations/airtable/custom_auth.py b/examples/code/integrations/airtable/custom_auth.py
new file mode 100644
index 000000000..bf350a114
--- /dev/null
+++ b/examples/code/integrations/airtable/custom_auth.py
@@ -0,0 +1,26 @@
+from arcadepy import Arcade
+
+client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable
+
+user_id = "{arcade_user_id}"
+
+# Start the authorization process
+auth_response = client.auth.start(
+ user_id=user_id,
+ provider="arcade-airtable",
+ scopes=["data.records:read", "data.records:write", "schema.bases:read"],
+)
+
+if auth_response.status != "completed":
+ print("Please complete the authorization challenge in your browser:")
+ print(auth_response.url)
+
+# Wait for the authorization to complete
+auth_response = client.auth.wait_for_completion(auth_response)
+
+if not auth_response.context.token:
+ raise ValueError("No token found in auth response")
+
+token = auth_response.context.token
+# TODO: Do something interesting with the token...
+
diff --git a/examples/code/integrations/airtable/custom_tool.py b/examples/code/integrations/airtable/custom_tool.py
new file mode 100644
index 000000000..f51b2f675
--- /dev/null
+++ b/examples/code/integrations/airtable/custom_tool.py
@@ -0,0 +1,27 @@
+from typing import Annotated
+
+import httpx
+
+from arcade_tdk import ToolContext, tool
+from arcade_tdk.auth import OAuth2
+
+
+@tool(requires_auth=OAuth2(id="arcade-airtable", scopes=["data.records:read", "data.records:write", "schema.bases:read"]))
+async def get_airtable_bases(
+ context: ToolContext,
+) -> Annotated[dict, "List of Airtable bases"]:
+ """Get all bases the authenticated user has access to."""
+ if not context.authorization or not context.authorization.token:
+ raise ValueError("No token found in context")
+
+ url = "https://api.airtable.com/v0/meta/bases"
+ headers = {
+ "Authorization": f"Bearer {context.authorization.token}",
+ "Content-Type": "application/json",
+ }
+
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url, headers=headers)
+ response.raise_for_status()
+ return response.json()
+
diff --git a/examples/code/integrations/calendly/config_provider.engine.yaml b/examples/code/integrations/calendly/config_provider.engine.yaml
new file mode 100644
index 000000000..0cf688554
--- /dev/null
+++ b/examples/code/integrations/calendly/config_provider.engine.yaml
@@ -0,0 +1,9 @@
+auth:
+ providers:
+ - id: arcade-calendly
+ description: "Arcade's default Calendly provider"
+ enabled: true
+ type: oauth2
+ provider_id: calendly
+ client_id: ${env:CALENDLY_CLIENT_ID}
+ client_secret: ${env:CALENDLY_CLIENT_SECRET}
diff --git a/examples/code/integrations/calendly/custom_auth.js b/examples/code/integrations/calendly/custom_auth.js
new file mode 100644
index 000000000..c613268c5
--- /dev/null
+++ b/examples/code/integrations/calendly/custom_auth.js
@@ -0,0 +1,22 @@
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable
+
+const userId = "{arcade_user_id}";
+
+// Start the authorization process
+let authResponse = await client.auth.start(userId, "arcade-calendly", {
+ scopes: ["default"],
+});
+
+if (authResponse.status !== "completed") {
+ console.log("Please complete the authorization challenge in your browser:");
+ console.log(authResponse.url);
+}
+
+// Wait for the authorization to complete
+authResponse = await client.auth.waitForCompletion(authResponse);
+
+const token = authResponse.context.token;
+// TODO: Do something interesting with the token...
+
diff --git a/examples/code/integrations/calendly/custom_auth.py b/examples/code/integrations/calendly/custom_auth.py
new file mode 100644
index 000000000..0c2f7681c
--- /dev/null
+++ b/examples/code/integrations/calendly/custom_auth.py
@@ -0,0 +1,26 @@
+from arcadepy import Arcade
+
+client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable
+
+user_id = "{arcade_user_id}"
+
+# Start the authorization process
+auth_response = client.auth.start(
+ user_id=user_id,
+ provider="arcade-calendly",
+ scopes=["default"],
+)
+
+if auth_response.status != "completed":
+ print("Please complete the authorization challenge in your browser:")
+ print(auth_response.url)
+
+# Wait for the authorization to complete
+auth_response = client.auth.wait_for_completion(auth_response)
+
+if not auth_response.context.token:
+ raise ValueError("No token found in auth response")
+
+token = auth_response.context.token
+# TODO: Do something interesting with the token...
+
diff --git a/examples/code/integrations/calendly/custom_tool.py b/examples/code/integrations/calendly/custom_tool.py
new file mode 100644
index 000000000..28ec9a06c
--- /dev/null
+++ b/examples/code/integrations/calendly/custom_tool.py
@@ -0,0 +1,27 @@
+from typing import Annotated
+
+import httpx
+
+from arcade_tdk import ToolContext, tool
+from arcade_tdk.auth import OAuth2
+
+
+@tool(requires_auth=OAuth2(id="arcade-calendly", scopes=["default"]))
+async def get_calendly_user(
+ context: ToolContext,
+) -> Annotated[dict, "Calendly user information"]:
+ """Get the authenticated user's Calendly profile information."""
+ if not context.authorization or not context.authorization.token:
+ raise ValueError("No token found in context")
+
+ url = "https://api.calendly.com/users/me"
+ headers = {
+ "Authorization": f"Bearer {context.authorization.token}",
+ "Content-Type": "application/json",
+ }
+
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url, headers=headers)
+ response.raise_for_status()
+ return response.json()
+
diff --git a/examples/code/integrations/miro/config_provider.engine.yaml b/examples/code/integrations/miro/config_provider.engine.yaml
new file mode 100644
index 000000000..1a77a870e
--- /dev/null
+++ b/examples/code/integrations/miro/config_provider.engine.yaml
@@ -0,0 +1,9 @@
+auth:
+ providers:
+ - id: arcade-miro
+ description: "Arcade's default Miro provider"
+ enabled: true
+ type: oauth2
+ provider_id: miro
+ client_id: ${env:MIRO_CLIENT_ID}
+ client_secret: ${env:MIRO_CLIENT_SECRET}
diff --git a/examples/code/integrations/miro/custom_auth.js b/examples/code/integrations/miro/custom_auth.js
new file mode 100644
index 000000000..ac25ce729
--- /dev/null
+++ b/examples/code/integrations/miro/custom_auth.js
@@ -0,0 +1,22 @@
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable
+
+const userId = "{arcade_user_id}";
+
+// Start the authorization process
+let authResponse = await client.auth.start(userId, "arcade-miro", {
+ scopes: ["boards:read", "boards:write"],
+});
+
+if (authResponse.status !== "completed") {
+ console.log("Please complete the authorization challenge in your browser:");
+ console.log(authResponse.url);
+}
+
+// Wait for the authorization to complete
+authResponse = await client.auth.waitForCompletion(authResponse);
+
+const token = authResponse.context.token;
+// TODO: Do something interesting with the token...
+
diff --git a/examples/code/integrations/miro/custom_auth.py b/examples/code/integrations/miro/custom_auth.py
new file mode 100644
index 000000000..70e441ea2
--- /dev/null
+++ b/examples/code/integrations/miro/custom_auth.py
@@ -0,0 +1,26 @@
+from arcadepy import Arcade
+
+client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable
+
+user_id = "{arcade_user_id}"
+
+# Start the authorization process
+auth_response = client.auth.start(
+ user_id=user_id,
+ provider="arcade-miro",
+ scopes=["boards:read", "boards:write"],
+)
+
+if auth_response.status != "completed":
+ print("Please complete the authorization challenge in your browser:")
+ print(auth_response.url)
+
+# Wait for the authorization to complete
+auth_response = client.auth.wait_for_completion(auth_response)
+
+if not auth_response.context.token:
+ raise ValueError("No token found in auth response")
+
+token = auth_response.context.token
+# TODO: Do something interesting with the token...
+
diff --git a/examples/code/integrations/miro/custom_tool.py b/examples/code/integrations/miro/custom_tool.py
new file mode 100644
index 000000000..49789cbc1
--- /dev/null
+++ b/examples/code/integrations/miro/custom_tool.py
@@ -0,0 +1,27 @@
+from typing import Annotated
+
+import httpx
+
+from arcade_tdk import ToolContext, tool
+from arcade_tdk.auth import OAuth2
+
+
+@tool(requires_auth=OAuth2(id="arcade-miro", scopes=["boards:read", "boards:write"]))
+async def get_miro_boards(
+ context: ToolContext,
+) -> Annotated[dict, "List of Miro boards"]:
+ """Get all boards the authenticated user has access to."""
+ if not context.authorization or not context.authorization.token:
+ raise ValueError("No token found in context")
+
+ url = "https://api.miro.com/v2/boards"
+ headers = {
+ "Authorization": f"Bearer {context.authorization.token}",
+ "Accept": "application/json",
+ }
+
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url, headers=headers)
+ response.raise_for_status()
+ return response.json()
+
diff --git a/examples/code/integrations/pagerduty/config_provider.engine.yaml b/examples/code/integrations/pagerduty/config_provider.engine.yaml
new file mode 100644
index 000000000..6b10c5910
--- /dev/null
+++ b/examples/code/integrations/pagerduty/config_provider.engine.yaml
@@ -0,0 +1,9 @@
+auth:
+ providers:
+ - id: arcade-pagerduty
+ description: "Arcade's default PagerDuty provider"
+ enabled: true
+ type: oauth2
+ provider_id: pagerduty
+ client_id: ${env:PAGERDUTY_CLIENT_ID}
+ client_secret: ${env:PAGERDUTY_CLIENT_SECRET}
diff --git a/examples/code/integrations/pagerduty/custom_auth.js b/examples/code/integrations/pagerduty/custom_auth.js
new file mode 100644
index 000000000..95b6049b0
--- /dev/null
+++ b/examples/code/integrations/pagerduty/custom_auth.js
@@ -0,0 +1,22 @@
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable
+
+const userId = "{arcade_user_id}";
+
+// Start the authorization process
+let authResponse = await client.auth.start(userId, "arcade-pagerduty", {
+ scopes: ["read", "write"],
+});
+
+if (authResponse.status !== "completed") {
+ console.log("Please complete the authorization challenge in your browser:");
+ console.log(authResponse.url);
+}
+
+// Wait for the authorization to complete
+authResponse = await client.auth.waitForCompletion(authResponse);
+
+const token = authResponse.context.token;
+// TODO: Do something interesting with the token...
+
diff --git a/examples/code/integrations/pagerduty/custom_auth.py b/examples/code/integrations/pagerduty/custom_auth.py
new file mode 100644
index 000000000..f7618d46e
--- /dev/null
+++ b/examples/code/integrations/pagerduty/custom_auth.py
@@ -0,0 +1,26 @@
+from arcadepy import Arcade
+
+client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable
+
+user_id = "{arcade_user_id}"
+
+# Start the authorization process
+auth_response = client.auth.start(
+ user_id=user_id,
+ provider="arcade-pagerduty",
+ scopes=["read", "write"],
+)
+
+if auth_response.status != "completed":
+ print("Please complete the authorization challenge in your browser:")
+ print(auth_response.url)
+
+# Wait for the authorization to complete
+auth_response = client.auth.wait_for_completion(auth_response)
+
+if not auth_response.context.token:
+ raise ValueError("No token found in auth response")
+
+token = auth_response.context.token
+# TODO: Do something interesting with the token...
+
diff --git a/examples/code/integrations/pagerduty/custom_tool.py b/examples/code/integrations/pagerduty/custom_tool.py
new file mode 100644
index 000000000..512940c31
--- /dev/null
+++ b/examples/code/integrations/pagerduty/custom_tool.py
@@ -0,0 +1,28 @@
+from typing import Annotated
+
+import httpx
+
+from arcade_tdk import ToolContext, tool
+from arcade_tdk.auth import OAuth2
+
+
+@tool(requires_auth=OAuth2(id="arcade-pagerduty", scopes=["read", "write"]))
+async def get_pagerduty_incidents(
+ context: ToolContext,
+) -> Annotated[dict, "List of PagerDuty incidents"]:
+ """Get all incidents from PagerDuty."""
+ if not context.authorization or not context.authorization.token:
+ raise ValueError("No token found in context")
+
+ url = "https://api.pagerduty.com/incidents"
+ headers = {
+ "Authorization": f"Bearer {context.authorization.token}",
+ "Accept": "application/vnd.pagerduty+json;version=2",
+ "Content-Type": "application/json",
+ }
+
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url, headers=headers)
+ response.raise_for_status()
+ return response.json()
+
diff --git a/examples/code/integrations/squareup/config_provider.engine.yaml b/examples/code/integrations/squareup/config_provider.engine.yaml
new file mode 100644
index 000000000..8d74fdfb2
--- /dev/null
+++ b/examples/code/integrations/squareup/config_provider.engine.yaml
@@ -0,0 +1,9 @@
+auth:
+ providers:
+ - id: arcade-squareup
+ description: "Arcade's default Square provider"
+ enabled: true
+ type: oauth2
+ provider_id: squareup
+ client_id: ${env:SQUARE_CLIENT_ID}
+ client_secret: ${env:SQUARE_CLIENT_SECRET}
diff --git a/examples/code/integrations/squareup/custom_auth.js b/examples/code/integrations/squareup/custom_auth.js
new file mode 100644
index 000000000..a101145d2
--- /dev/null
+++ b/examples/code/integrations/squareup/custom_auth.js
@@ -0,0 +1,22 @@
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade(); // Automatically finds the `ARCADE_API_KEY` env variable
+
+const userId = "{arcade_user_id}";
+
+// Start the authorization process
+let authResponse = await client.auth.start(userId, "arcade-squareup", {
+ scopes: ["MERCHANT_PROFILE_READ", "PAYMENTS_READ"],
+});
+
+if (authResponse.status !== "completed") {
+ console.log("Please complete the authorization challenge in your browser:");
+ console.log(authResponse.url);
+}
+
+// Wait for the authorization to complete
+authResponse = await client.auth.waitForCompletion(authResponse);
+
+const token = authResponse.context.token;
+// TODO: Do something interesting with the token...
+
diff --git a/examples/code/integrations/squareup/custom_auth.py b/examples/code/integrations/squareup/custom_auth.py
new file mode 100644
index 000000000..4cb858f73
--- /dev/null
+++ b/examples/code/integrations/squareup/custom_auth.py
@@ -0,0 +1,26 @@
+from arcadepy import Arcade
+
+client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable
+
+user_id = "{arcade_user_id}"
+
+# Start the authorization process
+auth_response = client.auth.start(
+ user_id=user_id,
+ provider="arcade-squareup",
+ scopes=["MERCHANT_PROFILE_READ", "PAYMENTS_READ"],
+)
+
+if auth_response.status != "completed":
+ print("Please complete the authorization challenge in your browser:")
+ print(auth_response.url)
+
+# Wait for the authorization to complete
+auth_response = client.auth.wait_for_completion(auth_response)
+
+if not auth_response.context.token:
+ raise ValueError("No token found in auth response")
+
+token = auth_response.context.token
+# TODO: Do something interesting with the token...
+
diff --git a/examples/code/integrations/squareup/custom_tool.py b/examples/code/integrations/squareup/custom_tool.py
new file mode 100644
index 000000000..23cc7c004
--- /dev/null
+++ b/examples/code/integrations/squareup/custom_tool.py
@@ -0,0 +1,28 @@
+from typing import Annotated
+
+import httpx
+
+from arcade_tdk import ToolContext, tool
+from arcade_tdk.auth import OAuth2
+
+
+@tool(requires_auth=OAuth2(id="arcade-squareup", scopes=["MERCHANT_PROFILE_READ", "PAYMENTS_READ"]))
+async def get_square_locations(
+ context: ToolContext,
+) -> Annotated[dict, "List of Square locations"]:
+ """Get all business locations for the authenticated Square account."""
+ if not context.authorization or not context.authorization.token:
+ raise ValueError("No token found in context")
+
+ url = "https://connect.squareup.com/v2/locations"
+ headers = {
+ "Authorization": f"Bearer {context.authorization.token}",
+ "Content-Type": "application/json",
+ "Square-Version": "2024-10-17",
+ }
+
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url, headers=headers)
+ response.raise_for_status()
+ return response.json()
+