# Welcome to Wavelength Source: https://docs.askassembly.app/introduction Wavelength is the AI-native customer intelligence platform built for post-sales teams. It unifies your conversational, usage, and CRM data into a single system, then uses AI agents to surface the signals that matter and let you act on them instantly. Instead of digging through CRM tables and switching between tools, your team can ask Wavelength directly about an account and get an answer, prioritize your book of business automatically based on customer signals, and turn insights into scaled, personalized outreach with a single click. These docs cover how to connect your data sources, set up account prioritization, build agent workflows, and get the most out of Wavelength across your team. What you'll find here Getting started — connect Salesforce, email, and your internal systems, and get your workspace set up Account intelligence — how Wavelength scores and prioritizes accounts based on signals across your stack Agents & workflows — build customizable workflows for your team, organization, or individual contributors Integrations — explore 40+ supported tools, from Slack to Zendesk If you can't find what you're looking for, contact us or reach out to your Wavelength team. # Integrations Source: https://docs.askassembly.app/quickstart Connect your tools to Wavelength. Navigate to the Manage Integrations page in the sidebar to view and configure all available integrations. Wavelength connects to the tools you already use, bringing all your customer context into one place. Below is a comprehensive list of all available integrations, organized by category. *** ## Communication Channels Connect your team communication tools to capture customer conversations, bugs, feature requests, and support threads. **Stay in sync with all customer bugs/issues, or feature request threads.** Click on the Slack integration and hit the enable button. After you have enabled the integration, select the customer channels or community channels sections and add the channels you'd like to collect support tickets or feedback from.
Private Channels
If you would like to add private customer channels or community channels to Wavelength's ticket inbox, (1) @Wavelength in a selected channel and then (2) reselect the same channel in the "select customer channels" dropdown.
Shortcuts Quick create Linear, Jira, or other external tickets directly from Slack by clicking the "+" button on the "create shortcut" section and selecting an emoji + destination to create the ticket.
**Stay in sync with all your community's bugs/issues, or feature request threads.** Connect a separate Slack workspace for your community. Works the same way as the customer Slack integration but designed for community-facing workspaces where you collect feedback from users and developers. **Stay up to date and interact with feature requests, bugs, and issues from your Discord community.** On the Manage Integrations page, click on the Discord integration and hit the "enable" button. Follow the sign-in instructions to link Discord to Wavelength.

After you have set up the integration, select the "customer channels" section and add the channels you'd like to collect support tickets or feedback from. Shortcuts To quick create Linear, Jira, or other external tickets directly from Discord, click the "+" button on the "create shortcut" section, select an emoji or "/" (slash) command, and a destination to create the ticket.
Moderators If your company email is not connected to your or your teams' Discord accounts, manually link all of these accounts to respond to Discord messages from the platform.
**Sync Gmail requests into Wavelength. You can add multiple Gmail accounts.** To connect a support email, hit the enable button and follow the sign-in instructions. In order to link a forwardable account or Google Group, create a group with an authenticable email and follow the same sign-in instructions as above.
Coming soon... **Turn your content into an AI search and support copilot with in-app embedded chat.** Once enabled, an API key will be generated. This will be used when embedding the iframe inside your application or on a webpage.

After this, follow the instructions on the chat widget integration page to complete installation.
*** ## Support Systems Integrate with your help desk and support platforms to sync tickets bi-directionally. **Auto-sync tickets from Zendesk to and from Wavelength.** Connect your Zendesk account to enable bi-directional ticket synchronization. Tickets created in Zendesk will appear in Wavelength, and you can manage support workflows across both platforms seamlessly. * Automatically import Zendesk tickets into Wavelength * Sync ticket status, comments, and updates in real-time * Map Zendesk ticket fields to Wavelength fields **Sync Intercom support requests into Wavelength.** Connect your Intercom account to bring customer conversations and support requests into Wavelength. This allows your team to have full visibility into customer interactions alongside other data sources. * Import Intercom conversations as tickets * Sync customer data and conversation history * Track support interactions across all channels *** ## Ticketing Systems Connect your issue tracking and project management tools to auto-generate tickets from conversations. **Auto-generate Jira tickets from Slack or Discord. Sync tickets by project mapped to a specific account.** Connect your Jira account to enable automatic ticket creation from customer conversations. When a customer reports a bug or feature request in Slack or Discord, you can instantly create a Jira ticket with a slash command or emoji reaction. * Create Jira tickets directly from Slack/Discord with shortcuts * Map projects to specific customer accounts * Sync ticket status and updates back to Wavelength * Automatically link conversations to Jira issues **Auto-generate Linear tickets from Slack or Discord. Sync tickets by project mapped to a specific account.** Connect your Linear workspace to streamline your issue tracking workflow. Create Linear issues directly from customer conversations without leaving your communication tools. * Create Linear issues from Slack/Discord with shortcuts * Sync ticket status bidirectionally * Map Linear teams and projects to customer accounts * Automatically populate issue details from conversation context **Stay up to date with GitHub issues and discussions, and automate GitHub issue creation with a slash "/" command or emojis.** To connect your GitHub, ensure you have admin privileges to enable the integration. Hit the "enable" button and follow the sign-in instructions. Select the repositories Wavelength will have limited access to with approval if necessary. Once the integration is enabled, select the "repositories" (the customer channels) whose activity will come to Wavelength.
Moderators If your company email is not connected to your or your teams' GitHub accounts, manually link all of these accounts to respond to GitHub issues.
*** ## CRM & Customer Data Connect your CRM systems to enrich customer context with account data, contacts, opportunities, and more. **Connect your Salesforce account to see customer data in Wavelength.** Enable the Salesforce integration
On the Manage Integrations page, click on the Salesforce integration and hit the enable button. Follow the sign-in instructions to connect Salesforce to Wavelength.

Select which Salesforce objects to sync
After enabling the integration, Wavelength will present a table mapping step where you can choose which Salesforce objects you want to bring into Wavelength. You can map Accounts, Contacts, Opportunities, and any custom objects from Salesforce.

Review how objects map into Wavelength
The Salesforce objects you select will be mapped to their Wavelength equivalents: Accounts → Accounts, Contacts → Contacts, Opportunities → Deals, and custom objects → custom objects in Wavelength. Review these mappings to ensure they match how your team works.

Use auto-mapping for fields
You can use Wavelength's auto-mapping to quickly align common Salesforce fields (such as name, owner, status, and amounts) to Wavelength fields. Adjust any individual field mappings as needed before saving to finalize your Salesforce integration.
**Connect your HubSpot account to see customer data in Wavelength.** Sync your HubSpot CRM data including contacts, companies, and deals into Wavelength. This enables your team to see the full customer picture when handling support requests and conversations. * Import HubSpot contacts and companies * Sync deal stages and pipeline data * Map HubSpot properties to Wavelength fields * Enable customer context in conversations
*** ## Usage Data & Analytics Connect your product analytics and business intelligence tools to understand customer behavior and usage patterns. **Connect your PostHog account to see usage data in Wavelength.** Bring product analytics data from PostHog into Wavelength to understand how customers are using your product. Correlate usage patterns with support requests and customer conversations. * View user session data and events * Understand feature adoption by account * Correlate usage patterns with support tickets **Connect Pendo to enable product analytics in Wavelength.** Integrate your Pendo account to access product usage analytics, feature adoption data, and user behavior insights directly within Wavelength. * Access product analytics alongside customer data * Track feature adoption by customer segment * Understand user engagement patterns **Sync Stripe subscriptions and revenue data into Wavelength.** Connect your Stripe account to bring subscription and revenue data into your customer view. Understand MRR, subscription status, and billing history for each account. * View subscription details by account * Track MRR and revenue metrics * See billing history and payment status * Correlate revenue with support and engagement data **Pull in Looker dashboards and data into accounts.** Connect Looker to bring business intelligence dashboards and data into your account views. We recommend authenticating with a service account for better permission control. * Embed Looker dashboards in account views * Access Looker data within Wavelength * Configure which dashboards are visible per account **Connect BigQuery to enable data warehouse analytics in Wavelength.** Integrate your BigQuery data warehouse to access custom analytics, usage metrics, and business data directly within Wavelength. * Query BigQuery datasets alongside customer data * Build custom analytics views per account * Access data warehouse insights in context *** ## Meeting Intelligence & Note Takers Connect AI meeting assistants to automatically transcribe, analyze, and sync meeting notes with customer accounts. **Connect your Gong account to see customer data in Wavelength.** Sync call recordings, transcripts, and conversation intelligence from Gong. Access sales and customer success call insights directly within customer account views. * Import call recordings and transcripts * Sync conversation insights and topics * Link calls to customer accounts * Surface key moments from customer conversations **Connect your Fireflies account to automatically transcribe and analyze your meetings in Wavelength.** Bring meeting transcriptions and AI-generated summaries from Fireflies into Wavelength. Link meetings to customer accounts for a complete view of all customer interactions. * Auto-sync meeting transcripts * Access AI meeting summaries * Link meetings to customer accounts * Search across all meeting content **Connect your Attention account to enable AI note-taking capabilities in Wavelength.** Integrate Attention's AI-powered meeting notes and insights. Automatically capture action items, key topics, and customer sentiments from your meetings. **Connect your Fathom account to see meeting notes and insights in Wavelength.** Integrate Fathom's AI-powered meeting recorder to automatically capture transcripts, summaries, and action items from your customer calls. * Auto-sync meeting recordings and transcripts * Access AI-generated meeting summaries * Track action items from customer calls **Connect your Granola account to see meeting notes and utilize their content within health scores & agents in Wavelength.** *** ## Knowledge Base & Documentation Connect your documentation and knowledge management tools to enable AI-powered search and answers. **Ingest your Confluence documents (BETA).** Connect Confluence to bring your team's documentation into Wavelength's AI search. Enable your support team and AI assistants to find and reference internal knowledge. * Index Confluence spaces and pages * Enable AI-powered search across documentation * Reference docs in customer conversations **Automatically search through and answer questions about your Notion knowledge base.** Connect your Notion workspace to enable AI-powered search across your documentation, wikis, and knowledge base content. * Index Notion pages and databases * Enable natural language search * Use Notion content for AI-assisted responses **Automatically search through and answer questions about your web docs.** Add URLs to your public documentation, help center, or marketing site. Wavelength will index the content and enable AI-powered search and question answering. * Index public documentation sites * Enable AI search across web content * Keep content synced with automatic re-indexing *** ## Scheduling & Calendar Connect your calendar tools to see meeting context alongside customer data. **Connect your Google Calendar accounts to see your events in Wavelength. You can add multiple calendar accounts.** Sync your Google Calendar to see upcoming and past meetings with customers. Link calendar events to customer accounts for complete meeting history. * View upcoming customer meetings * See meeting history by account * Add multiple calendar accounts *** ## Data Sources Connect spreadsheet and data tools to import customer data. **Connect your Google Sheet account to see customer data in Wavelength.** Import data from Google Sheets to populate customer accounts or enrich existing data. Useful for importing customer lists, CSVs, or maintaining manual data alongside automated syncs. * Import customer data from spreadsheets * Map sheet columns to Wavelength fields * Keep data synced with automatic updates *** ## API Integration Build custom integrations using the Wavelength API. **Generate API keys to build custom integrations with Wavelength.** Access the API key management page to generate and manage API keys for programmatic access to Wavelength. Build custom integrations, sync data from proprietary systems, or automate workflows. * Generate and manage API keys * Access REST API documentation * Build custom data pipelines # Setting Up Source: https://docs.askassembly.app/setting-up ## Authentication Wavelength supports flexible sign-in options so your team can log in the way that fits your existing identity setup. You can sign in with Google via OAuth for one-click access using your existing Google account, no separate password required. For organizations with centralized identity management, Wavelength supports Single Sign-On (SSO), letting your team authenticate through your company's identity provider. Email and password login is also available for teams that prefer it, with self-serve password reset built in. Image ## Creating an Organization After you sign in for the first time, you'll be prompted to create an organization or join an existing one. To create a new organization, enter an organization name — this is visible to other users in your organization and anyone you invite. Image You can also control how teammates join using two optional settings: * **Any user with an \[company] email can join without approval** — lets anyone with a matching company email domain join your organization automatically, no invite required * **Restrict membership to only users with an \[company] email** — locks membership to your company's email domain, preventing outside users from joining If your team already has an organization set up in Wavelength, select **Join an existing organization** instead of creating a new one. # Analytics Source: https://docs.askassembly.app/usage/analytics Get in depth metrics of how your support, success, and CS organization are performing and how your customers feel about their interactions. Wavelength has tons of default and custom analytics dashboards that you have access to ### Analytics 101 Without customization, you can view the following for you entire organization: * Total interactions * Total bugs * Total feature requests * Total questions asked * Total escalations * Total resolutions * Average Response Time (per day): This is calculated by finding the average time between when all tickets move from needs response to the open status * Average Resolution Time: The average across all tickets for when a ticket is first created to when it goes to the closed status state * Time to First Response: The average time a reply is given a support ticket when it is first created * SLA Breach Duration: If SLA's are setup, this metric will calculate the total average time tickets stay in the breached status state # Overview Source: https://docs.askassembly.app/usage/customerSuccess/Overview Customer success is intrinsically linked to the support process. We hope to build the bridge between support tickets and genuine customer interactions # Announcements Source: https://docs.askassembly.app/usage/customerSuccess/announcements The announcements page allows you to broadcast product updates, send renewal emails, newsletters and more directly on the platform.

To draft an announcement, click on the “create an announcement” button on the page. Type in the name of the announcement and choose where you’d like to send the announcement to. You can send the announcement to a “customer group”, your entire customer base, a specific set of customers, or an individual one as well! Note, the announcement page supports real-time collaboration! Anyone can type and draft parts of your broadcast at the same time!

# Accounts Table Source: https://docs.askassembly.app/usage/customerSuccess/companies The companies table is a collection of all the customer profiles you have interacted with! #### Setup Other fields that natively are available within Wavelength include renewal date, account labels, last interaction time, and the account assignee

To add more fields to the table or edit existing values, right click on the customer and hit “edit”
You can also points of contact to existing customer profiles after clicking on the customer profile and hitting the “add contact” button #### Customer 360 The customer 360 is meant to be the one true dashboard for any account. It includes usage data, recent interactions, news about the account, as well as any open milestones. At the top, Wavelength’s LLM generates a suggested action based on all of the customer data it has available to assist CSM’s in interacting with customers at scale.

#### Contacts Using the account page menu bar, you can quickly navigate to the company’s contacts. This shows you the people on the account, how you’ve interacted with them, as well as when. #### Interactions The account interactions page displays all of the communication (outbound or inbound), across all channels, that you have had with the account. Easily view open support tickets, escalated bugs, and more. #### Milestones Tracking your customer’s journey on your platform is a main goal of using Wavelength. Milestones can be configured to track your customers onboarding journey, how quickly they find value in your tool, and their power user metrics. #### Tasks A collection of tasks makes a milestone! To create a task, simply use the “+” button at the end of the milestone sheet. Make sure to name, describe, and assign the task an owner and a due date! Last but not least, track the status of your task in the status column! This refers to what will start the workflow and can be things like a new support request tagged as a bug. Now you will setup the rules for this workflow. #### Creating Milestones To create a milestone, click the “+Add Milestone” button on the main page. When creating a Milestone, make sure to assign it to a team member, create both start and end dates, as well as a description.

When the tasks in a milestone are complete, the milestone’s status will change accordingly. #### Notes Using the account page menu bar, you can quickly navigate to the company’s contacts. This shows you the people on the account, how you’ve interacted with them, as well as when. #### Attributes In order to see additional information (attributes) about the account, navigate to the attributes page via the accounts menu bar. There, you will be able to see more information synced from your CRM (see integration support), imported fields, and other values not displayed on the accounts table In order to edit the attributes, simply right click (two-finger on Mac) to edit the values! # Product Insights Source: https://docs.askassembly.app/usage/customerSuccess/productInsights The product insights tab is meant to show high level themes and trends after analyzing all the support tickets that exist within the Wavelength system.

#### Creating Insights To create an insight, click on the “create insight” button on the top right of the product insights.

Type in the title of the insight and add a detailed description if possible! To attach existing support interactions to this insight, click the “related issues” button and manually search + add tickets to this insight. Or.. click the “sparkle” icon to use AI to link issues based on the description for you! Once you save this insight, any new support interactions that match the description of the insight will automatically be linked. #### Generating AI Insights Every time you navigate to the product insights tab, our AI will automatically generate a set of insights for you. To see more than top 3 insights being displayed, click the “generate more insights button



To save a generated insight, tap the “bookmark” icon on the insight page or on it’s row in the insights table # Accounts Source: https://docs.askassembly.app/usage/developer/accountsAPI Send events from your own integrations to Wavelength. This is a great way to track and update your accounts. ## API Key You must first create an API Key in Wavelength. It is located under "Manage Integrations", then scroll to the bottom and click "Manage API Keys". Here, you can see your existing keys, their names and expiration. You can delete existing ones. When creating a new API key, the name must be unique in your organization. Once the key has been created, you will be shown a one-time dialog box with your new API key. You should copy this down somewhere safe -- we will not show this again. If you lose your API key, you will need to make a new one. ## Accounts Maintain your accounts through these API endpoints. Accounts types include: Company or Customer. In general you can refer to an account by appending its Wavelength ID or domain to the URL. Or you can add the source specific ID of the account as a query parameter. #### Create Account Required attributes are `type`, `name`, and `domain`. The domain for a Company Account should be a domain i.e. `company.com` while the domain for a Customer Account should be an email i.e. `name@company.com`. There are various optional attributes that you can add to define the account. The possible attributes are shown in the example below. You can also add source specific IDs that will then act as another id for you to reference the account with in lieu of using our Wavelength ID for the account. Note there are attributes specific to Company versus Customer Accounts: * Company Specific Attributes: `company_type` and `number_of_seats` * Customer Specific Attributes: `role` The Wavelength ID of the created account is returned in the format `{"id":"01945162-52fc-739b-98de-e2ac9f2eec49"}` ```bash theme={null} curl -X \ POST https://api.wavelength.cx/accounts \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "type": "Company", "name": "account_name", "domain": "account.com", "source_specific_ids": ["accountSSID"], # Optional "image_url": "https://cdn.prod.website-files.com/673724d522a7665c49daddc6/67376a99be79e6944889418a_main-logo.svg", # Optional "phone": ["123456789"], # Optional "contract_value": 100, # Optional "contract_type": "Month", # Optional - either "Month" or "Year" "renewal_date": "2027-12-31", # Optional "trial_expiration_date": "2025-07-31", # Optional "tier": "8780fd65-3b8d-4c06-891b-636acda3a71c", # Optional - ID of the tier label "stage": "c738534b-62d6-4f0a-aee1-3a5c6d860d86", # Optional - ID of the stage label "tags": ["0195a666-3405-7664-aaf1-ad65b6091e96", "0195af0e-3213-78fc-a3cf-cb8c192abeaf"], # Optional - List of account tag labels to include "notes": "A note about the account", # Optional "id_in_tool": "idA", # Optional - the ID of the company/user in your own tool for reference "additional_properties": { # Optional - a key-value mapping of custom attributes specific to the account "Login_count": 2, "Timezone": "America/Los_Angeles" }, # Only supported for company accounts "company_type": "0195a67e-1ac2-748f-9f8e-709449c2e68a", # Optional - ID of the company type label "number_of_seats": 20, # Optional # Only supported for customer accounts "role": "support", # Optional - The role/domain of the customer }' ``` #### Get Accounts You can grab all the accounts for your organization by calling `accounts`. This call is paginated, so please include the limit and offset in your URL otherwise the default `limit=100` and `offset=0` will be used. The return type is `{"accounts": {"data": [...], "has_next_page": ..., "next_cursor": ...}}`. This endpoint returns **Company Accounts only**. It used to fill the rest of a page with Customer Accounts (contacts) once it ran out of companies, so a single page could contain both. It no longer does. To list contacts, use the `contacts` endpoints below. Every row comes back with `"account_type": "Company"`. The accounts are sorted alphabetically by account name. Each account will include multiple attributes in a key-value format. The attributes include `id` (the assembly id), `name`, `domain`, `image_url`, `contract_value`, `contract_type`, `account_type`, `source_specific_ids`, and others. An account can have more than one source specific ID, so the list endpoint returns **two** fields for them: * `source_specific_ids` — the full list. Each entry is an object with `id` (the Wavelength id of the link), `source_specific_id` (your external id), and `source` (where it came from, e.g. `API`, `HubSpot`, `Salesforce`). The list is empty (`[]`) when the account has none. * `source_specific_id` — a single one of those ids, as a plain string, for tools that cannot map a list field. It is the id you sent us through this API (`source` = `API`) when the account has one; otherwise it is the first id in the list. It is `null` when the account has none. ```json theme={null} "source_specific_ids": [ {"id": "6e668fb6-1b09-440f-bfc7-a07dc2898a04", "source_specific_id": "488dc44e-b9b2-4f0c-90d6-c2ae7414e600", "source": "API"} ], "source_specific_id": "488dc44e-b9b2-4f0c-90d6-c2ae7414e600" ``` Use `source_specific_ids` if you care about which system an id came from. `source_specific_id` is a convenience only — when an account has ids from several systems, it gives you one of them and hides the rest. `source_specific_id` is the field to match on when you are syncing accounts in and out of another system: it holds the id **your** system assigned, so an update-or-create sync keyed on it updates the account you already have rather than creating a second one. Matching on the Wavelength `id` instead means storing a UUID your system has never seen. Each account also comes back with an `attributes` object — every custom attribute in your org, keyed by a stable slug, flat and individually addressable. See [Attributes](#attributes) for the full shape. Company Accounts also include an **`account_cta`** field — the latest AI-generated brief and call-to-action for the account, or `null` when none has been generated yet. It is not populated for Customer Accounts, which always return `null`. ```json theme={null} { "name": "11x", "domain": "11x.ai", "account_cta": null } ``` When a brief exists, `account_cta` is the whole record: ```json theme={null} "account_cta": { "id": "0198c4a1-7b2e-7f10-9c31-6d2f5a0e77bd", "org_id": "d7bd9178-3af1-4276-870e-9b539a970ed2", "company_id": "ca8c9cd8-e159-4a1f-977a-4cfcc7e007e4", "customer_id": null, "created_at": "2026-08-12T09:04:19.291588Z", "content": "The data import just wrapped — and the adoption report Dana wanted is finally buildable.", "action_type": "cross_sell_outreach", "metadata": { "color": "green", "account_summary": "Mid-cycle Growth-plan account ~7 months from renewal, steady weekly usage; last touchpoint the June 30 call with Dana about the now-complete data import. Single-threaded through Dana — the main thing to watch.", "description": "Mid-cycle Growth-plan account ~7 months from renewal, steady weekly usage; last touchpoint the June 30 call with Dana about the now-complete data import. Single-threaded through Dana — the main thing to watch.", "draft": "…", "email_brief": "…", "insight_id": "3f7b1c88-…", "generated_by": "…", "generation_id": "…", "generated_at": "2026-08-12T09:04:19Z" } } ``` The fields worth building against: * **`content`** — the headline. One short, specific sentence about this account, written to be read at a glance. * **`account_summary`** (under `metadata`) — the briefing: two or three plain sentences on how the customer is doing and the one thing to watch next. This is the field to show when you want more than the headline. * **`metadata.description`** — a legacy mirror of `account_summary`, kept so older integrations keep working. Prefer `account_summary`; the two hold the same text. * **`action_type`** — what kind of follow-up is suggested, e.g. `cross_sell_outreach`. Some action types add their own keys under `metadata` (a cross-sell brief also carries `next_best_action`, `nba_type` and `company_name`). * **`metadata.color`** — `green`, `blue`, `orange` or `red`. It is the read on the account, independent of whether there is an email to send: a healthy account you just spoke to stays green. * **`created_at`** — re-stamped on every generation, so it means "last generated at", not "first created". Two things to know before you store any of it: * **`account_cta` is an open object.** New keys get added as the generation improves, and they will appear here without warning. Read the keys you need and ignore the rest rather than validating against a fixed schema. * **`account_cta.id` is not stable.** Each generation replaces the account's brief in place, and the id is rewritten with it. Key off the account, not off this id. ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/accounts?limit=50&offset=0" \ -H "Authorization: " ``` If wanted, you can include a name filter which is applied to account names and domains. Name comparisons match the search bar's results on the Wavelength Accounts Page. To include names with spaces, make sure that the spaces are url encoded like in this example where the name query is "test company". ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/accounts?name=test%20company&limit=50&offset=0" \ -H "Authorization: " ``` You can also pass an optional `updated_after` query parameter to return only accounts updated at or after a given time (`updated_at >= updated_after`) — useful for incremental sync. It accepts an RFC3339 timestamp (e.g. `2026-06-01T00:00:00Z`) or a bare date (`2026-06-01`, treated as UTC midnight). Accounts with no `updated_at` are excluded when the filter is set, and an invalid value returns `400 - invalid updated_after parameter`. The filter applies to the list path (with or without `name`). It covers Company Accounts, since that is all this endpoint returns — for contacts, pass `updated_after` to the `contacts` endpoints instead. For Company Accounts, `updated_after` also matches when the account's `account_cta` was created or updated — even if nothing else on the account changed. When a Company Account matches, the response includes the full account with its current `account_cta`. ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/accounts?updated_after=2026-06-01T00:00:00Z&limit=50&offset=0" \ -H "Authorization: " ``` To grab a specific account, you can use the ID from Wavelength, the domain of the account, or a source specific ID that you've already linked to the account. This call is not paginated since you are only grabbing a specific account. The account is returned in this format `{"account":{...}}`. Note that the source specific ID format is a bit different since it needs to be passed in as a query parameter. ```bash theme={null} curl -X \ GET https://api.wavelength.cx/accounts/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET https://api.wavelength.cx/accounts/account.com \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/accounts?source_specific_id=accountSSID" \ -H "Authorization: " ``` #### Search Accounts Search and filter accounts by additional properties, name, or domain with pagination support. This endpoint provides more advanced filtering capabilities compared to the standard GET accounts endpoint. Unlike `GET /accounts`, this endpoint still returns **both** Company Accounts and Customer Accounts (contacts) in one list — companies first, then contacts. Check `account_type` on each row to tell them apart. **Request Body Structure:** ```json theme={null} { "additional_properties": [ { "name": "string", "value": "string" } ], "name": "string (optional)", "domain": "string (optional)", "limit": "integer (optional, default 50, max 1000)", "offset": "integer (optional, default 0)", "updated_after": "string (optional, RFC3339 or YYYY-MM-DD)" } ``` **Field Descriptions:** * **additional\_properties** (optional, array): Filter by attribute name and exact value match * **name** (required if additional\_properties provided): Attribute name * **value** (required if additional\_properties provided): Exact value to match * **name** (optional): Filter by account name (case-insensitive partial match) * **domain** (optional): Filter by account domain (case-insensitive partial match) * **limit** (optional): Number of results per page (1-1000, default 50) * **offset** (optional): Number of results to skip (default 0) * **updated\_after** (optional): Return only accounts updated at or after this time (`updated_at >= updated_after`). Accepts an RFC3339 timestamp (`2026-06-01T00:00:00Z`) or a bare date (`2026-06-01`, treated as UTC midnight). Accounts with no `updated_at` are excluded when set; an invalid value returns `400 - invalid updated_after parameter`. For Company Accounts, this also matches when the account's `account_cta` was created or updated, even if nothing else changed. **Response Structure:** ```json theme={null} { "accounts": { "data": [ { "account": { "id": "uuid", "name": "string", "domain": "string", "type": "Company|Customer", "attributes_cached": [...] } } ], "has_next_page": "boolean", "next_cursor": "integer" } } ``` **Example Usage:** ```bash theme={null} # Search by additional property curl -X POST "https://api.wavelength.cx/accounts/search" \ -H "Authorization: " \ -H "Content-Type: application/json" \ -d '{ "additional_properties": [{ "name": "Account Name", "value": "Sample Company" }], "limit": 50, "offset": 0 }' # Search by name + additional property curl -X POST "https://api.wavelength.cx/accounts/search" \ -H "Authorization: " \ -H "Content-Type: application/json" \ -d '{ "name": "Acme", "additional_properties": [{ "name": "Account Name", "value": "Sample Company" }], "limit": 50, "offset": 0 }' # Incremental sync - accounts changed at or after a timestamp curl -X POST "https://api.wavelength.cx/accounts/search" \ -H "Authorization: " \ -H "Content-Type: application/json" \ -d '{ "updated_after": "2026-06-01T00:00:00Z", "limit": 50, "offset": 0 }' ``` **Error Responses:** * **400 Bad Request**: Invalid request body, missing required fields, or pagination limits out of bounds * **401 Unauthorized**: Invalid or missing API key * **500 Internal Server Error**: Database or server errors **Important Notes:** 1. If `additional_properties` is provided, both `name` and `value` must be present for each filter 2. Multiple `additional_properties` filters are combined with AND logic 3. `name` and `domain` filters use case-insensitive partial matching 4. Pagination follows the same pattern as GET /accounts endpoint 5. Response format is identical to GET /accounts for consistency #### Update Account Update any of the account fields with this method. You can update an account via the Wavelength ID, the domain of the account, or with a source specific ID that you've already linked to the account. In the input arguments, include any of the fields that you want to update. For updating additional properties, if the key already exists on the account then the value of that key will be updated, otherwise the whole key value pair will be added. If you want more granular actions for additional properties, please use the Additional Properties Endpoints. All the allowed parameters are the same as the `CREATE` endpoint except there are additional parameters for tags and source specific IDs. The general `tags` will be tags that are being added and then there's an additional `removed_tags` to indicate removing a tag. Similarily, the `source_specific_ids` will be added while the 'removed\_source\_specific\_ids' will be removed. The Wavelength ID of the updated account is returned in the format `{"id":"01945162-52fc-739b-98de-e2ac9f2eec49"}`. ```bash theme={null} curl -X \ PUT https://api.wavelength.cx/accounts/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "name": "New Account Name", "tags": ["18eb8ad2-b538-420a-80e5-7827429f3ed3"], "removed_tags": ["0195a666-3405-7664-aaf1-ad65b6091e96"], "source_specific_ids": ["newAccountSSID"], "removed_source_specific_ids": ["accountSSID"], "contract_value": 50000.00, "contract_type": "Year", "additional_properties": { "existingKey": "newValue", "newKey": "newValue" } }' ``` #### Upsert Account A combination of the create and the update endpoints! If no Wavelength ID, domain, nor source specific ID is passed in or if the values pass in don't map to an existing account, then the account will be created. Otherwise, the existing account will be updated. The Wavelength ID of the updated or created account is returned in the format `{"id":"01945162-52fc-739b-98de-e2ac9f2eec49"}`. We recommend always inputting the required attributes for create (`type`, `name`, and `domain`) so that the endpoint won't fail when the account needs to be created. ```bash theme={null} curl -X \ PUT https://api.wavelength.cx/accounts/upsert/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "type": "Company", "name": "account_name", "domain": "account.com" }' ``` ```bash theme={null} curl -X \ PUT "https://api.wavelength.cx/accounts/upsert?source_specific_id=accountSSID" \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "type": "Company", "name": "account_name", "domain": "account.com" }' ``` #### Delete Account Delete an account by referencing the account with its Wavelength ID, domain, or source specific ID. The return is empty `{}`. ```bash theme={null} curl -X \ DELETE https://api.wavelength.cx/accounts/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Authorization: " ``` ## Contacts Endpoints for managing your contacts (customers who are apart of a company account). These endpoints are similar in format to the Accounts APIs. In general you can refer to a contact by appending its Wavelength ID or domain to the URL. Or you can add the source specific ID of the contact as a query parameter. #### Create Contact Required attributes are `name`,`domain` (email i.e. `name@company.com`), and some sort of company identifier. For the company identifier, you need to include the Wavelength ID of the company in `company_id`, the domain of the company in `company_domain`, or the source specific ID of the company in `company_source_specific_id`. There are various optional attributes that you can add to define the contact, all the options are shown below. You can also add source specific IDs that will then act as another id for you to reference the contact with in lieu of using our Wavelength ID for the contact. The Wavelength ID of the created contact is returned in the format `{"id":"01966e6b-3219-78be-a0fe-260e590565f5"}` ```bash theme={null} curl -X \ POST https://api.wavelength.cx/contacts \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "name": "contact_name", "domain": "name@company.com", # 1 of the 3 company values below must be included "company_id": "01945162-52fc-739b-98de-e2ac9f2eec49", "company_domain": "company.com", "company_source_specific_id": "companySSID", "source_specific_ids": ["contactSSID"], # Optional "image_url": "https://cdn.prod.website-files.com/673724d522a7665c49daddc6/67376a99be79e6944889418a_main-logo.svg", # Optional "phone": ["123456789"], # Optional "contract_value": 100, # Optional "contract_type": "Month", # Optional - either "Month" or "Year" "renewal_date": "2027-12-31", # Optional "trial_expiration_date": "2025-07-31", # Optional "tier": "8780fd65-3b8d-4c06-891b-636acda3a71c", # Optional - ID of the tier label "stage": "c738534b-62d6-4f0a-aee1-3a5c6d860d86", # Optional - ID of the stage label "tags": ["0195a666-3405-7664-aaf1-ad65b6091e96", "0195af0e-3213-78fc-a3cf-cb8c192abeaf"], # Optional - List of account tag labels to include "notes": "A note about the account", # Optional "id_in_tool": "idA", # Optional - the ID of the company/user in your own tool for reference "additional_properties": { # Optional - a key-value mapping of custom attributes specific to the account "Login_count": 2, "Timezone": "America/Los_Angeles" }, "role": "support", # Optional - The role/domain of the customer }' ``` #### Get Contacts You can grab all the contacts for your organization by calling `contacts`. This call is paginated, so please include the limit and offset in your URL otherwise the default `limit=100` and `offset=0` will be used. The return type is `{"contacts": {"data": [...], "has_next_page": ..., "next_cursor": ...}}`. Contacts carry `source_specific_ids` and `source_specific_id` in the same shape as accounts. The list form does **not** include the flat `attributes` object — fetch a contact by ID for that. See [Attributes](#attributes). ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/contacts?limit=50&offset=0" \ -H "Authorization: " ``` To grab a company account's contacts, you can use the ID from Wavelength, the domain of the company account, or a source specific ID that you've already linked to the company account. This call is also paginated, so please include the limit and offset in your URL. The contacts are returned in this format `{"contacts":{...}}`. Note that the format for passing in the source specific ID is a bit different, it needs to be passed in as a query parameter. ```bash theme={null} curl -X \ GET https://api.wavelength.cx/contacts/company/01945162-52fc-739b-98de-e2ac9f2eec49?limit=50&offset=0 \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET https://api.wavelength.cx/contacts/company/company.com?limit=50&offset=0\ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/contacts/company?company_source_specific_id=companySSID&limit=50&offset=0" \ -H "Authorization: " ``` You can also pass an optional `updated_after` query parameter to return only contacts updated at or after a given time (`updated_at >= updated_after`) — useful for incremental sync. It accepts an RFC3339 timestamp (e.g. `2026-06-01T00:00:00Z`) or a bare date (`2026-06-01`, treated as UTC midnight). Contacts with no `updated_at` are excluded when the filter is set, and an invalid value returns `400 - invalid updated_after parameter`. This works on both the all-contacts list and the by-company list shown above. ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/contacts?updated_after=2026-06-01T00:00:00Z&limit=50&offset=0" \ -H "Authorization: " ``` To grab a specific contact, you can use the ID from Wavelength, the domain of the contact, or a source specific ID that you've already linked to the contact. This call is not paginated since you are only grabbing a specific account. The contact is returned in this format `{"contact":{...}}` ```bash theme={null} curl -X \ GET https://api.wavelength.cx/contacts/01966e6b-3219-78be-a0fe-260e590565f \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET https://api.wavelength.cx/contacts/name@company.com\ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/contacts?source_specific_id=contactSSID" \ -H "Authorization: " ``` #### Update Contact Update any of the contact's fields via the Wavelength ID, the domain of the contact, or with a source specific ID that you've already linked to the contact. In the input arguments, include any of the fields that you want to update. For updating additional properties, if the key already exists on the account then the value of that key will be updated, otherwise the whole key value pair will be added. If you want more granular actions for additional properties, please use the Additional Properties Endpoints. All the allowed parameters are the same as the `CREATE` endpoint except there are additional parameters for tags and source specific IDs. The general `tags` will be tags that are being added and then there's an additional `removed_tags` to indicate removing a tag. Similarily, the `source_specific_ids` will be added while the 'removed\_source\_specific\_ids' will be removed. The Wavelength ID of the updated contact is returned in the format `{"id":"01966e6b-3219-78be-a0fe-260e590565f"}`. ```bash theme={null} curl -X \ PUT https://api.wavelength.cx/contacts/01966e6b-3219-78be-a0fe-260e590565f \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "name": "New Contact Name", "tags": ["18eb8ad2-b538-420a-80e5-7827429f3ed3"], "removed_tags": ["0195a666-3405-7664-aaf1-ad65b6091e96"], "source_specific_ids": ["newContactSSID"], "removed_source_specific_ids": ["contactSSID"], "additional_properties": { "existingKey": "newValue", "newKey": "newValue" } }' ``` #### Upsert Contact A combination of the create and the update endpoints! If no Wavelength ID, domain, nor source specific ID is passed in or if the values pass in don't map to an existing contact, then the contact will be created. Otherwise, the existing account will be updated. The Wavelength ID of the updated or created contact is returned in the format `{"id":"01966e6b-3219-78be-a0fe-260e590565f"}`. We recommend always inputting the required attributes for create (`name`, `domain`, and one of the company identifiers so `company_id`, `company_domain` or `company_source_specific_id`) so that the endpoint won't fail when the contact needs to be created. ```bash theme={null} curl -X \ PUT https://api.wavelength.cx/contacts/upsert/01966e6b-3219-78be-a0fe-260e590565f \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "name": "account_name", "domain": "account.com", "company_id": "01945162-52fc-739b-98de-e2ac9f2eec49" }' ``` ```bash theme={null} curl -X \ PUT "https://api.wavelength.cx/contacts/upsert?source_specific_id=contactSSID" \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "name": "account_name", "domain": "account.com", "company_id": "01945162-52fc-739b-98de-e2ac9f2eec49" }' ``` #### Delete Contact Delete a contact by referencing the contact with its Wavelength ID, domain, or source specific ID. The return is empty `{}`. ```bash theme={null} curl -X \ DELETE https://api.wavelength.cx/contacts/01966e6b-3219-78be-a0fe-260e590565f \ -H "Authorization: " ``` ## Deals Manage deals (opportunities) linked to your company accounts through these API endpoints. You can reference a deal by its Wavelength ID or by a source specific ID you have already linked to it. A **source specific ID** (`source_specific_ids`) is your own external identifier for a deal — for example a HubSpot deal ID or a Salesforce opportunity ID. An **account source specific ID** (`account_source_specific_id`) is the external identifier you used when creating the parent company account. #### Create Deal Required attributes are `name` and `account_source_specific_id`. The `account_source_specific_id` must match a source specific ID that was already registered on an existing company account. You can optionally supply `source_specific_ids` — an array of your own external IDs for this deal — so that you can look it up later without the Wavelength UUID. The Wavelength ID of the created deal is returned in the format `{"id":"01945162-52fc-739b-98de-e2ac9f2eec49"}`. ```bash theme={null} curl -X \ POST https://api.wavelength.cx/deals \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "name": "Acme Expansion Q3", "account_source_specific_id": "acct-ssid-123", "source_specific_ids": ["deal-ext-001"], # Optional "amount": 25000.00, # Optional "stage": "Proposal", # Optional "close_date": "2026-09-30", # Optional - format YYYY-MM-DD "description": "Upsell opportunity", # Optional "type": "New Business", # Optional "next_step": "Send proposal", # Optional "main_competitors": "Competitor X", # Optional "assignee_user_id": "user-uuid-here", # Optional - Wavelength user UUID "time_length": 3, # Optional "time_unit": "months", # Optional "additional_properties": { # Optional - custom attributes "Custom Field": "value" } }' ``` Every deal returned by these endpoints carries three fields for referring to it and its company without using Wavelength IDs: * `source_specific_ids` — the full list of external ids linked to the deal, same shape as on accounts. * `source_specific_id` — one of those as a plain string, chosen by the same rule accounts use: the id you sent through this API (`source` = `API`) when there is one, otherwise the first. `null` when the deal has none. * `external_company` — the deal's company, expressed as **that company's** `source_specific_id`. It is the same string you pass as `account_source_specific_id` when creating a deal, so you can write the deal-to-company link back to your own system without ever resolving a Wavelength UUID. It is `null` when the deal has no company, or when the company has no source specific id of its own (an account created in Wavelength and never synced from anywhere). ```json theme={null} { "id": "01945162-52fc-739b-98de-e2ac9f2eec49", "name": "Airbnb — renewal", "source_specific_id": "deal-ext-001", "external_company": "488dc44e-b9b2-4f0c-90d6-c2ae7414e600", "attributes": { "…": "see Attributes below" } } ``` #### Get Deal Fetch a single deal by its Wavelength UUID or by a source specific ID you have linked to it. The deal is returned in the format `{"deal": {...}}`. ```bash theme={null} curl -X \ GET https://api.wavelength.cx/deals/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/deals/01945162-52fc-739b-98de-e2ac9f2eec49?source_specific_id=deal-ext-001" \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/deals?source_specific_id=deal-ext-001" \ -H "Authorization: " ``` #### Get Deals List all deals for your organisation or narrow the list to a single company account. This call is paginated — include `limit` and `offset` in the URL, otherwise the defaults `limit=100` and `offset=0` are used. The deals are returned in the format `{"deals": [...]}`, sorted by creation date (newest first). ```bash theme={null} # All deals for the org curl -X \ GET "https://api.wavelength.cx/deals?limit=50&offset=0" \ -H "Authorization: " ``` ```bash theme={null} # All deals for a specific company account curl -X \ GET "https://api.wavelength.cx/deals?account_source_specific_id=acct-ssid-123&limit=50&offset=0" \ -H "Authorization: " ``` You can also pass an optional `updated_after` query parameter to return only deals updated at or after a given time (`updated_at >= updated_after`) — useful for incremental sync. It accepts an RFC3339 timestamp (e.g. `2026-06-01T00:00:00Z`) or a bare date (`2026-06-01`, treated as UTC midnight). Deals with no `updated_at` are excluded when the filter is set, and an invalid value returns `400 - invalid updated_after parameter`. The filter applies to both list paths (all-org and `account_source_specific_id`); it is ignored on the single-deal `source_specific_id` lookup. Each deal in the response already includes an `updated_at` field. ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/deals?updated_after=2026-06-01T00:00:00Z&limit=50&offset=0" \ -H "Authorization: " ``` #### Update Deal Update any fields on an existing deal. Reference the deal by its Wavelength UUID (path parameter) or by a source specific ID (query parameter). Only the fields you include in the body are changed. For `source_specific_ids`, the values you provide will be **added** to the deal. Use `removed_source_specific_ids` to remove existing ones. The Wavelength ID of the updated deal is returned in the format `{"id":"01945162-52fc-739b-98de-e2ac9f2eec49"}`. ```bash theme={null} # By Wavelength ID curl -X \ PUT https://api.wavelength.cx/deals/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "stage": "Closed Won", "amount": 28000.00, "source_specific_ids": ["deal-ext-new"], "removed_source_specific_ids": ["deal-ext-old"], "additional_properties": { "Custom Field": "updated value" } }' ``` ```bash theme={null} # By source specific ID curl -X \ PUT "https://api.wavelength.cx/deals?source_specific_id=deal-ext-001" \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "stage": "Negotiation", "amount": 30000.00 }' ``` #### Upsert Deal A combination of create and update. If the deal already exists (matched by Wavelength UUID in the path or by a source specific ID in the body) it will be updated in place. Otherwise a new deal is created. We recommend always including the fields required for creation (`name` and `account_source_specific_id`) so the endpoint never fails when the deal needs to be created for the first time. The Wavelength ID of the created or updated deal is returned in the format `{"id":"01945162-52fc-739b-98de-e2ac9f2eec49"}`. ```bash theme={null} # Upsert without a known Wavelength ID (matched via source_specific_ids in the body) curl -X \ PUT https://api.wavelength.cx/deals/upsert \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "name": "Acme Expansion Q3", "account_source_specific_id": "acct-ssid-123", "source_specific_ids": ["deal-ext-001"], "stage": "Negotiation", "amount": 30000.00 }' ``` ```bash theme={null} # Upsert with a known Wavelength ID curl -X \ PUT https://api.wavelength.cx/deals/upsert/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "name": "Acme Expansion Q3", "account_source_specific_id": "acct-ssid-123", "source_specific_ids": ["deal-ext-001"], "stage": "Negotiation", "amount": 30000.00 }' ``` #### Delete Deal Delete a deal by its Wavelength UUID (path parameter) or by a source specific ID (query parameter). This also removes the linked source specific ID rows and any custom attribute values for that deal. The Wavelength ID of the deleted deal is returned in the format `{"id":"01945162-52fc-739b-98de-e2ac9f2eec49"}`. ```bash theme={null} # By Wavelength ID curl -X \ DELETE https://api.wavelength.cx/deals/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Authorization: " ``` ```bash theme={null} # By source specific ID curl -X \ DELETE "https://api.wavelength.cx/deals?source_specific_id=deal-ext-001" \ -H "Authorization: " ``` ## Attributes These endpoints let you inspect the attribute definitions in your org. Attributes are the schema of your additional properties — each one has an ID, a name, and a datatype. Use the list endpoint to look up attribute IDs before calling the Backfill endpoint. #### The `attributes` field on responses Accounts, contacts and deals come back with an `attributes` object alongside `additional_properties`. It is the same data in a flat shape: one key per attribute, keyed by a short stable slug, with the value unwrapped. ```json theme={null} "attributes": { "csm_owner": {"referenced_actor_id": "b0b876b7-f5ee-45f5-a8c3-cd7d6b8a6e43", "referenced_actor_type": "workspace-member"}, "linkedin": "https://linkedin.com/company/airbnb", "estimated_arr": null, "billing_city": null } ``` `additional_properties` is a list of objects, which many sync and ETL tools cannot map to a destination field. `attributes` exists so each custom attribute is addressable on its own, as `attributes.`. Four things to rely on when you build against it: * **Every attribute in your org appears on every record**, whether or not that record has a value. An unset attribute is `null`, never a missing key. This is deliberate — tools that infer a schema by sampling a few records would otherwise never see an attribute that happened to be unset in the sample, and could not offer it as a mapping target at all. * **Keys are slugs, not display names.** The slug is derived from the attribute name the first time it is needed — lowercased, with any run of non-alphanumeric characters replaced by `_` — and then stored on the attribute. Renaming the attribute in Wavelength does **not** change its slug, so a mapping you build today keeps working. `CSM Owner` becomes `csm_owner`, `Estimated ARR` becomes `estimated_arr`. If two attributes would produce the same slug, the later one gets a numeric suffix (`stage_2`). * **Slugs are unique per object type, not across your org.** Accounts and deals are separate namespaces, so both can have a `stage`. * **Values keep their type.** A `number` attribute is a JSON number, a `boolean` is a boolean, a `jsonb` attribute is a JSON object — not a quoted string. The value is identical to the one under `additional_properties[].value` for the same attribute. Value shapes by datatype: | Datatype | Example value | | ---------------------- | ----------------------------------------------------------------------------------------------------- | | `text`, `url`, `email` | `"https://linkedin.com/company/airbnb"` | | `number` | `168` | | `boolean` | `true` | | `date`, `timestamp` | `"2026-05-15"`, or an RFC3339 timestamp | | `select` | `{"option_id": "c8694bc2-…", "title": "$0-$1M", "is_archived": false}` | | `multiselect` | a list of those option objects | | `jsonb` | whatever was stored, e.g. `{"referenced_actor_id": "…", "referenced_actor_type": "workspace-member"}` | A `jsonb` value that holds an external system's reference — an owner, an assignee — is passed through unchanged. Wavelength does not translate it into a Wavelength user, so you can write it straight back to the system it came from. Which endpoints return it: | Endpoint | `attributes` | | ---------------------------------------------------------------------------- | ------------------ | | `GET /accounts`, `GET /accounts/{id}`, `POST /accounts/search` | yes | | `GET /contacts/{id}` | yes | | `GET /contacts` (list) | **no** — see below | | `GET /deals`, `GET /deals/{id}`, and the deal create/update/upsert responses | yes | `GET /contacts` as a list does not include `attributes`. The query behind it does not return attribute values for a page of contacts, and sending back a map of all-`null` values would look like "this contact has nothing set" when the truth is "values were not returned". Fetch a contact by ID when you need its attributes. If the attribute definitions cannot be loaded for a request, `attributes` is left off the account records and returned as `null` on deals, and the rest of the record is still returned. Treat that as "not returned this time" and retry — it does not mean the values were cleared. #### List Attributes Returns all attributes for a given object type, sorted alphabetically by name. The `object_type` path parameter must be one of `accounts`, `deals`, or `interactions`. Response format: `{"data": [{"id": "...", "name": "...", "datatype": "..."}, ...]}`. The `datatype` field is one of: `text`, `number`, `boolean`, `date`, `timestamp`, `url`, `email`, `select`, `multiselect`, `jsonb`, `person`. ```bash theme={null} curl -X \ GET https://api.wavelength.cx/list/accounts/attributes \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET https://api.wavelength.cx/list/deals/attributes \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET https://api.wavelength.cx/list/interactions/attributes \ -H "Authorization: " ``` Example response: ```json theme={null} { "data": [ { "id": "8f14e2aa-1234-4321-aaaa-000000000001", "name": "Health Score", "datatype": "number" }, { "id": "8f14e2aa-1234-4321-aaaa-000000000002", "name": "Health Status", "datatype": "select" }, { "id": "8f14e2aa-1234-4321-aaaa-000000000003", "name": "Risk Factors", "datatype": "multiselect" } ] } ``` **Errors:** * **400 Bad Request** — `object_type` is not one of the accepted values. * **401 Unauthorized** — invalid or missing API key. ## Additional Properties The custom attributes are stored on a per item basis. Therefore, all of these endpoints affect a specific account's, contact's, or deal's additional properties. In general, the additional properties endpoints are in this format `add_props/:item/:id`, where `:item` is one of `accounts`, `contacts`, or `deals`. #### Create Additional Properties To denote which account, contact, or deal to create the property on, you have to append the item (`accounts`, `contacts`, or `deals`) and either the item's Wavelength ID, source specific id, or domain (accounts and contacts only) to the url. Then include a map of the properties you want to add to the value of each property. If the property already exists on that item's additional properties, an "already exists" error will be returned. Otherwise, the Wavelength ID of the item that the property was created for is returned in the format `{"id":"01945162-52fc-739b-98de-e2ac9f2eec49"}`. ```bash theme={null} curl -X \ POST https://api.wavelength.cx/add_props/accounts/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "property1": "value1", "property2": "value2" }' ``` ```bash theme={null} curl -X \ POST https://api.wavelength.cx/add_props/deals/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "property1": "value1", "property2": "value2" }' ``` #### Get Additional Properties Specify the item (`accounts`, `contacts`, or `deals`) and either the Wavelength ID, domain (accounts and contacts only), or source specific ID of the item to get the additional properties. The return includes the ID of the item and its additional properties in this format `{"id": "01945162-52fc-739b-98de-e2ac9f2eec49", "additional_properties": [...]}` ```bash theme={null} curl -X \ GET https://api.wavelength.cx/add_props/accounts/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET https://api.wavelength.cx/add_props/accounts/account.com \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/add_props/accounts?source_specific_id=accountSSID" \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET https://api.wavelength.cx/add_props/deals/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/add_props/deals?source_specific_id=dealSSID" \ -H "Authorization: " ``` You can also pass an optional `updated_after` query parameter to return only attribute values updated at or after a given time (`updated_at >= updated_after`) — useful for incremental sync. It accepts an RFC3339 timestamp (e.g. `2026-06-01T00:00:00Z`) or a bare date (`2026-06-01`, treated as UTC midnight). Values with no `updated_at` are excluded when the filter is set, and an invalid value returns `400 - invalid updated_after parameter`. ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/add_props/accounts/01945162-52fc-739b-98de-e2ac9f2eec49?updated_after=2026-06-01T00:00:00Z" \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/add_props/deals/01945162-52fc-739b-98de-e2ac9f2eec49?updated_after=2026-06-01T00:00:00Z" \ -H "Authorization: " ``` #### Update Additional Properties Update an item's additional properties by specifying the item (`accounts`, `contacts`, or `deals`) and either the item's Wavelength ID, domain (accounts and contacts only), or source specific ID. Include any of the fields that we want to update with a mapping to their new values. If the specified field does not already exist in the item's additional properties, we will directly add it. The ID of the item whose additional properties were updated is returned in the format `{"id": "01945162-52fc-739b-98de-e2ac9f2eec49"}` ```bash theme={null} curl -X \ PUT https://api.wavelength.cx/add_props/accounts/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "Custom Property": "New Value", "New Property 1": 7, "New Property 2": ["value1", "value2"] }' ``` ```bash theme={null} curl -X \ PUT https://api.wavelength.cx/add_props/deals/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "Custom Property": "New Value", "New Property 1": 7, "New Property 2": ["value1", "value2"] }' ``` #### Delete Additional Property Similarly, specify the item (`accounts`, `contacts`, or `deals`) and its Wavelength ID, domain (accounts and contacts only), or source specific ID and then specify which properties to delete by the property names. The return is empty `{}`. ```bash theme={null} curl -X DELETE \ https://api.wavelength.cx/add_props/accounts/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Authorization: " \ -H "Content-Type: application/json" \ -d '{ "properties": ["Property1", "Property2", "Property3"] }' ``` ```bash theme={null} curl -X DELETE \ https://api.wavelength.cx/add_props/deals/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Authorization: " \ -H "Content-Type: application/json" \ -d '{ "properties": ["Property1", "Property2", "Property3"] }' ``` #### Backfill Additional Properties Adds or removes historical data points for an additional property that **already exists**, without touching the additional property's current value (the current value and the historical timeline are independent by design). This is useful when migrating historical data into Wavelength — for example, a year of monthly Health Scores from your old system. The additional property must already exist — it's looked up by its Wavelength ID and you'll get a `404` if it doesn't. This endpoint never creates additional properties, and only accepts an additional property ID, not a name (additional property names aren't guaranteed unique per org). A data point is identified by **(additional property, company, timestamp, value)**. There are two methods on the same URL: * **`POST`** always **adds a new historical data point**, even if a data point already exists for that exact `(company, timestamp)`, or even the exact same `(company, timestamp, value)`. Every `POST` produces a new row — nothing is ever overwritten or deduplicated. If you re-run the same request twice, you'll end up with two identical data points in the history. * **`DELETE`** removes every data point matching a record's exact `(company, timestamp, value)` — including every duplicate, if more than one exists for that exact combination. A record whose `(company, timestamp)` matches but whose `value` doesn't is **not** a match and deletes nothing — value is part of what identifies a data point to delete, not just company and timestamp, so you can remove one specific duplicated value without disturbing a different value recorded at the same company and timestamp. `records` is an array of 1 to 500 entries; if you have more to backfill, split them across multiple requests. Each record needs a `timestamp` (RFC3339, must be in the past), a `value`, and exactly one company identifier — `company_id`, `company_domain`, or `company_source_specific_id`. The request body shape is identical for both methods. `value` must match the additional property's own datatype and can never be `null` (there's no unset/delete-a-value-in-place through this endpoint — use `DELETE` to remove a data point entirely instead). The shape of a valid `value` depends on the datatype: * **Text, Number, Boolean, Select, Date, Timestamp, URL, Email, Person** — a plain scalar: a string, number, or boolean. * **Multiselect** — a JSON array of strings, e.g. `["Option A", "Option B"]`. * **JSONB** — any JSON value: a scalar, an array, or a nested object. **Add (POST)** — always creates a new row: ```bash theme={null} curl -X \ POST https://api.wavelength.cx/attributes/8f14e2aa-1234-4321-aaaa-000000000001/history \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "records": [ { "company_id": "8f14e2aa-0000-0000-0000-000000000002", "timestamp": "2025-01-01T00:00:00Z", "value": 82 }, { "company_id": "8f14e2aa-0000-0000-0000-000000000002", "timestamp": "2025-02-01T00:00:00Z", "value": 75 }, { "company_id": "8f14e2aa-0000-0000-0000-000000000002", "timestamp": "2025-03-01T00:00:00Z", "value": 71 } ] }' ``` Response: `{"inserted": 3}`. Sending that exact same request again would add three *more* rows — `POST` never checks for an existing match. **Remove (DELETE)** — removes every row matching the given `(company, timestamp, value)` exactly: ```bash theme={null} curl -X \ DELETE https://api.wavelength.cx/attributes/8f14e2aa-1234-4321-aaaa-000000000001/history \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "records": [ { "company_id": "8f14e2aa-0000-0000-0000-000000000002", "timestamp": "2025-01-01T00:00:00Z", "value": 82 } ] }' ``` Response: `{"deleted": 1}` — or higher, if that exact combination had duplicates from repeated `POST`s. If nothing matches, the response is `{"deleted": 0}`; this is not an error, `DELETE` is idempotent. Either method works for a single data point too — just send an array of one, and you can identify the company by domain instead of Wavelength ID: ```bash theme={null} curl -X \ POST https://api.wavelength.cx/attributes/8f14e2aa-1234-4321-aaaa-000000000001/history \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "records": [ { "company_domain": "acme.com", "timestamp": "2025-06-15T00:00:00Z", "value": "At Risk" } ] }' ``` A Multiselect or JSONB additional property works the same way, just with a non-scalar `value`: ```bash theme={null} curl -X \ POST https://api.wavelength.cx/attributes/8f14e2aa-1234-4321-aaaa-000000000003/history \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "records": [ { "company_id": "8f14e2aa-0000-0000-0000-000000000002", "timestamp": "2025-06-15T00:00:00Z", "value": ["Enterprise", "Renewal Risk"] } ] }' ``` Validation is all-or-nothing — if anything in the batch is invalid, nothing is written or deleted, and the response tells you exactly which records failed and why, with `index` being the zero-based position of the record in your `records` array: ```json theme={null} // 400 — wrong type for a number attribute { "errors": [ { "index": 2, "reason": "value type text does not match attribute datatype number" } ] } ``` ```json theme={null} // 400 — value not in the configured options for a select attribute { "errors": [ { "index": 0, "reason": "'invalid-option' is not a valid option for this select attribute" } ] } ``` Other errors (shared by both methods): * **404 Not Found** — the additional property ID doesn't exist in your org. * **400 Bad Request** — `records` is empty; more than 500 records in one request; a record is missing a company identifier or provides more than one; the company identifier doesn't resolve to a company in your org; `timestamp` is missing, not valid RFC3339, or not in the past; `value` is missing, `null`, or doesn't match the additional property's datatype (e.g. an array sent for a non-Multiselect/JSONB property, or a string that isn't one of the property's configured Select options). ## Labels Here are the API endpoints for account labels. Account label types include: `Tier`, `Stage`, `CompanyType`, or `AccountTag` (Tags). #### Create Label Required attributes are the `type`, `name`, and `color` of the label. Optionally, include a description. The ID of the created label will be returned in this format `{"id": "0195a666-3405-7664-aaf1-ad65b6091e96"}`. ```bash theme={null} curl -X \ POST https://api.wavelength.cx/accounts/labels \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "type": "Tier", "name": "Example Tier", "color": "#545A92", # hex code "description": "A description about example tier" # optional }' ``` #### Get Labels There are three versions of grabbing account labels. You can grab all the account labels for your organization by calling `/account/labels`. To grab a specific type of account labels, add the label type so `/account/labels/{type}`. And then to grab a specific label, add the label ID so `/account/labels/{label_id}`. The labels are returned in this format `{"labels": [...]}`. ```bash theme={null} curl -X \ GET https://api.wavelength.cx/accounts/labels \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET https://api.wavelength.cx/accounts/labels/tier \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET https://api.wavelength.cx/accounts/labels/0195a666-3405-7664-aaf1-ad65b6091e96 \ -H "Authorization: " ``` You can also pass an optional `updated_after` query parameter to return only labels updated at or after a given time (`updated_at >= updated_after`) — useful for incremental sync. It accepts an RFC3339 timestamp (e.g. `2026-06-01T00:00:00Z`) or a bare date (`2026-06-01`, treated as UTC midnight). Labels with no `updated_at` are excluded when the filter is set, and an invalid value returns `400 - invalid updated_after parameter`. ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/accounts/labels?updated_after=2026-06-01T00:00:00Z" \ -H "Authorization: " ``` #### Update Label To update an account label, you must have the label id and call `/account/labels/{label_id}`. Use the get account labels endpoints to grab the label id if needed. Include the fields that you are changing for the label in the json object. The possible arguments are `type`, `name`, `color`, and `description`. The ID of the updated label is returned as `{"id": "0195a666-3405-7664-aaf1-ad65b6091e96"}` ```bash theme={null} curl -X \ PUT https://api.wavelength.cx/accounts/labels/0195a666-3405-7664-aaf1-ad65b6091e96 \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "name": "New Example Tier" }' ``` #### Delete Label You also must have the label\_id to delete an account label. Then just call `/account/labels/{label_id}` with a delete operation. The return is empty `{}`. ```bash theme={null} curl -X \ DELETE https://api.wavelength.cx/accounts/labels/0195a666-3405-7664-aaf1-ad65b6091e96 \ -H "Authorization: " ``` ## Usage Here are the API endpoints for sending usage logs to be stored under a specific account. #### Add Usage Log The required attributes are: * `timestamp`: when the event happened * `type`: the type of log, the options are "Action", "Metric", or "CumulativeMetric" * `content`: what happened in the log (think the string that shows up in your logs) * `attribute`: the category that the log is related to, we'll use this attribute to group logs together for account 360s and for calculating health scores (i.e. "Login" or "AccountSpend") * `value`: if the type of the log is "Metric" or "CumulativeMetric", we require you to pass in the value of the metric so that we can use it for grouping and filtering (i.e. \$20 or 7) Additionally, you can add any metadata that you want to store. The ID of the created log is returned in this format `{"id": "01945162-52fc-739b-98de-e2ac9f2eec49"}`. ```bash theme={null} curl -X \ POST https://api.wavelength.cx/usage \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "timestamp": "2025-03-20T15:30:00Z", "type": "Action", "content": "User logged in", "attribute": "Login", "metadata": {"key": "value"} # Optional }' ``` ```bash theme={null} curl -X \ POST https://api.wavelength.cx/usage \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "timestamp": "2025-03-20T15:30:00Z", "type": "Metric", "content": "User spent $20 from their savings account", "attribute": "AccountSpend", "value": 20, "metadata": {"key": "value"} # Optional }' ``` If you want to tie the usage log to a specific item, then you'll need to include the item (`accounts` or `contacts`) and its Wavelength ID, source specific id, or domain in the url. ```bash theme={null} curl -X \ POST https://api.wavelength.cx/usage/accounts/01945162-52fc-739b-98de-e2ac9f2eec49 \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "timestamp": "2025-03-20T15:30:00Z", "type": "Action", "content": "User logged in", "attribute": "Login" }' ``` ```bash theme={null} curl -X \ POST "https://api.wavelength.cx/usage/contacts?source_specific_id=contactSSID" \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "timestamp": "2025-03-20T15:30:00Z", "type": "Action", "content": "User logged in", "attribute": "Login" }' ``` #### Get Usage Logs You can either grab all the usage logs for your organization or specify a specific account/contact through one of the three identifying methods. This call is also paginated, so please include the limit and offset in your URL otherwise the default `limit=100` and `offset=0` will be used. Note: the logs are returned in decreasing timestamp order so the newest logs are returned first. The logs are returned in this format `{"logs": {"data": [...], "has_next_page": ..., "next_cursor":...}}` ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/usage?limit=50&offset=0" \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/usage/accounts/01945162-52fc-739b-98de-e2ac9f2eec49?limit=50&offset=0" \ -H "Authorization: " ``` ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/usage/contacts?source_specific_id=contactSSID&limit=50&offset=0" \ -H "Authorization: " ``` You can also pass an optional `updated_after` query parameter to return only usage logs updated at or after a given time (`updated_at >= updated_after`) — useful for incremental sync. It accepts an RFC3339 timestamp (e.g. `2026-06-01T00:00:00Z`) or a bare date (`2026-06-01`, treated as UTC midnight). Logs with no `updated_at` are excluded when the filter is set, and an invalid value returns `400 - invalid updated_after parameter`. The filter works on the org-wide list and the per-account/contact lists. ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/usage?updated_after=2026-06-01T00:00:00Z" \ -H "Authorization: " ``` You can also grab a specific usage log by appending the usage log's id. ```bash theme={null} curl -X \ GET "https://api.wavelength.cx/usage/usageLogID" \ -H "Authorization: " ``` #### Update Usage Log Update any values in a usage log by the usage log's ID. Include the values that you want to update in the input data. Note, that updating the metadata field will completely override the existing metadata value. The ID of the updated log is returned in this format `{"id": "01945162-52fc-739b-98de-e2ac9f2eec49"}`. ```bash theme={null} curl -X \ PUT https://api.wavelength.cx/usage/usageLogID \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "attribute": "Seats" }' ``` #### Delete Usage Log Delete a usage log by its specific ID which is returned when a usage log is created. The return is empty `{}`. ```bash theme={null} curl -X \ DELETE https://api.wavelength.cx/usage/usageLogID \ -H "Authorization: " ``` ## Other #### Create Account Note Create (or idempotently upsert) an Account Note for a company resolved by **domain**. Use this endpoint for CRM/meeting note ingestion (e.g., meeting notes from Gong, Salesforce, etc.). The endpoint: * Resolves the **primary company** for the provided domain (`is_primary=true`) within the org * Stores note content and meeting metadata (including raw participant emails) * Performs **idempotent upsert** using `(org_id, source, source_specific_id)` — repeated calls with the same `source` and `source_specific_id` update the same record instead of creating duplicates **POST** `/accounts/create_note` **Authentication:** API key via header `Authorization: ` (required) **Request body (JSON):** | Field | Type | Required | Description | | -------------------- | ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------- | | `domain` | string | Yes | Domain used to resolve the company (e.g. `"berkeley.edu"`). Must map to an existing **primary** company for the org. | | `source` | string | Yes | Source system identifier (e.g. `"gong"`, `"salesforce"`). Treated as an opaque string. | | `source_specific_id` | string | Yes | Unique ID for this note in the source system (used for idempotency). | | `note` | string | Yes | The note content/body. | | `original_timestamp` | string | Yes | Meeting time in RFC3339 format (e.g. `"2025-02-25T14:30:00Z"`). | | `participants` | array of strings | Yes | Raw participant emails (stored in meeting metadata). | | `title` | string | No | Optional title for the note. | | `action_items` | string | No | Optional action items extracted from the note. | | `transcript` | string | No | Optional full transcript. | **Response (JSON):** On success, returns the created/updated note ID: `{"id": "uuid"}`. The stored note includes `org_id`, `company_id`, `source`, `source_specific_id`, and `metadata.meeting.participant_emails` (array of strings, when provided). **Idempotency:** Calls with the same `(source, source_specific_id)` within an org will **update** the existing note instead of creating a duplicate. **Error cases:** `401 Unauthorized` (missing/invalid Authorization), `400 Bad Request` (invalid payload), `412 Precondition Failed` (domain does not correspond to an existing company account), `502 Bad Gateway` (upstream error). ```bash theme={null} curl -X \ POST https://api.wavelength.cx/accounts/create_note \ -H "Content-Type: application/json" \ -H "Authorization: " \ -d '{ "domain": "acmecorp.com", "source": "zoom", "source_specific_id": "call_84729", "note": "Initial sync regarding Q3 deliverables and integration timeline.", "original_timestamp": "2026-04-12T10:15:00Z", "participants": [ "alice.smith@acmecorp.com", "bob.jones@acmecorp.com" ] }' ``` ## Rate Limiting There is a rate limit of 30 events per second. If you exceed this, you will receive a 429 response code. If you need a higher rate, please let us know. # Interactions Source: https://docs.askassembly.app/usage/developer/interactionsAPI Send events from your own integrations to Wavelength. This is a great way to track how your customers are interacting with your product. #### API Key You must first create an API Key in Wavelength. It is located under "Manage Integrations", then scroll to the bottom and click "Manage API Keys". Here, you can see your existing keys, their names and expiration. You can delete existing ones. When creating a new API key, the name must be unique in your organization. Once the key has been created, you will be shown a one-time dialog box with your new API key. You should copy this down somewhere safe -- we will not show this again. If you lose your API key, you will need to make a new one. For these endpoints, set the `Authorization` header to your API key value directly (for example `Authorization: `). Do **not** add a `Bearer` prefix; the full header value is validated as the key. *** #### List Interactions ## GET /interactions Returns a paginated list of interactions (top-level tickets) for your organization. ### Query Parameters | Parameter | Type | Required | Default | Description | | ---------------- | ------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `limit` | integer | No | 50 | Number of interactions to return. Must be between 1 and 200. | | `offset` | integer | No | 0 | Number of interactions to skip (for pagination). | | `status` | string | No | — | Filter by ticket status. Repeatable for multiple values. Valid values: `Open`, `NeedsResponse`, `Breaching`, `Escalated`, `Closed`. Case-sensitive. | | `assignee_id` | string | No | — | Filter by assignee user ID. Repeatable for multiple values. | | `company_id` | string | No | — | Filter by company ID. Repeatable for multiple values. | | `customer_id` | string | No | — | Filter by customer/contact ID. Repeatable for multiple values. | | `source` | string | No | — | Filter by source channel. Repeatable for multiple values. Valid values: `Slack`, `Gmail`, `Web`, `API`, `Zendesk`, `Intercom`, `Discord`, `GitHubTicket`. | | `category_id` | string (UUID) | No | — | Filter by category ID. Repeatable for multiple values. Use `GET /categories` to list available IDs. | | `tag_id` | string (UUID) | No | — | Filter by tag ID. Repeatable for multiple values. Use `GET /tags` to list available IDs. | | `team_owner_id` | string (UUID) | No | — | Filter by the owning team's ID. Use `GET /teams` to list available IDs. | | `updated_after` | string | No | — | Return only interactions updated at or after this time. Accepts RFC3339 (`2026-06-01T00:00:00Z`) or bare date (`2026-06-01`). | | `updated_before` | string | No | — | Return only interactions updated before this time. Same format as `updated_after`. | ### Example: List all interactions ```bash theme={null} curl -X GET "https://api.wavelength.cx/interactions?limit=50&offset=0" \ -H "Authorization: " ``` ### Example: Filter by status ```bash theme={null} curl -X GET "https://api.wavelength.cx/interactions?status=Open&limit=50" \ -H "Authorization: " ``` ### Example: Filter by multiple statuses ```bash theme={null} curl -X GET "https://api.wavelength.cx/interactions?status=Open&status=NeedsResponse&limit=50" \ -H "Authorization: " ``` ### Example: Filter by source ```bash theme={null} curl -X GET "https://api.wavelength.cx/interactions?source=Slack&limit=50" \ -H "Authorization: " ``` ### Example: Filter by source (multiple) ```bash theme={null} curl -X GET "https://api.wavelength.cx/interactions?source=Slack&source=Gmail&limit=50" \ -H "Authorization: " ``` ### Example: Filter by category ```bash theme={null} curl -X GET "https://api.wavelength.cx/interactions?category_id=&limit=50" \ -H "Authorization: " ``` ### Example: Filter by tag ```bash theme={null} curl -X GET "https://api.wavelength.cx/interactions?tag_id=&limit=50" \ -H "Authorization: " ``` ### Example: Filter by team owner ```bash theme={null} curl -X GET "https://api.wavelength.cx/interactions?team_owner_id=&limit=50" \ -H "Authorization: " ``` ### Example: Incremental sync (interactions updated in the last 24 hours) ```bash theme={null} curl -X GET "https://api.wavelength.cx/interactions?updated_after=2026-07-16T00:00:00Z&limit=200" \ -H "Authorization: " ``` ### Response ```json theme={null} { "data": [ { "id": "01KXNWCA9Y6E2QRB9FSHDQZKGS", "ticket_identifier": "ASM", "ticket_number": 6579, "title": "Support request title", "query": "The original message content", "ticket_status": "Open", "source": "API", "url": "https://mail.google.com/...", "wavelength_url": "http://dashboard.askassembly.app/issue/ASM-6579", "created_at": "2026-07-16T16:32:57Z", "updated_at": "2026-07-16T16:37:59Z", "assignee_user_id": "user-uuid", "company_id": "company-uuid", "customer_id": "customer-uuid", "owner_replies": 1, "customer_replies": 2, "attributes": { "my_custom_attr": "value" }, "user_info": { "id": "user@example.com", "name": "Jane Doe", "email": "user@example.com" }, "company_info": { "id": "company-uuid", "name": "Acme Corp" } } ], "has_next_page": true, "next_cursor": 50 } ``` | Field | Description | | ---------------- | ------------------------------------------------------ | | `url` | Source URL (Slack thread link, Gmail email link, etc.) | | `wavelength_url` | Direct link to the ticket in the Wavelength dashboard | | `attributes` | Custom account attributes cached on the interaction | ```` To fetch the next page, pass `next_cursor` as the `offset` in your next request: ```bash curl -X GET "https://api.wavelength.cx/interactions?limit=50&offset=50" \ -H "Authorization: " ```` ### Error Responses | Code | Meaning | | ---- | -------------------------------------------------------------------------------- | | 400 | Invalid `limit` (must be 1–200), invalid `offset` (must be ≥ 0), or missing auth | | 429 | Rate limit exceeded | *** #### Get Interaction with History ## GET /interactions/:id Returns a single interaction and its full reply/message thread. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ---------------------------------- | | `id` | string | Wavelength ULID of the interaction | ### Example ```bash theme={null} curl -X GET "https://api.wavelength.cx/interactions/01KXNWCA9Y6E2QRB9FSHDQZKGS" \ -H "Authorization: " ``` ### Response ```json theme={null} { "interaction": { "id": "01KXNWCA9Y6E2QRB9FSHDQZKGS", "ticket_identifier": "ASM", "ticket_number": 6579, "title": "Support request title", "query": "The original message content", "ticket_status": "Open", "source": "API", "url": "https://mail.google.com/...", "wavelength_url": "http://dashboard.askassembly.app/issue/ASM-6579", "created_at": "2026-07-16T16:32:57Z", "updated_at": "2026-07-16T16:37:59Z", "owner_replies": 1, "customer_replies": 2, "attributes": { "my_custom_attr": "value" }, "company_info": { "id": "company-uuid", "name": "Acme Corp" }, "user_info": { "id": "user@example.com", "name": "Jane Doe", "email": "user@example.com" } }, "history": [ { "id": "01KXNWCA9Y6E2QRB9FSHDQZKGT", "parent_id": "01KXNWCA9Y6E2QRB9FSHDQZKGS", "type": "Message", "content": "The original message content", "source": "Gmail", "created_at": "2026-07-16T16:32:57Z", "from": "customer@example.com", "to": ["support@company.com"], "cc": ["manager@company.com"] }, { "id": "01KXNWCA9Y6E2QRB9FSHDQZKGU", "parent_id": "01KXNWCA9Y6E2QRB9FSHDQZKGS", "type": "Message", "content": "Thanks for reaching out! Looking into this now.", "source": "Web", "created_at": "2026-07-16T16:35:00Z", "from": "agent@company.com" } ] } ``` The `history` array contains all messages, replies, and internal notes for the interaction in chronological order. The `from` field identifies the message author. For Gmail interactions, `to` and `cc` are included when available. ### Error Responses | Code | Meaning | | ---- | -------------------------------------------------------------- | | 400 | Missing or invalid auth | | 404 | Interaction not found (wrong ID or belongs to a different org) | | 429 | Rate limit exceeded | *** #### List Teams ## GET /teams Returns all teams in your organization. Use team IDs with the `team_owner_id` filter on `GET /interactions`. ### Example ```bash theme={null} curl -X GET "https://api.wavelength.cx/teams" \ -H "Authorization: " ``` ### Response ```json theme={null} { "data": [ { "id": "fc12e902-f8ee-4176-947b-7f716469d1d6", "name": "Support", "description": "Customer support team" } ] } ``` ### Error Responses | Code | Meaning | | ---- | ----------------------- | | 400 | Missing or invalid auth | | 429 | Rate limit exceeded | *** #### List Categories ## GET /categories Returns all categories in your organization. Use category IDs with the `category_id` filter on `GET /interactions`. ### Example ```bash theme={null} curl -X GET "https://api.wavelength.cx/categories" \ -H "Authorization: " ``` ### Response ```json theme={null} { "data": [ { "id": "0199868a-ec2d-7d36-bd94-749a4db30dfe", "name": "Bug Report" } ] } ``` ### Error Responses | Code | Meaning | | ---- | ----------------------- | | 400 | Missing or invalid auth | | 429 | Rate limit exceeded | *** #### List Tags ## GET /tags Returns all account tags in your organization. Use tag IDs with the `tag_id` filter on `GET /interactions`. ### Example ```bash theme={null} curl -X GET "https://api.wavelength.cx/tags" \ -H "Authorization: " ``` ### Response ```json theme={null} { "data": [ { "id": "0195af0e-3213-78fc-a3cf-cb8c192abeaf", "name": "VIP", "description": "High-priority customer", "color": "#7695ff" } ] } ``` ### Error Responses | Code | Meaning | | ---- | ----------------------- | | 400 | Missing or invalid auth | | 429 | Rate limit exceeded | *** #### Create Interaction ## POST /interactions Creates a new interaction (a top-level ticket) in Wavelength from your own integration. This is the endpoint to use for tracking how your customers interact with your product. The interaction is created with source `API`, attributed to the integration linked to the API key you authenticate with. ### Headers * `Content-Type: multipart/form-data` (required) * `Authorization: ` (required; raw key, no `Bearer` prefix) ### Request Body (multipart/form-data) | Field | Type | Required | Description | | ---------------------------- | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `query` | string | Yes | The body of the interaction — the customer's message or event text. | | `title` | string | No | Title of the ticket. Defaults to empty. | | `source_specific_id` | string | No | Your own unique identifier for this ticket. Used for deduplication and for replying later via `POST /interactions/reply`. If omitted, Wavelength generates a UUID. | | `user` | object (JSON string) | No | Who created the interaction. If omitted, the interaction is attributed to `Anonymous`. See the User Object table below. | | `company_id` | string (UUID) | No | Wavelength ID of the company account to attach this interaction to. Must exist in your organization. | | `company_source_specific_id` | string | No | Source-specific ID of the company account to attach this interaction to. Must already be mapped to a company in your organization. | | `additional_properties` | object (JSON string) | No | Arbitrary key/value pairs stored as custom properties on the ticket. Values may be strings, numbers, or booleans. | | `original_timestamp` | string | No | When the event actually occurred, in RFC3339 format (e.g. `2024-06-10T18:34:00Z`). Defaults to receipt time. | | `files` | file\[] | No | File attachments (multipart). Repeat the field to attach multiple files. | | `files_image_url` | string | No | Comma-separated URLs of images to fetch and attach. | `user`, `additional_properties`, and any other object field are sent as JSON-encoded strings inside the multipart form. #### User Object | Field | Type | Required | Description | | -------------------- | ------------- | -------- | --------------------------------------------------------------- | | `email` | string | No\* | User's email address. | | `id` | string (UUID) | No\* | Wavelength ID of an existing customer. | | `source_specific_id` | string | No\* | The user's source-specific ID, as already mapped in Wavelength. | | `name` | string | No | Display name. Falls back to `email` if omitted. | | `username` | string | No | Username. Falls back to `email` if omitted. | | `image_url` | string | No | Avatar URL. | \*If you include a `user` object at all, it must contain at least one of `email`, `id`, or `source_specific_id`. Omit `user` entirely to create the interaction as `Anonymous`. How the user is resolved: * **`id`** and **`source_specific_id`** must match a customer that already exists in your organization. If no match is found, the request fails with a 400. * **`email`** alone does not require a pre-existing customer — if no matching Wavelength contact exists, one is created. ### Example: Minimal request `query` is the only required field. ```bash theme={null} curl -X POST https://api.wavelength.cx/interactions \ -H "Authorization: " \ -H "Content-Type: multipart/form-data" \ -F "query=Sample query text" ``` ### Example: With a user and a source-specific ID ```bash theme={null} curl -X POST https://api.wavelength.cx/interactions \ -H "Authorization: " \ -H "Content-Type: multipart/form-data" \ -F "title=Sample title" \ -F "query=Sample query text" \ -F "source_specific_id=my_ticket_123" \ -F 'user={"email": "user@example.com", "name": "John Doe"}' \ -F "original_timestamp=2024-06-10T18:34:00Z" ``` You can then add replies to this ticket using the same `source_specific_id`: ```bash theme={null} curl -X POST "https://api.wavelength.cx/interactions/reply?source_specific_id=my_ticket_123" \ -H "Authorization: " \ -F "content=This is a reply to the ticket" \ -F 'user={"email":"agent@example.com","name":"Support Agent"}' ``` ### Example: Attaching to a company by source-specific ID ```bash theme={null} curl -X POST https://api.wavelength.cx/interactions \ -H "Authorization: " \ -H "Content-Type: multipart/form-data" \ -F "title=Sample title" \ -F "query=Sample query text" \ -F "source_specific_id=unique_ticket_id" \ -F 'user={"email": "user@example.com", "name": "John Doe", "source_specific_id": "userSSID"}' \ -F "company_source_specific_id=companySSID" \ -F 'additional_properties={"key1": "value1", "key2": 123, "key3": "value3"}' \ -F "original_timestamp=2024-06-10T18:34:00Z" ``` ### Example: Attaching to a company by Wavelength ID ```bash theme={null} curl -X POST https://api.wavelength.cx/interactions \ -H "Authorization: " \ -H "Content-Type: multipart/form-data" \ -F "title=Sample title" \ -F "query=Sample query text" \ -F 'user={"email": "user@example.com", "name": "John Doe"}' \ -F "company_id=01945162-52fc-739b-98de-e2ac9f2eec49" \ -F 'additional_properties={"key1": "value1", "key2": 123, "key3": "value3"}' \ -F "original_timestamp=2024-06-10T18:34:00Z" ``` ### Example: Attaching files Upload files directly with `files`: ```bash theme={null} curl -X POST https://api.wavelength.cx/interactions \ -H "Authorization: " \ -H "Content-Type: multipart/form-data" \ -F "query=Sample query text" \ -F "files=@screenshot.png" ``` Attach multiple files by repeating the field: ```bash theme={null} curl -X POST https://api.wavelength.cx/interactions \ -H "Authorization: " \ -H "Content-Type: multipart/form-data" \ -F "query=Sample query text" \ -F "files=@screenshot_1.png" \ -F "files=@screenshot_2.png" ``` Or have Wavelength fetch images from URLs (comma-separated): ```bash theme={null} curl -X POST https://api.wavelength.cx/interactions \ -H "Authorization: " \ -H "Content-Type: multipart/form-data" \ -F "query=Sample query text" \ -F "files_image_url=https://example.com/img1.jpg,https://example.com/img2.jpg" ``` ### Response ```json theme={null} { "created_ticket_number": "ASM-111" } ``` | Field | Type | Description | | ----------------------- | ------ | --------------------------------------------------- | | `created_ticket_number` | string | The ticket number of the newly created interaction. | ### Error Responses Errors take the following format in JSON: ```json theme={null} {"error": "message"} ``` | Status | Meaning | | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | Invalid request — malformed form data, missing/invalid `query`, a `user` object with none of `email`/`id`/`source_specific_id`, an unresolvable user or company ID, a file that is too large or could not be opened, or an `original_timestamp` that is not valid RFC3339. | | 400 | Missing or invalid `Authorization` header, or an expired API key. | | 429 | Rate limit exceeded. | | 503 | Wavelength server error. It has been logged and our team has been notified. | ### Important Notes * **File Size Limit**: 4MB per file. * **Supported Image Types**: JPG and PNG. * **Rate Limiting**: 30 events per second. Exceeding this returns a 429. If you need a higher rate, please let us know. * **Anonymous Interactions**: Unlike `POST /interactions/reply`, the `user` field is optional here — omit it and the interaction is attributed to `Anonymous`. *** #### Create Interaction Reply ## POST /interactions/reply Creates a new reply/message for an existing ticket. Supports multiple ticket identification methods and file attachments. ### Query Parameters Exactly one of the following is required: | Parameter | Type | Description | | -------------------- | ------ | -------------------------------------------- | | `id` | string | Wavelength ULID of the ticket to reply to | | `source_specific_id` | string | Source-specific ID of the ticket to reply to | ### Headers * `Content-Type: multipart/form-data` (required for file uploads) * `Authorization: ` (required; raw key, no `Bearer` prefix) ### Request Body (multipart/form-data) | Field | Type | Required | Description | | -------------------- | ------- | -------- | ---------------------------------------------------- | | `content` | string | Yes | The reply message content | | `source_specific_id` | string | No | Unique identifier for this reply (for deduplication) | | `original_timestamp` | string | No | Original timestamp in RFC3339 format | | `user` | object | Yes | User information object | | `files` | file\[] | No | File attachments (multipart) | | `files_image_url` | string | No | Comma-separated URLs of images to attach | #### User Object | Field | Type | Required | Description | | -------------------- | ------ | -------- | ------------------------- | | `id` | string | No\* | User ID | | `name` | string | No | User name | | `email` | string | No\* | User email | | `source_specific_id` | string | No | User's source-specific ID | \*At least one of `id` or `email` is required. If only `email` is provided, it will be used as the user identifier. ### Ticket Identification The endpoint requires exactly one query parameter for ticket identification: * **`id`** - Wavelength ULID of the ticket * **`source_specific_id`** - Source-specific ID of the ticket Both parameters cannot be provided simultaneously, and at least one is required. ### Example Request (with Wavelength ULID) ```bash theme={null} curl -X POST "https://api.wavelength.cx/interactions/reply?id=01KKXRZM9PJRKJ488ZK3PB6JPX" \ -H "Authorization: " \ -F "content=This is my reply to the ticket" \ -F 'user={"name":"John Doe","email":"john@example.com"}' \ -F "source_specific_id=reply_12345" ``` ### Example Request (with source\_specific\_id) ```bash theme={null} curl -X POST "https://api.wavelength.cx/interactions/reply?source_specific_id=ticket_ss_123" \ -H "Authorization: " \ -F "content=Reply via source specific ID" \ -F "files=@attachment.pdf" \ -F 'user={"name":"Jane Doe","email":"jane@example.com"}' ``` ### Example Request (with file uploads) ```bash theme={null} curl -X POST "https://api.wavelength.cx/interactions/reply?source_specific_id=ticket_abc123" \ -H "Authorization: " \ -F "content=Here are the files you requested" \ -F "files=@document1.pdf" \ -F "files=@image.png" \ -F "files_image_url=https://example.com/image1.jpg,https://example.com/image2.jpg" \ -F 'user={"name":"Alice","email":"alice@example.com"}' ``` ### Response ```json theme={null} { "created_history_id": "01H8X9Z7Y2W3R4T5U6V7W8X9Y" } ``` | Field | Type | Description | | -------------------- | ------ | ---------------------------------- | | `created_history_id` | string | ULID of the created history record | ### Error Responses #### 400 Bad Request ```json theme={null} { "error": "content is required" } ``` ```json theme={null} { "error": "user is required" } ``` ```json theme={null} { "error": "user.id or user.email is required" } ``` ```json theme={null} { "error": "exactly one of id or source_specific_id must be provided" } ``` ```json theme={null} { "error": "invalid ULID format for id parameter" } ``` #### 404 Not Found ```json theme={null} { "error": "ticket not found for identifier: ticket_abc123" } ``` #### 409 Conflict (Duplicate) ```json theme={null} { "error": "source specific id already exists" } ``` #### 413 Payload Too Large ```json theme={null} { "error": "File size exceeds 32MB" } ``` ### Important Notes * **File Size Limit**: 32MB per file * **Supported File Types**: Any file type can be uploaded * **Deduplication**: Duplicate `source_specific_id` values are rejected for Slack/Community Slack sources * **User Requirement**: User object with `user.id` is now mandatory (no anonymous users) * **Query Parameter Exclusivity**: Exactly one of `id` or `source_specific_id` must be provided, not both * **Rate Limiting**: Subject to rate limiting (60 requests per minute per API key/IP) # MCP Server Source: https://docs.askassembly.app/usage/developer/mcp Connect Wavelength to Claude, Cursor, ChatGPT, or any MCP-compatible client, and ask questions about your accounts in plain English. **Read-only.** The Wavelength MCP server can look things up. It cannot send an email, change a field, or delete anything. ## Before you start * A Wavelength account with an active login. * An MCP-compatible client: Claude (desktop, web, or Code), Cursor, ChatGPT, or your own client built on the MCP SDK. * MCP is enabled for all Wavelength organizations by default — there is nothing to switch on. If a connection fails with "mcp is not enabled for this organization", MCP has been paused for your org; contact support. You sign in with your normal Wavelength login. You do not need an API key. ## Server details | Field | Value | | ---------- | ------------------------------- | | Server URL | `https://mcp.wavelength.cx/mcp` | | Transport | Streamable HTTP | | Auth | OAuth 2.1 (browser sign-in) | | Access | Read-only | ## Setup Find the **Connectors** section in your Claude settings. Click **Add custom connector**. Name: `Wavelength`. URL: `https://mcp.wavelength.cx/mcp`. Click **Connect**. A browser window opens; sign in to Wavelength and approve. The connector shows as connected, with the Wavelength tools listed. ```bash theme={null} claude mcp add --transport http wavelength https://mcp.wavelength.cx/mcp ``` Then run `/mcp` inside Claude Code and choose **Authenticate** for `wavelength`. **Settings → MCP → Add new MCP server**, or add to `~/.cursor/mcp.json`: ```json theme={null} { "mcpServers": { "wavelength": { "url": "https://mcp.wavelength.cx/mcp" } } } ``` Restart Cursor, then click **Sign in** next to the Wavelength server. **Settings → Connectors → Create**, using the same URL. Sign in when prompted. Point it at `https://mcp.wavelength.cx/mcp` over Streamable HTTP. The server advertises its authorization server at: ``` https://mcp.wavelength.cx/.well-known/oauth-protected-resource ``` An unauthenticated request returns `401` with a `WWW-Authenticate` header pointing at that document — that response starts the standard OAuth 2.1 flow. Dynamic client registration is supported. ## What you can do with it Everything below works today. The tool named on each line is what answers it — you never call tools by name yourself, but knowing which one runs tells you what the answer is built from. ### Prepare for your day | Ask | What comes back | Tool | | ----------------------------------------- | -------------------------------------------------------------------------------------- | ------------- | | *"What meetings do I have this week?"* | Your booked meetings, each with the account it belongs to | `get_my_week` | | *"What do I need to know before my 2pm?"* | The same meeting plus its pre-meeting brief | `get_my_week` | | *"What did I have last week?"* | Past meetings — the window looks backward as well as forward, up to 30 days either way | `get_my_week` | Meetings are the ones you are the assignee on. Scoped to you, not your org. ### Get up to speed on an account or a person | Ask | What comes back | Tool | | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------- | | *"Give me a brief on Vanta."* | Health, current CTA, 2 recent meeting notes, 5 recent interactions, up to 10 deals, up to 10 contacts — in one call | `get_brief` | | *"Who is Jane Smith and what account is she on?"* | Their role, account, and details | `get_brief` | | *"Who at Flosum do we talk to?"* | Every contact on the account, with role and email | `list_contacts` | | *"What's the account CTA for Acme?"* | The current call to action, if there is one | `get_brief` | Every section of a brief reports an exact total, like `"deals": "10 of 47"`, so you always know when you are seeing a capped view rather than everything. ### Work your book | Ask | What comes back | Tool | | ------------------------------------------------------------------- | ------------------------------------------- | --------------- | | *"Which of my accounts are at risk and renew in the next 60 days?"* | Thin account rows plus an exact total | `list_accounts` | | *"Enterprise accounts I haven't talked to in 30 days"* | Same, filtered on last interaction | `list_accounts` | | *"Accounts tagged Expansion in the onboarding stage"* | Same, filtered on tags and stage | `list_accounts` | | *"Accounts where \[your custom attribute] is X"* | Same, filtered on your org's own attributes | `list_accounts` | Filterable by health pulse, renewal date, last interaction, tier, stage, company type, tags, custom attributes, and whether the account is yours. At least one filter is required, and 25 rows come back per call. ### Look at deals | Ask | What comes back | Tool | | ----------------------------------- | --------------------------------------------- | ------------ | | *"Open deals closing this quarter"* | Deals with stage, amount, close date, account | `list_deals` | | *"Deals over \$50k in Negotiation"* | Same, filtered on amount and stage | `list_deals` | | *"What's open on Acme?"* | Same, narrowed to one account | `list_deals` | Sorted by close date, newest first, with an exact total and paging. It lists deals; it does not total them. ### Find what was actually said | Ask | What comes back | Tool | | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------- | | *"What did we say to Acme about the SSO request?"* | Matching threads by meaning, scoped to that account | `search` | | *"What came up about pricing in Vanta's meetings?"* | Matching meeting notes and recordings | `search` | | *"What are people asking about billing this month?"* | Matching support threads across the org, by meaning and keyword | `search` | | *"What's escalating on the Support team right now?"* | Same, narrowed to one team | `search` | | *"Find the thread where I promised them a migration date"* | Matches across the org, folding in your own mailbox for anything not linked to an account | `search` | | *"Pull the full transcript of that call"* | The complete note, summary, action items, and transcript | `get_content` | | *"Show me that whole email thread"* | The message-by-message thread, oldest first, plus topic, category and attributes | `get_content` | Search covers one corpus per call — `interactions` (email and support threads) or `notes` (meeting notes and recordings). With an account, `interactions` search is scoped to it. With no account, it searches the whole organization — or one team, if you name one — by both meaning and keyword. Personal email that was never linked to an account is only ever visible to its owner, so your own mailbox is folded into that same org-wide result rather than being a separate mode. Search finds the item; `get_content` reads it. ### Filter tickets and threads by field | Ask | What comes back | Tool | | ------------------------------------------------- | ------------------------------------------------- | ------------------- | | *"What open requests are assigned to me?"* | Matching interactions, newest first, with a total | `list_interactions` | | *"How many tickets did Acme open last month?"* | Just the count, when the number is the answer | `list_interactions` | | *"Unassigned support threads from the last week"* | Same, filtered on assignee and window | `list_interactions` | | *"Everything from Zendesk on this account"* | Same, filtered on source | `list_interactions` | | *"Tickets tagged 'Refund Request'"* | Same, filtered on topic | `list_interactions` | | *"Tickets categorized OPS ESCALATION"* | Same, filtered on category | `list_interactions` | | *"Open tickets on the Support team"* | Same, filtered on team | `list_interactions` | Filter on status, assignee (`"me"`, `"unassigned"`, or a teammate), source, account, team, topic, category, and time window. Defaults to the last 90 days. Each row carries `topic`, `bot_category`, `owner_replies`, `customer_replies`, and any attributes, in addition to the usual fields. 25 rows come back per call; page past that with `offset`. Use this when the question is about fields; use `search` when it is about wording. **Nothing is silently dropped.** If a row is withheld — the only case today is a colleague's personal email that was never linked to an account — the response says so via `privacy_filtered_count` and a `note`, rather than quietly returning fewer rows than `total_count` implies. ### Get defined numbers | Ask | What comes back | Tool | | ------------------------------------------------------ | ------------------------------------- | ------------ | | *"What's our average first response time this month?"* | The figure plus its definition inline | `get_metric` | | *"How many SLA breaches last quarter, by team?"* | Same, filtered by team | `get_metric` | | *"Average resolution time for tickets from Intercom"* | Same, filtered by source | `get_metric` | Five support metrics, filterable by team, source, assignee, and date window. Each result states what it counted and what it excluded. See the [full list](#defined-support-metrics). ### Speak your organization's language | Ask | What comes back | Tool | | ---------------------------------------------- | --------------------------------------------------- | ------------------ | | *"What counts as at risk here?"* | Your org's actual definition, not a generic one | `search_semantics` | | *"What does this health driver measure?"* | The same | `search_semantics` | | *"Did you mean Acme Corp or Acme Industries?"* | Every candidate, so the model can pick from context | `resolve` | | *"What ticket categories do we use?"* | Every configured category name for your org | `resolve` | `resolve` runs automatically behind the other tools — you rarely ask for it directly. It is what lets you say "Vanta" instead of pasting an id, and it is also how you look up the exact wording of a ticket topic or category before filtering `list_interactions` or `search` on it. ### The typical loop Start with your week, pull a brief for each account you are meeting, then dig into the underlying conversations when something needs a closer look. Most sessions are `get_my_week` → `get_brief` → `search` → `get_content`. **One difference you may notice.** MCP can surface meeting notes that the Wavelength web app currently hides. Notes ingested from some notetakers are marked inactive in the app but are still real, still yours, and still useful context — so MCP reads them rather than pretending they don't exist. If a note shows up here that you can't find in the web app, that is why, not a bug. ## Tools All tools are read-only, and all are scoped to your organization automatically. You never pass an organization or user id — the server takes both from your sign-in. | Tool | What it does | Worth knowing | | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `wavelength_v1_api_read_get_my_week` | Your booked meetings over a window, each with its account and pre-meeting brief. | 7-day window by default, 30 max, forward or backward. Only meetings you are the assignee on. | | `wavelength_v1_api_read_get_brief` | A fixed-size snapshot of one account or one person: health, current CTA, 2 recent notes, 5 recent interactions, up to 10 deals, up to 10 contacts. | Every section states an exact total, like `"deals": "10 of 47"`, so a capped section is never mistaken for an empty one. Bounded on purpose — cheap enough to call before every meeting. | | `wavelength_v1_api_read_list_deals` | Deals across your organization, filtered by stage, amount, close date, or account. | Sorted by close date, newest first, with an exact total and paging. Lists deals; does not total them. Deal type is not filterable or returned yet. | | `wavelength_v1_api_read_list_contacts` | Every contact on one account, with role and email. | Requires an account. Exact total, pages via `offset`. | | `wavelength_v1_api_read_list_accounts` | Your book filtered by health pulse, renewal date, last interaction, tier, stage, company type, tags, custom attributes, or ownership. | 25 rows per call. At least one filter is required — "list all my accounts" is refused on purpose. Too broad a filter returns a count and a request to narrow, not a truncated list. | | `wavelength_v1_api_read_search` | Meaning-based search over one corpus per call: `interactions` (email and support threads) or `notes` (meeting notes and recordings). | Scope to an account for meaning-based results. With no account, `interactions` searches org-wide — or one team, with `teams` — by both meaning and keyword. Personal email never linked to an account stays visible only to its owner, folded into the same result rather than a separate mode. `notes` still requires an account in this release. Returns summaries; use `get_content` to read the full thing. | | `wavelength_v1_api_read_list_interactions` | Structured filtering over tickets, email threads, and conversations by status, assignee, source, account, team, topic, category, or time window. | Last 90 days unless you say otherwise. `assignee` takes `"me"`, `"unassigned"`, or a teammate. Rows carry `topic`, `bot_category`, `owner_replies`, `customer_replies`, and attributes. 25 rows, newest first, with a total; page past that with `offset`. `count_only` returns just the number. Over 1000 matches, it refuses and asks you to narrow. Any row withheld by the email-privacy rule is counted, not silently dropped — see `privacy_filtered_count`. | | `wavelength_v1_api_read_get_content` | The full text behind a search result. | Notes return the full note, summary, action items, and transcript where one exists. Interactions return the message-by-message thread (up to 40 messages, oldest first, each tagged customer or support), plus status, source, topic, category, reply counts, and attributes — `messages_truncated` is set if a thread ran longer than that. Subject to the email rule below. | | `wavelength_v1_api_read_get_metric` | A defined support metric, with its definition returned inline. | Five metrics, listed below. Filterable by team, source, assignee, and window. Nothing outside that list is answerable. | | `wavelength_v1_api_read_search_semantics` | Looks up what Wavelength's own terms mean — at risk, health drivers, tiers, SLAs, how filters combine. | Reads a fixed table, not your data. This is what stops the model inventing its own definition of your vocabulary. | | `wavelength_v1_api_read_resolve` | Turns free text into a specific company, contact, team, teammate, interaction topic, or interaction category. | Runs automatically behind the other tools. `kind: "topic"` and `kind: "category"` are their own namespace — separate from account-level labels — so you can look up the exact wording of a topic or category before filtering on it. On an ambiguous name it returns every candidate rather than guessing. | ### What you can see in email Everything that is not email — support tickets, Slack-sourced threads, and the rest — is visible to everyone in your organization, as it is in the app. Email is narrower: | Email | Who can read it | | ------------------------ | ----------------------------------- | | Linked to an account | anyone in your organization | | Not linked to an account | only the person whose mailbox it is | So a colleague's personal correspondence never appears in your results, and yours never appears in theirs. A shared mailbox such as `support@` belongs to no individual, so mail there that was never linked to an account is not reachable through these tools at all — open it in Wavelength instead. One practical consequence: `get_content` can return fewer items than the ids you gave it. That is this rule at work, not an error. ### Defined support metrics Numbers come from a fixed list, not from a general "sum this column" tool. All five are filterable by team, source, assignee, and date window: | Metric | What it measures | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `avg_first_response_time` | Mean time from a customer's first message to the first human reply. | | `avg_response_time` | Mean time a ticket sat in needs-response before being picked up. Not the same as first response — don't use them interchangeably. | | `avg_resolution_time` | Mean time from a ticket's first message to close, over tickets **closed** in the window. | | `sla_breach_count` | Number of SLA breaches in the window. | | `avg_sla_breach_duration` | Mean overrun on breached SLAs. | Every result carries its own definition inline, including what it excludes. All are wall-clock, not business hours. **Not available**: deal totals, pipeline sums, ARR, account-level aggregates, and timer totals. If a number is not in the table above, the server says so rather than approximating it. That is on purpose: a plausible wrong number in a QBR is worse than no number. ## How your data is scoped * **Everything is limited to one organization** — the one on your sign-in. There is no cross-organization read, search, or comparison, and no tool accepts an organization as a parameter. * **The server knows who you are, not just which org you are in.** Your sign-in gives it both. Anything phrased as "mine" resolves to you personally: `get_my_week` returns only meetings you are the assignee on, and `assignee: "me"` on `list_interactions` and `assignee_scope` on `list_accounts` mean you. A no-account `search` over interactions reaches the whole org (or one team, if you name one), not just your inbox — your own mailbox is simply the subset of that result that includes your personal email, since email never linked to an account is otherwise visible only to its owner. * **Account data itself is organization-wide.** Any connected user can read any account in their organization, including other reps' notes and meetings. This matches what the Wavelength web app already allows. The one exception is email that was never linked to an account, which stays private to its owner — see [What you can see in email](#what-you-can-see-in-email). If your organization needs tighter limits than that, talk to us before rolling MCP out broadly. * **Authorization is checked on every request**, not once per session. Token validation is cached briefly, so revocation can take up to about a minute to take effect. * **Read-only.** No tool can change anything in Wavelength. ## Limits ### Rate limits There are two layers. Every request passes through a connection limit first; the tool then checks its own tier budget. #### Connection limits | Limit | Value | | -------------------------------------- | --------------------- | | Per organization | 120 requests / minute | | Per connection (each connected client) | 60 requests / minute | This is the ceiling in practice — a burst of cheap lookups hits the 120/minute org limit long before it hits the read tier's 100/second. Exceeding it returns HTTP `429`. #### Tool tiers Tier limits are **per organization** — shared across everyone in your org who has connected — and are set per tier, because the tools do very different amounts of work. | Tier | Limit | Tools | | --------------- | ---------------------- | ------------------------------------------------------------------------------------------- | | Read | 100 / second | `resolve`, `get_my_week`, `get_content`, `list_interactions`, `list_deals`, `list_contacts` | | Brief | 10 / second | `get_brief` | | Search | 300 / minute | `list_accounts` | | Semantic search | 2 / second | `search` | | Reporting | 2 / second | `get_metric` | | — | connection limits only | `search_semantics` | `search_semantics` carries no tier of its own because it does not query anything — it looks up Wavelength's own vocabulary in a fixed table held in memory. Limiting it would only push the model into guessing what your terms mean, which is the failure it exists to prevent. It still passes through the connection limits above like every other call. **Do not confuse it with `search`**, which does search your data and sits on the tightest tier in the table. Tiers are counted separately, so heavy searching never uses up the budget for cheap lookups. If you hit a limit you get a clear error naming the tier and how many seconds to wait — never a silent truncation and never an empty result. ### Other limits | Limit | Value | | ----------------------------------------- | --------------------------------------------------------------------- | | Rows per list or search call | 25 | | Messages per interaction in `get_content` | 40, oldest first — `messages_truncated` is set if a thread ran longer | | Meeting window | 30 days max | | Time per tool call | 30 seconds | **Results are never silently cut short.** Every list response tells you both how many rows came back and how many matched in total. If a query is too broad, the server says so and tells you how to narrow it. ## Troubleshooting Your admin has not turned MCP on. Contact them, or email support. Check that you signed in with the same email you use for Wavelength, and that the account belongs to the organization you expect. Two common causes: the name did not resolve (try the exact account name, or ask the model to resolve it first), or the time window excluded it (search defaults to a recent window — say "in the last year" explicitly). Your token expired or was revoked. Reconnect from your client's connector settings. Broad filters over tens of thousands of accounts take longer. Narrowing the filter is faster than paging. ## FAQ Talk to your Wavelength account team. Yes — it is a standard MCP server over Streamable HTTP with OAuth 2.1. Nothing about it is Claude-specific. Yes, in the sense that any tool result your client requests is passed to the model you are using, the same as anything you paste into a chat. The MCP server itself does not send your data anywhere else. This release is deliberately read-only. Write support is the next thing we are scoping. The `wavelength_v1_` prefix is a version marker. Names within `v1` are stable; a breaking change ships as `v2` alongside it. ## What it can't do (yet) | Not supported | Why | | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Sending or drafting email, updating fields, creating records | The server is read-only in this release. | | Ranking your book ("what should I work on today?") | You can *filter* your book by your own criteria; Wavelength does not rank it for you. | | Searching meetings by participant across the org | You can see your own booked meetings. Org-wide meeting search is not available yet. | | Filtering deals by deal type | Not filterable and not returned yet. Coming in a later release. | | Deal totals, pipeline sums, and ARR | Only the named metrics above are available. Anything not on that list is not answerable here. | | Semantic search inside a long call recording | Recordings match as a whole, not passage by passage. | | "Every email mentioning X" | Email search finds threads owned by the people you scope to. Someone who was only cc'd may not appear. | | Slack conversations outside tickets | Not indexed for search. | | Guaranteed verbatim message text | `get_content` returns each message in a thread, but prefers a generated summary over the raw body when one exists — you get the gist of each message, not always its literal wording. | ## Support Email **[support@wavelength.cx](mailto:support@wavelength.cx)**. Include your organization name, the client you are using, and the question you asked. # Users Source: https://docs.askassembly.app/usage/developer/usersAPI List and look up workspace members via the external API. # Users List and look up workspace members via the external API. ## Authentication All endpoints require an `Authorization` header with a valid org API key: ```http theme={null} Authorization: ``` ## Endpoints ### List users (paginated) ```http theme={null} GET /list-users?page=&page_size= ``` Returns a paginated list of users in the workspace/org associated with the API key. #### Query parameters * `page` (optional, integer, default: `0`) — Zero‑based page index. * `page_size` (optional, integer, default: `10`, max: `100`) — Number of users to return per page. #### Response ```json theme={null} { "users": [ { "id": "a1b2c3d4-e5f6-7890-1234-567890abcdef", "email": "alice@example.com", "username": "alice", "first_name": "Alice", "last_name": "Smith", "picture_url": "https://example.com/alice.jpg", "user_role": "Admin", "calendar_link": "https://calendly.com/alice" }, { "id": "f0e9d8c7-b6a5-4321-fedc-ba9876543210", "email": "bob@example.com", "username": "bob", "first_name": "Bob", "last_name": "Jones", "picture_url": "https://example.com/bob.jpg", "user_role": "Member", "calendar_link": null } ], "page": 0, "page_size": 10, "has_more": true } ``` ### Get a single user by ID ```http theme={null} GET /fetch-user?id= ``` Fetch a single user by their UUID. The user must belong to the workspace/org associated with the API key. #### Response ```json theme={null} { "user": { "id": "a1b2c3d4-e5f6-7890-1234-567890abcdef", "email": "alice@example.com", "username": "alice", "first_name": "Alice", "last_name": "Smith", "picture_url": "https://example.com/alice.jpg", "user_role": "Admin", "calendar_link": "https://calendly.com/alice" } } ``` ### Get a single user by email ```http theme={null} GET /fetch-user?email= ``` Fetch a single user by their email address (case-insensitive). The user must belong to the workspace/org associated with the API key. #### Response ```json theme={null} { "user": { "id": "a1b2c3d4-e5f6-7890-1234-567890abcdef", "email": "alice@example.com", "username": "alice", "first_name": "Alice", "last_name": "Smith", "picture_url": "https://example.com/alice.jpg", "user_role": "Admin", "calendar_link": "https://calendly.com/alice" } } ``` #### Errors * `400 Bad Request` if: * Neither `id` nor `email` is provided. * Both `id` and `email` are provided. * The user ID or email is not found in your workspace. * `401 Unauthorized` if the API key is missing or invalid. # Labels Source: https://docs.askassembly.app/usage/platform/labels Wavelength has two types of labels: "interaction" and "account". Interaction Categories refers to whether the support request is a bug, feature request, or a general question. This label is automatically attached to every incoming support request. You can change labels here:

Request type Interaction Tag is about the product this support request is about. Examples of these tags are “billing”, “account management”, “authentication”, and more. You can turn on AI auto-tagging for these topic labels by clicking on the AI-Autotagging toggle!

Request Topic To create your own tag, click on the “add tag” button and provide a title and description for the label. You can filter through these tags on the labels table below

Tier: the payment method Stage: where the user is in the post-sale lifecycle (onboarding, live, etc) Company: “size” of the client Tags: entirely custom. You may add / edit any of these labels by clicking the “+ Add Label button”

# Links Source: https://docs.askassembly.app/usage/platform/links When on a support interaction, you can link, or attach, an external issue such as a Jira, Linear, or GitHub ticket directly to the support request.When on a support interaction, you can link, or attach, an external issue such as a Jira, Linear, or GitHub ticket directly to the support request.' If these integrations have been enabled, just click on the “link external issue” button and create the ticket.

You can also link a product insight (covered in the customer success section of the documentation) to a support interaction. If there is an existing product insight, simply hit the “link insight” button and link the appropriate ones to this support interaction.
# Knowledge Search Source: https://docs.askassembly.app/usage/platform/search Wavelength offers platform level search across not just support requests, but also your entire knowledge base (regardless of where it is). You may search through slack messages, your ticketing system (linear/jira), help center (intercom/zendesk), notion, confluence, even PR’s if you’d like.

Navigate to the manage integrations page and choose which parts of your knowledge base you would like to add to our search algorithm. # Teams Source: https://docs.askassembly.app/usage/platform/teams Support ticketing in Wavelength is made up of “inboxes”, which are team based or private to you. During setup, you must create at least one team (typically a “general team”) which will have access to the “all Issues” inbox. ### Team Setup If you have more than one team, you may create them here : Dashboard > Preferences > on the left hand sidebar:

Once a team has been created - you’ll need to assign accounts the team has access to under its settings page. All new incoming tickets will be synced to each team accordingly.

Once created, each individual team will be able to see their own inbox, assign tickets to team members, create their own SLA’s & tags, and more. #### General Team The General team on the left hand sidebar represents the home of Wavelength. All issues can be viewed here, setup, overarching analytics and companies. Each team serves as a subset of issues of the General team. Each remaining team contains the interactions for the accounts assigned to the team. This will also start to contain team-based Analytics and Product Insights. # Workflows & Rules Source: https://docs.askassembly.app/usage/platform/workflows&rules Workflows are the backbone for most automations within the platform. From responding to customer support requests with a templated response to following up with a renewal notice, we hope our workflow builder will accomplish all of that and more. #### Setup To create a workflow, tap on the “+ Create” on the top righthand corner of the page.

#### Creating a Workflow Once you’ve decided which type of workflow you would like to create, you’ll see the following page

You’ll notice the workflow tool bar on the left hand side with the following elements: triggers, conditions, waits, and actions. By design, a workflow needs a trigger, a condition, the lack of which suggests a blanket action for a specific trigger, and an action. With that being said, we’ll dive into what each of these mean #### Triggers A trigger is the element that initiates a workflow. Interaction triggers could include items such as a new interaction being created, the status being updated, a label being changed, etc. An account trigger includes elements such as when a health score is updated, specific attributes (usage, sentiment, etc) changes, as well as when the account increases their payment plan. br/>
#### Conditions The condition(s) is the set of dependencies that the trigger may or must have for the workflow to run. On the interaction side, this could include interaction specific field updates such as status, label, assignee, team, and more. Again, the account workflow condition set reflects account size, renewal date, pricing tier, and more. br/>
#### Waits Typically used for followups or adding a buffer between a trigger and the action, the “wait” time is exactly as advertised. Simply drag and drop the wait component and select the amount of time an action must wait before being triggered. #### Actions These are the meat & potatoes of workflows. The outcome you would like to drive via interaction or account workflows are determined by the action or set of actions you setup. On the interaction side of things, the actions range from replying to customer interactions with templates (or an AI) to updating statuses, tags, and more on the interaction itself. Accounts on the other hand have actions such as checking in with the customer, sending renewal notices, sending slack messages to CSM’s about changes in the customer’s health, and much more.

# Alerting Source: https://docs.askassembly.app/usage/support/alert To set up slack notifications for new support requests, issues that have been updated by customers, and to avoid breaching SLA’s, navigate to the Alerts page underneath preferences

You can customize what kinds of requests you would like to receive notifications for. To select which slack channel to funnel these notifications, hit the enable notifications toggle at the bottom of the page and choose a channel. We recommend that you create your own “assembly-notifications” channel to keep support requests separate from other notifications in slack. # AskAI Source: https://docs.askassembly.app/usage/support/askai Use the Wavelength AI co-pilot to help resolve support requests by searching through your knowledge base When responding to frequently asked questions or if you’re faced with a question you know whose answer exists in your KB (notion, Google drive, Confluence, Linear, etc), hit the “Ask AI” button and Wavelength will use it’s platform search to query your entire KB for the answer. # Business Hours Source: https://docs.askassembly.app/usage/support/businesshours To set business hours for yourself or for your team, navigate to your profile or the members tab under team. Hit the “Add Hours” and customize when you would like to receive notifications, have SLA’s turned on, and more. # Issues & Inboxes Source: https://docs.askassembly.app/usage/support/issues Support ticketing in Wavelength is made up of “inboxes”, which are team based or private to you. During setup, you must create at least one team (typically a “general team”) which will have access to the “all Issues” inbox. If you have more than one team, you may create them here: Once created, each individual team will be able to see their own inbox, assign tickets to team members, create their own SLA’s & tags, and more. # Service Level Agreements (SLA) Source: https://docs.askassembly.app/usage/support/slas SLA are the Service Level Agreements that you have with your customers. To set one up, go to the preferences page from the dashboard and navigate to the SLA section There you’ll be able to determine an SLA with a breach limit (how long you have to respond) and a warning notification (when you’ll receive a bump in slack or another messenger to respond prior to breaching) If you are unable to respond within the SLA period, the ticket will move under the “breaching” status. Once responded to, the ticket’s SLA timer will reset for the next customer message. # Statuses Source: https://docs.askassembly.app/usage/support/statuses Support tickets unattached to an SLA have 3 statuses: needs response, open, and closed. Needs response refers to a ticket that has either been newly raised by a customer or an existing “open” ticket that has been updated by the customer. Open tickets are those that are waiting for the customer to respond or take action. Typically this could be after your first response or after you have resolved the issue and are waiting for confirmation from the customer. Once issues are resolved or are stale, you can move the ticket into the “closed” state. Note, that if a customer responds to an issue previously closed, it will return to the “needs response” state.