> ## Documentation Index
> Fetch the complete documentation index at: https://docs.askassembly.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Accounts

> Send events from your own integrations to Assembly. This is a great way to track and update your accounts. 

## API Key

You must first create an API Key in Assembly. 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 Assembly 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 Assembly 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 Assembly 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: <API_KEY>" \
 -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": ...}}`.
The accounts are sorted by type so all Company Accounts will show up before Customer Accounts. Within each account type section, 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.

Company Accounts also include an `insight` field — a nullable object containing the latest AI-generated call-to-action for the account. It has two fields: `content` (the insight text) and `description` (optional context from the insight metadata). When no insight exists the field is `null`. This field is not populated for Customer Accounts.

```bash theme={null}
curl -X \
 GET "https://api.wavelength.cx/accounts?limit=50&offset=0" \
 -H "Authorization: <API_KEY>"
```

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 Assembly 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: <API_KEY>"
```

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`) and covers both companies and contacts.

For Company Accounts, `updated_after` also matches when the account's `insight` 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 `insight`.

```bash theme={null}
curl -X \
 GET "https://api.wavelength.cx/accounts?updated_after=2026-06-01T00:00:00Z&limit=50&offset=0" \
 -H "Authorization: <API_KEY>"
```

To grab a specific account, you can use the ID from Assembly, 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: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET https://api.wavelength.cx/accounts/account.com \
 -H "Authorization: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET "https://api.wavelength.cx/accounts?source_specific_id=accountSSID" \
 -H "Authorization: <API_KEY>"
```

#### 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.

**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 `insight` 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: <API_KEY>" \
  -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: <API_KEY>" \
  -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: <API_KEY>" \
  -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 Assembly 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 Assembly 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: <API_KEY>" \
 -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 Assembly 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 Assembly 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: <API_KEY>" \
 -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: <API_KEY>" \
-d '{
   "type": "Company",
   "name": "account_name",
   "domain": "account.com"
}'
```

#### Delete Account

Delete an account by referencing the account with its Assembly 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: <API_KEY>"
```

## 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 Assembly 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 Assembly 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 Assembly ID for the contact.

The Assembly 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: <API_KEY>" \
 -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": ...}}`.

```bash theme={null}
curl -X \
 GET "https://api.wavelength.cx/contacts?limit=50&offset=0" \
 -H "Authorization: <API_KEY>"
```

To grab a company account's contacts, you can use the ID from Assembly, 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: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET https://api.wavelength.cx/contacts/company/company.com?limit=50&offset=0\
 -H "Authorization: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET "https://api.wavelength.cx/contacts/company?company_source_specific_id=companySSID&limit=50&offset=0" \
 -H "Authorization: <API_KEY>"
```

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: <API_KEY>"
```

To grab a specific contact, you can use the ID from Assembly, 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: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET https://api.wavelength.cx/contacts/name@company.com\
 -H "Authorization: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET "https://api.wavelength.cx/contacts?source_specific_id=contactSSID" \
 -H "Authorization: <API_KEY>"
```

#### Update Contact

Update any of the contact's fields via the Assembly 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 Assembly 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: <API_KEY>" \
 -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 Assembly 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 Assembly 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: <API_KEY>" \
 -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: <API_KEY>" \
-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 Assembly 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: <API_KEY>"
```

## Deals

Manage deals (opportunities) linked to your company accounts through these API endpoints. You can reference a deal by its Assembly 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 Assembly UUID. The Assembly 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: <API_KEY>" \
 -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 - Assembly user UUID
    "time_length": 3,                                # Optional
    "time_unit": "months",                           # Optional
    "additional_properties": {                       # Optional - custom attributes
       "Custom Field": "value"
    }
 }'
```

#### Get Deal

Fetch a single deal by its Assembly 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: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET "https://api.wavelength.cx/deals/01945162-52fc-739b-98de-e2ac9f2eec49?source_specific_id=deal-ext-001" \
 -H "Authorization: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET "https://api.wavelength.cx/deals?source_specific_id=deal-ext-001" \
 -H "Authorization: <API_KEY>"
```

#### 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: <API_KEY>"
```

```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: <API_KEY>"
```

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: <API_KEY>"
```

#### Update Deal

Update any fields on an existing deal. Reference the deal by its Assembly 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 Assembly ID of the updated deal is returned in the format `{"id":"01945162-52fc-739b-98de-e2ac9f2eec49"}`.

```bash theme={null}
# By Assembly ID
curl -X \
 PUT https://api.wavelength.cx/deals/01945162-52fc-739b-98de-e2ac9f2eec49 \
 -H "Content-Type: application/json" \
 -H "Authorization: <API_KEY>" \
 -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: <API_KEY>" \
 -d '{
    "stage": "Negotiation",
    "amount": 30000.00
 }'
```

#### Upsert Deal

A combination of create and update. If the deal already exists (matched by Assembly 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 Assembly 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 Assembly 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: <API_KEY>" \
 -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 Assembly ID
curl -X \
 PUT https://api.wavelength.cx/deals/upsert/01945162-52fc-739b-98de-e2ac9f2eec49 \
 -H "Content-Type: application/json" \
 -H "Authorization: <API_KEY>" \
 -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 Assembly 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 Assembly ID of the deleted deal is returned in the format `{"id":"01945162-52fc-739b-98de-e2ac9f2eec49"}`.

```bash theme={null}
# By Assembly ID
curl -X \
 DELETE https://api.wavelength.cx/deals/01945162-52fc-739b-98de-e2ac9f2eec49 \
 -H "Authorization: <API_KEY>"
```

```bash theme={null}
# By source specific ID
curl -X \
 DELETE "https://api.wavelength.cx/deals?source_specific_id=deal-ext-001" \
 -H "Authorization: <API_KEY>"
```

## 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.

#### 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: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET https://api.wavelength.cx/list/deals/attributes \
 -H "Authorization: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET https://api.wavelength.cx/list/interactions/attributes \
 -H "Authorization: <API_KEY>"
```

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 Assembly 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 Assembly 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: <API_KEY>" \
 -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: <API_KEY>" \
 -d '{
    "property1": "value1",
    "property2": "value2"
 }'
```

#### Get Additional Properties

Specify the item (`accounts`, `contacts`, or `deals`) and either the Assembly 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: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET https://api.wavelength.cx/add_props/accounts/account.com \
 -H "Authorization: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET "https://api.wavelength.cx/add_props/accounts?source_specific_id=accountSSID" \
 -H "Authorization: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET https://api.wavelength.cx/add_props/deals/01945162-52fc-739b-98de-e2ac9f2eec49 \
 -H "Authorization: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET "https://api.wavelength.cx/add_props/deals?source_specific_id=dealSSID" \
 -H "Authorization: <API_KEY>"
```

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: <API_KEY>"
```

```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: <API_KEY>"
```

#### Update Additional Properties

Update an item's additional properties by specifying the item (`accounts`, `contacts`, or `deals`) and either the item's Assembly 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: <API_KEY>" \
 -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: <API_KEY>" \
 -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 Assembly 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: <API_KEY>" \
  -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: <API_KEY>" \
  -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 Assembly — for example, a year of monthly Health Scores from your old system.

The additional property must already exist — it's looked up by its Assembly 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: <API_KEY>" \
 -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: <API_KEY>" \
 -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 Assembly 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: <API_KEY>" \
 -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: <API_KEY>" \
 -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: <API_KEY>" \
 -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: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET https://api.wavelength.cx/accounts/labels/tier \
 -H "Authorization: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET https://api.wavelength.cx/accounts/labels/0195a666-3405-7664-aaf1-ad65b6091e96 \
 -H "Authorization: <API_KEY>"
```

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: <API_KEY>"
```

#### 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: <API_KEY>" \
 -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: <API_KEY>"
```

## 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: <API_KEY>" \
 -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: <API_KEY>" \
 -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 Assembly 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: <API_KEY>" \
 -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: <API_KEY>" \
 -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: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET "https://api.wavelength.cx/usage/accounts/01945162-52fc-739b-98de-e2ac9f2eec49?limit=50&offset=0" \
 -H "Authorization: <API_KEY>"
```

```bash theme={null}
curl -X \
 GET "https://api.wavelength.cx/usage/contacts?source_specific_id=contactSSID&limit=50&offset=0" \
 -H "Authorization: <API_KEY>"
```

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: <API_KEY>"
```

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: <API_KEY>"
```

#### 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: <API_KEY>" \
 -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: <API_KEY>"
```

## 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: <API_KEY>` (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: <API_KEY>" \
 -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.
