diff --git a/.env.local.example b/.env.local.example
index 2fa613e3e..9872e55dd 100644
--- a/.env.local.example
+++ b/.env.local.example
@@ -14,3 +14,6 @@ ANTHROPIC_API_KEY=
NEXT_PUBLIC_ALGOLIA_APP_ID=
NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY=
NEXT_PUBLIC_ALGOLIA_INDEX_NAME=
+
+# Required for Contact Sales form submission (n8n → Attio pipeline)
+NEXT_PUBLIC_ATTIO_WEBHOOK_URL=
diff --git a/app/en/references/auth-providers/canva/page.mdx b/app/en/references/auth-providers/canva/page.mdx
new file mode 100644
index 000000000..f4c7a60ad
--- /dev/null
+++ b/app/en/references/auth-providers/canva/page.mdx
@@ -0,0 +1,324 @@
+import { Tabs, Callout, Steps } from "nextra/components";
+
+# Canva
+
+The Canva auth provider enables tools and agents to call [Canva APIs](https://developers.canva.com/docs/rest-api/) on behalf of a user using OAuth 2.0 authentication.
+
+
+ Want to quickly get started with Canva in your agent or AI app? The pre-built
+ [Arcade Canva MCP Server](/resources/integrations/development/canva) is what you
+ want!
+
+
+### What's documented here
+
+This page describes how to use and configure Canva auth with Arcade.
+
+This auth provider is used by:
+
+- The [Arcade Canva MCP Server](/resources/integrations/development/canva), which provides pre-built tools for interacting with Canva
+- Your [app code](#using-canva-auth-in-app-code) that needs to call the Canva API
+- Or, your [custom tools](#using-canva-auth-in-custom-tools) that need to call the Canva API
+
+### Required scopes for the Canva MCP Server
+
+If you're using the [Arcade Canva MCP Server](/resources/integrations/development/canva), you'll need to configure these scopes based on which tools you plan to use:
+
+- `file_content:read` - File structure, pages, nodes, and image exports
+- `library_content:read` - Published components, styles, and component sets from files
+- `team_library_content:read` - Team library content
+- `library_assets:read` - Individual component, style, and component set metadata
+- `file_comments:read` / `file_comments:write` - Reading and creating comments
+- `current_user:read` - User profile information
+- `projects:read` - Team projects and files (**requires private OAuth app**)
+
+
+ The `projects:read` scope is **only available in private Canva OAuth apps**.
+ If you need to access team projects and files, you must create a private OAuth
+ app through your Canva organization.
+
+
+For detailed descriptions of all available Canva OAuth scopes, refer to the [Canva OAuth Scopes documentation](https://developers.canva.com/docs/rest-api/scopes/).
+
+## Configuring Canva auth
+
+
+ When using your own app credentials, make sure you configure your project to
+ use a [custom user
+ verifier](/guides/user-facing-agents/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 Canva app credentials. This way, your users will see your application's name requesting permission.
+
+Before showing how to configure your Canva app credentials, let's go through the steps to create a Canva app.
+
+### Create a Canva app
+
+To integrate with Canva's API, you'll need to set up OAuth 2.0 authentication by creating an app in the Canva Developer Portal:
+
+
+
+#### Access the Canva Developer Portal
+
+Navigate to the [Canva Developer Portal](https://www.canva.com/developers/) and sign in with your existing Canva credentials or create a new account.
+
+#### Create a new app
+
+1. Once logged in, go to your developer dashboard and select "My Apps"
+2. Click on "Create a new app"
+3. Fill in the required details:
+ - **App Name**: Choose a descriptive name for your application
+ - **Website**: Provide the URL of your application's website
+ - **App Logo**: Upload a 100x100px PNG image representing your app
+
+#### Set up OAuth configuration
+
+1. After creating your app, you'll receive a `client_id` and `client_secret`
+2. Set the redirect URI to the redirect URL generated by Arcade (see configuration section below)
+3. Configure the required scopes for your application based on the tools you need:
+ - `file_content:read` - Read access to file content and structure
+ - `library_content:read` - Read access to published library content
+ - `team_library_content:read` - Read access to team library content
+ - `library_assets:read` - Read access to individual library assets
+ - `file_comments:read` - Read access to file comments
+ - `file_comments:write` - Write access to file comments
+ - `current_user:read` - Read access to user profile
+ - `projects:read` - Read access to team projects (private apps only)
+
+For a complete list of available scopes, refer to the [Canva OAuth Scopes documentation](https://developers.canva.com/docs/rest-api/scopes/)
+
+
+
+For detailed instructions, refer to Canva's official [Authentication documentation](https://developers.canva.com/docs/rest-api/authentication/).
+
+Next, add the Canva app to Arcade.
+
+## Configuring your own Canva Auth Provider in Arcade
+
+
+
+
+### Configure Canva 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 **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**.
+- Click **Add OAuth Provider** in the top right corner.
+- Select the **OAuth 2.0** tab at the top.
+
+#### Enter the provider details
+
+- Choose a unique **ID** for your provider (e.g. "canva").
+- Optionally enter a **Description**.
+- Enter the **Client ID** and **Client Secret** from your Canva app.
+- Configure the OAuth 2.0 endpoints:
+ - **Authorization URL**: `https://www.canva.com/oauth`
+ - **Token URL**: `https://api.canva.com/v1/oauth/token`
+ - **Scope Delimiter**: ` ` (space)
+ - **Use PKCE**: Enabled (S256)
+- Note the **Redirect URL** generated by Arcade. This must be set as your Canva app's redirect URI.
+
+#### Create the provider
+
+Hit the **Create** button and the provider will be ready to be used.
+
+
+
+
+
+
+
+### Configure Canva Auth Using Configuration File
+
+
+ This method is only available when you are [self-hosting the
+ engine](/guides/deployment-hosting/on-prem
+
+
+
+
+#### Set environment variables
+
+Set the following environment variables:
+
+```bash
+export CANVA_CLIENT_ID=""
+export CANVA_CLIENT_SECRET=""
+```
+
+Or, you can set these values in a `.env` file:
+
+```bash
+CANVA_CLIENT_ID=""
+CANVA_CLIENT_SECRET=""
+```
+
+#### Edit the Engine configuration
+
+Edit the `engine.yaml` file and add a new item to the `auth.providers` section:
+
+```yaml
+auth:
+ providers:
+ - id: canva
+ description: Canva OAuth 2.0 provider
+ enabled: true
+ type: oauth2
+ client_id: ${env:CANVA_CLIENT_ID}
+ client_secret: ${env:CANVA_CLIENT_SECRET}
+ oauth2:
+ scope_delimiter: " "
+ use_pkce: true
+ pkce_code_challenge_method: S256
+ authorize_request:
+ endpoint: "https://www.canva.com/oauth"
+ params:
+ response_type: code
+ client_id: "{{client_id}}"
+ redirect_uri: "{{redirect_uri}}"
+ scope: "{{scopes}} {{existing_scopes}}"
+ state: "{{state}}"
+ token_request:
+ endpoint: "https://api.canva.com/v1/oauth/token"
+ auth_method: client_secret_basic
+ params:
+ grant_type: authorization_code
+ redirect_uri: "{{redirect_uri}}"
+ request_content_type: application/x-www-form-urlencoded
+ response_content_type: application/json
+ refresh_request:
+ endpoint: "https://api.canva.com/v1/oauth/token"
+ auth_method: client_secret_basic
+ params:
+ grant_type: refresh_token
+ refresh_token: "{{refresh_token}}"
+ request_content_type: application/x-www-form-urlencoded
+ response_content_type: application/json
+```
+
+
+ **Note on `projects:read` scope:** If you need access to the `projects:read`
+ scope for team projects and files navigation, you must create a **private
+ Canva OAuth app**. This scope is not available in public OAuth apps. Learn
+ more in the [Canva OAuth Scopes
+ documentation](https://developers.canva.com/docs/rest-api/scopes/).
+
+
+
+
+
+
+
+When you use tools that require Canva auth using your Arcade account credentials, Arcade will automatically use this Canva OAuth provider. If you have multiple Canva providers, see [using multiple auth providers of the same type](/references/auth-providers#using-multiple-providers-of-the-same-type) for more information.
+
+## Using Canva auth in app code
+
+Use the Canva auth provider in your own agents and AI apps to get a user token for the Canva API. See [authorizing agents with Arcade](/get-started/about-arcade) to understand how this works.
+
+Use `client.auth.start()` to get a user token for the Canva API:
+
+
+
+
+```python {8-12}
+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="canva",
+ scopes=["file_content:read", "file_comments: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)
+
+token = auth_response.context.token
+# Do something interesting with the token...
+```
+
+
+
+
+
+```javascript {8-11}
+import { Arcade } from "@arcadeai/arcadejs";
+
+const client = new Arcade();
+
+const userId = "{arcade_user_id}";
+
+// Start the authorization process
+const authResponse = await client.auth.start(userId, "canva", [
+ "file_content:read",
+ "file_comments: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;
+// Do something interesting with the token...
+```
+
+
+
+
+
+## Using Canva auth in custom tools
+
+You can use the pre-built [Arcade Canva MCP Server](/resources/integrations/development/canva) to quickly build agents and AI apps that interact with Canva.
+
+If the pre-built tools in the Canva MCP Server don't meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server) that interact with the Canva API.
+
+Use the `Canva()` auth class to specify that a tool requires authorization with Canva. The `context.authorization.token` field will be automatically populated with the user's Canva token:
+
+```python {5,8}
+from typing import Annotated
+
+import httpx
+from arcade_tdk import ToolContext, tool
+from arcade_tdk.auth import Canva
+
+
+@tool(requires_auth=Canva(scopes=["file_content:read"]))
+async def get_canva_file(
+ context: ToolContext,
+ file_key: Annotated[str, "The Canva file key to retrieve."],
+) -> Annotated[dict, "The Canva file data."]:
+ """
+ Retrieve a Canva file by its key.
+ """
+ url = f"https://api.canva.com/v1/files/{file_key}"
+ headers = {
+ "Authorization": f"Bearer {context.authorization.token}",
+ }
+
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url, headers=headers)
+ response.raise_for_status()
+ return dict(response.json())
+```
+
+For a complete list of available Canva OAuth scopes and their descriptions, refer to the [Canva OAuth Scopes documentation](https://developers.canva.com/docs/rest-api/scopes/).
diff --git a/app/en/resources/contact-us/contact-cards.tsx b/app/en/resources/contact-us/contact-cards.tsx
index 292ae1ca9..0041a0a90 100644
--- a/app/en/resources/contact-us/contact-cards.tsx
+++ b/app/en/resources/contact-us/contact-cards.tsx
@@ -1,30 +1,269 @@
"use client";
import {
+ Button,
Dialog,
DialogContent,
+ DialogHeader,
+ DialogTitle,
Discord,
Github,
+ Input,
+ Textarea,
} from "@arcadeai/design-system";
-import { HeartPulse, Mail, Shield, Users } from "lucide-react";
+import {
+ AlertOctagon,
+ CheckCircle,
+ HeartPulse,
+ Mail,
+ Shield,
+ Users,
+} from "lucide-react";
import posthog from "posthog-js";
-import { useEffect, useRef, useState } from "react";
+import { useState } from "react";
+import { useForm } from "react-hook-form";
import { QuickStartCard } from "../../../_components/quick-start-card";
+const WEBHOOK_URL = process.env.NEXT_PUBLIC_ATTIO_WEBHOOK_URL;
+
+function getUtmParams(): Record {
+ if (typeof window === "undefined") {
+ return {};
+ }
+ const params = new URLSearchParams(window.location.search);
+ const utms: Record = {};
+ for (const key of [
+ "utm_source",
+ "utm_medium",
+ "utm_campaign",
+ "utm_content",
+ ]) {
+ const value = params.get(key);
+ if (value) {
+ utms[key] = value;
+ }
+ }
+ return utms;
+}
+
+function collectFields(data: FormValues): Record {
+ const fields: Record = {};
+ for (const [key, value] of Object.entries(data)) {
+ if (key !== "website" && value.trim()) {
+ fields[key] = value.trim();
+ }
+ }
+ return fields;
+}
+
+async function fireHoneypot(): Promise {
+ if (!WEBHOOK_URL) {
+ return;
+ }
+ try {
+ await fetch(WEBHOOK_URL, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ _hp: true }),
+ });
+ } catch {
+ // Swallow errors for spam submissions
+ }
+}
+
+async function submitToAttio(
+ fields: Record
+): Promise<{ success: boolean; error?: string }> {
+ if (!WEBHOOK_URL) {
+ return { success: false, error: "Webhook URL not configured" };
+ }
+
+ const payload = {
+ submission_id: crypto.randomUUID(),
+ form_type: "contact_sales",
+ fields,
+ context: {
+ pageUri: window.location.href,
+ pageName: "Contact Sales",
+ timestamp: new Date().toISOString(),
+ ...getUtmParams(),
+ },
+ };
+
+ const response = await fetch(WEBHOOK_URL, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(payload),
+ });
+
+ if (response.ok) {
+ return { success: true };
+ }
+ return {
+ success: false,
+ error: `HTTP ${response.status}: ${response.statusText}`,
+ };
+}
+
+type FormValues = {
+ firstname: string;
+ lastname: string;
+ email: string;
+ company: string;
+ message: string;
+ website: string;
+};
+
+function ContactSalesForm({ onSuccess }: { onSuccess: () => void }) {
+ const [submitError, setSubmitError] = useState("");
+ const {
+ register,
+ handleSubmit,
+ formState: { isSubmitting },
+ } = useForm();
+
+ const onSubmit = async (data: FormValues) => {
+ setSubmitError("");
+
+ // Honeypot — silently succeed for bots
+ if (data.website) {
+ await fireHoneypot();
+ onSuccess();
+ return;
+ }
+
+ const result = await submitToAttio(collectFields(data));
+
+ if (result.success) {
+ posthog.capture("contact_sales_form_submitted", {
+ form_type: "contact_sales",
+ page: "Contact Sales",
+ source: "contact_us_page",
+ });
+ onSuccess();
+ } else {
+ posthog.capture("contact_sales_form_submit_failed", {
+ form_type: "contact_sales",
+ page: "Contact Sales",
+ error: result.error,
+ });
+ setSubmitError("Oops! Something went wrong. Please try again.");
+ }
+ };
+
+ return (
+
+ );
+}
+
+function SuccessMessage({ onClose }: { onClose: () => void }) {
+ return (
+