# Batch create contacts Source: https://developers.heymarket.com/api-reference/contacts/bulk api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/batch/contacts Create multiple contacts with the legacy v1 batch endpoint. Use this endpoint when you need to create many v1 contacts in one request. # Bulk delete contacts (v2) Source: https://developers.heymarket.com/api-reference/contacts/bulk-delete-v2 api-reference/openapi/heymarket-openapi-3.1.2.yaml DELETE /v2/contacts Bulk delete v2 contacts by UUID. Use this endpoint when you need to delete multiple v2 contacts in one request. # Bulk create contacts (v2) Source: https://developers.heymarket.com/api-reference/contacts/bulk-v2 api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v2/contacts Bulk create or update v2 contacts. Use this endpoint for v2 bulk contact imports and updates. # Create contact channel Source: https://developers.heymarket.com/api-reference/contacts/channels api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v2/contact/{contactID}/channel Add a phone, email, or other channel to a v2 contact. Use this endpoint to add another way to reach an existing contact. # Delete contact channel Source: https://developers.heymarket.com/api-reference/contacts/channels-delete api-reference/openapi/heymarket-openapi-3.1.2.yaml DELETE /v2/contact/{contactID}/channel/{channelType} Delete a channel from a v2 contact. Use this endpoint to remove a phone, email, or other channel from a contact. # Get contact channel Source: https://developers.heymarket.com/api-reference/contacts/channels-get api-reference/openapi/heymarket-openapi-3.1.2.yaml GET /v2/contact/{contactID}/channel/{channelType} Get one channel from a v2 contact. Use this endpoint to retrieve one channel type for a contact. # List contact channels Source: https://developers.heymarket.com/api-reference/contacts/channels-list api-reference/openapi/heymarket-openapi-3.1.2.yaml GET /v2/contact/{contactID}/channels List all channels for a v2 contact. Use this endpoint to inspect every channel attached to a contact. # Update contact channel Source: https://developers.heymarket.com/api-reference/contacts/channels-update api-reference/openapi/heymarket-openapi-3.1.2.yaml PUT /v2/contact/{contactID}/channel Update a channel on a v2 contact. Use this endpoint when a contact channel value changes. # List contact fields Source: https://developers.heymarket.com/api-reference/contacts/contact-fields api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/contact-fields List custom contact fields for your team. Use this endpoint to get custom field IDs before setting values in a contact `custom` object. # Count contacts Source: https://developers.heymarket.com/api-reference/contacts/count api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/contacts/count Get the count of contacts that match the pagination filters. Use this endpoint before exports or sync jobs that need a total contact count. # Create contact Source: https://developers.heymarket.com/api-reference/contacts/create api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/contact Create a contact with the v1 phone-number-based endpoint. Use this endpoint when you want to create a contact with a primary phone number. > [!NOTE] > > Phone numbers use E.164 digits without the leading plus sign. # Create contact fields Source: https://developers.heymarket.com/api-reference/contacts/create-contact-fields api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/contacts/fields Create custom contact fields for your team. Use this endpoint to create custom fields that can later be set through a contact `custom` object. # Create contact (v2) Source: https://developers.heymarket.com/api-reference/contacts/create-v2 api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v2/contact Create a contact with v2 contact channels. Use this endpoint when a contact can have multiple channel identifiers, such as phone and email. # Delete contact Source: https://developers.heymarket.com/api-reference/contacts/delete api-reference/openapi/heymarket-openapi-3.1.2.yaml DELETE /v1/contact/{id} Delete a contact by integer ID. > [!WARNING] > > Deleting a contact is permanent. Confirm the ID before sending this request. # Delete contact (v2) Source: https://developers.heymarket.com/api-reference/contacts/delete-v2 api-reference/openapi/heymarket-openapi-3.1.2.yaml DELETE /v2/contact/{contactID} Delete a contact by UUID. > [!WARNING] > > Deleting a contact is permanent. Confirm the UUID before sending this request. # Get contact Source: https://developers.heymarket.com/api-reference/contacts/get api-reference/openapi/heymarket-openapi-3.1.2.yaml GET /v1/contact/{id} Get a contact by integer ID. Use this endpoint when you already have the v1 integer contact ID. # Get contact by phone or ID Source: https://developers.heymarket.com/api-reference/contacts/get-by-phone-or-id api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/contact/get Get a contact by phone number or integer ID. Use this endpoint when your integration has a phone number but not the contact ID. # Get contact (v2) Source: https://developers.heymarket.com/api-reference/contacts/get-v2 api-reference/openapi/heymarket-openapi-3.1.2.yaml GET /v2/contact/{contactID} Get a contact by UUID. Use this endpoint when you are working with v2 UUID-based contact identifiers. # List contacts Source: https://developers.heymarket.com/api-reference/contacts/list api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/contacts Paginate contacts with the v1 cursor-based endpoint. Use this endpoint to retrieve contacts page by page. # List contacts (v2) Source: https://developers.heymarket.com/api-reference/contacts/list-v2 api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v2/contacts/list List contacts with v2 pagination. Use this endpoint for v2 contact list pagination. # Contacts API overview Source: https://developers.heymarket.com/api-reference/contacts/overview Manage contacts in Heymarket: create, retrieve, update, delete, search, and control opt-out and block status across v1 and v2 endpoints. The Contacts API lets you programmatically manage the people you message in Heymarket. You can create and update contact records, attach custom field values, search by phone or email, and control messaging status such as opt-outs and blocks. The API exposes both a legacy v1 surface and a newer v2 surface that introduces UUID-based identifiers and contact channels. ## The Contact object Every contact is represented by the following fields. | Field | Type | Description | | - | - | - | | `id` | integer | Unique integer identifier for the contact. | | `display_name` | string | Full display name shown in the inbox. | | `first` | string | First name. | | `last` | string | Last name. | | `email` | string | Email address. | | `phone` | string | Primary phone number in E.164 format without the leading `+`. | | `custom` | object | Custom field values keyed by field ID (string). | | `team_id` | integer | ID of the team that owns this contact. | | `creator_id` | integer | ID of the user who created the contact. | | `shared` | boolean | Whether the contact is shared across teams. | | `created` | string | ISO 8601 timestamp of when the contact was created. | | `updated` | string | ISO 8601 timestamp of the last update. | | `rev` | integer | Revision number, incremented on every write. | | `op` | string | Last operation performed on the contact. | | `assigned_user_id` | integer | ID of the user currently assigned to this contact. | | `tags` | array | List of tag objects attached to the contact. | | `is_opted_out` | boolean | Whether the contact has opted out of messages. | > [!NOTE] > > Phone numbers must be in E.164 format **without** the leading `+` sign. For example, use `14155553434` instead of `+14155553434`. ## Endpoints | Method | Endpoint | Description | | - | - | - | | `POST` | `/v1/contact` | Create a contact | | `GET` | `/v1/contact/{id}` | Get a contact by integer ID | | `PUT` | `/v1/contact/{id}` | Update a contact | | `DELETE` | `/v1/contact/{id}` | Delete a contact | | `POST` | `/v1/contacts` | List and paginate contacts | | `POST` | `/v1/contacts/count` | Get the total contact count | | `POST` | `/v1/contact/get` | Get a contact by phone number or ID | | `POST` | `/v1/contact/status` | Get contact messaging status | | `POST` | `/v1/contact/set_status` | Set contact messaging status | | `POST` | `/v1/batch/contacts` | Batch create contacts | | `POST` | `/v1/contact-fields` | List custom contact fields | | `POST` | `/v1/contacts/fields` | Create custom contact fields | | `POST` | `/v2/contact` | Create a contact (v2) | | `GET` | `/v2/contact/{contactID}` | Get a contact by UUID (v2) | | `PUT` | `/v2/contact/{contactID}` | Update a contact (v2) | | `DELETE` | `/v2/contact/{contactID}` | Delete a contact (v2) | | `POST` | `/v2/contacts` | Bulk create or update contacts (v2) | | `DELETE` | `/v2/contacts` | Bulk delete contacts (v2) | | `POST` | `/v2/contacts/list` | List contacts with pagination (v2) | | `POST` | `/v2/contacts/search` | Search contacts by channel (v2) | ## Authentication All requests require a Bearer token in the `Authorization` header. For new integrations, generate a signed JWT from an API Secret and use it as the Bearer token. ```bash theme={null} Authorization: Bearer YOUR_SIGNED_JWT ``` # Search contacts Source: https://developers.heymarket.com/api-reference/contacts/search api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v2/contacts/search Search contacts by channel value, such as phone number or email address. Use this endpoint when you know a channel value and need the matching contact. # Set contact status Source: https://developers.heymarket.com/api-reference/contacts/set-status api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/contact/set_status Update a contact's messaging status. Use this endpoint to block, unblock, subscribe, or unsubscribe a contact. # Get contact status Source: https://developers.heymarket.com/api-reference/contacts/status api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/contact/status Get a contact's messaging status. Use this endpoint to check whether a contact is blocked or opted out before sending. # Update contact Source: https://developers.heymarket.com/api-reference/contacts/update api-reference/openapi/heymarket-openapi-3.1.2.yaml PUT /v1/contact/{id} Update a v1 contact by integer ID. Use this endpoint to update a v1 contact. The `overwrite` query parameter controls whether custom fields are merged or replaced. # Update contact (v2) Source: https://developers.heymarket.com/api-reference/contacts/update-v2 api-reference/openapi/heymarket-openapi-3.1.2.yaml PUT /v2/contact/{contactID} Update a v2 contact by UUID. Use this endpoint when you are working with v2 UUID-based contact identifiers. # Close conversation Source: https://developers.heymarket.com/api-reference/conversations/close api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/conversations/close Mark a conversation as closed. Use this endpoint to close a resolved conversation. # Count conversations Source: https://developers.heymarket.com/api-reference/conversations/count api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/conversations/count Get the count of conversations matching the provided filters. Use this endpoint before exports or sync jobs that need a total conversation count. # Get conversation Source: https://developers.heymarket.com/api-reference/conversations/get api-reference/openapi/heymarket-openapi-3.1.2.yaml GET /v1/conversations/{id} Get a conversation by ID. Use this endpoint to retrieve conversation metadata before sending a reply or changing conversation state. # List conversations Source: https://developers.heymarket.com/api-reference/conversations/list api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/conversations Paginate conversations across one or more inboxes. Use this endpoint to list conversations by inbox, closed state, unread state, and pagination cursor. # Mark conversation as read Source: https://developers.heymarket.com/api-reference/conversations/mark-read api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/conversations/read Mark a conversation as read for a user. Use this endpoint after your integration has processed a conversation. # Mark conversation as unread Source: https://developers.heymarket.com/api-reference/conversations/mark-unread api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/conversations/unread Mark a conversation as unread for a user. Use this endpoint when a conversation still needs attention. # Open conversation Source: https://developers.heymarket.com/api-reference/conversations/open-close api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/conversations/open Mark a conversation as open. Use this endpoint to move a conversation back into the active queue. # Reassign conversation Source: https://developers.heymarket.com/api-reference/conversations/reassign api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/conversations/reassign Reassign a conversation to another user. Use this endpoint to move ownership of a conversation to another team member in the same inbox. # Transfer conversation Source: https://developers.heymarket.com/api-reference/conversations/transfer api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/conversations/transfer Transfer a conversation to another inbox. Use this endpoint to move a conversation to another inbox when routing changes. > [!NOTE] > > The OpenAPI description covers transfer constraints and response details. # Get inboxes Source: https://developers.heymarket.com/api-reference/inboxes api-reference/openapi/heymarket-openapi-3.1.2.yaml GET /v1/inboxes Retrieve inboxes available to your team. Use this endpoint to find the `inbox_id` values required by message, conversation, and schedule requests. > [!NOTE] > > For message sending, use a member ID from `GET /v1/inboxes` or `GET /v1/team` as `creator_id`. # Bulk create links Source: https://developers.heymarket.com/api-reference/links/bulk-create api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/batch/links Create up to five tracked short links in one request. # Create link Source: https://developers.heymarket.com/api-reference/links/create api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/link Create a tracked short link for a destination URL. # Delete link Source: https://developers.heymarket.com/api-reference/links/delete api-reference/openapi/heymarket-openapi-3.1.2.yaml DELETE /v1/link/{id} Delete a tracked link by its ID. # Get link Source: https://developers.heymarket.com/api-reference/links/get api-reference/openapi/heymarket-openapi-3.1.2.yaml GET /v1/link/{id} Retrieve a tracked link by its ID. # Get link by short code Source: https://developers.heymarket.com/api-reference/links/get-by-short-code api-reference/openapi/heymarket-openapi-3.1.2.yaml GET /v1/link/code/{code} Retrieve a tracked link by its short URL code. # List links Source: https://developers.heymarket.com/api-reference/links/list api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/links Paginate the tracked links owned by your team. # Links API overview and object Source: https://developers.heymarket.com/api-reference/links/overview Create and manage tracked short links and understand the Link object returned by the Heymarket API. The Links API lets you create Heymarket short URLs, attribute them to messaging activity, update their destinations, and inspect their latest click activity. > [!NOTE] > > Link tracking must be enabled for your team. Requests from teams without the feature return `403` with the message `link_tracking_not_available`. Every link belongs to the team identified by your API credentials. You cannot retrieve, update, or delete another team's links. Cross-team lookups return `404`. ## The Link object | Field | Type | Description | | - | - | - | | `id` | integer | Unique identifier for the link. | | `short_code` | string | Code segment of the short URL. | | `short_url` | string | Full short URL to include in messages. | | `original_url` | string | Destination URL the short link redirects to. | | `team_id` | integer | Identifier of the team that owns the link. | | `target` | string | Target the link is attributed to, when present. | | `conversation_id` | integer | Conversation the link is attributed to, when present. | | `broadcast_id` | integer | Broadcast the link is attributed to, when present. | | `campaign_step_id` | integer | Campaign step the link is attributed to, when present. | | `template_id` | integer | Template the link is attributed to, when present. | | `last_visited_at` | string | ISO 8601 timestamp of the most recent click. Omitted when the link has not been clicked. | | `created_at` | string | ISO 8601 timestamp when the link was created. | ## Endpoints | Method | Endpoint | Description | | - | - | - | | `POST` | [`/v1/link`](/api-reference/links/create) | Create a tracked short link. | | `POST` | [`/v1/batch/links`](/api-reference/links/bulk-create) | Create up to five tracked short links. | | `GET` | [`/v1/link/{id}`](/api-reference/links/get) | Get a link by ID. | | `GET` | [`/v1/link/code/{code}`](/api-reference/links/get-by-short-code) | Get a link by short code. | | `PUT` | [`/v1/link/{id}`](/api-reference/links/update) | Update a link's destination URL. | | `DELETE` | [`/v1/link/{id}`](/api-reference/links/delete) | Delete a link. | | `POST` | [`/v1/links`](/api-reference/links/list) | Paginate your team's links. | # Update link URL Source: https://developers.heymarket.com/api-reference/links/update api-reference/openapi/heymarket-openapi-3.1.2.yaml PUT /v1/link/{id} Change a tracked link's destination URL without changing its short code. # Count lists (v1) Source: https://developers.heymarket.com/api-reference/lists/count api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/lists/count Get the count of lists that match the legacy v1 pagination filters. > [!WARNING] > > This endpoint is deprecated and will be removed in a future release. Use [Count lists](/api-reference/lists/count-v2) instead. Use this endpoint to count the lists that would be returned by `POST /v1/lists` with the same request body. ## Filter by list ID Use `filters.id` to count one or more specific lists. ```json theme={null} { "filters": { "id": [21152] } } ``` The legacy `filter` string field is ignored by this endpoint. # Count lists Source: https://developers.heymarket.com/api-reference/lists/count-v2 api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v2/lists/count Count the lists that match the v2 pagination filters. Use this endpoint to count the lists that `POST /v2/lists/paginate` would return for the same request body. It replaces the deprecated `POST /v1/lists/count`. The count ignores `limit` and `page`, so it reports every matching list rather than one page of them. ## Filter by list ID Use `filters.id` to count one or more specific lists. ```json theme={null} { "filters": { "id": [21152, 21153] } } ``` # Create list Source: https://developers.heymarket.com/api-reference/lists/create api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v2/lists Create a contact list with the v2 list endpoint. Use this endpoint to create a list for broadcast messaging. # Delete list Source: https://developers.heymarket.com/api-reference/lists/delete api-reference/openapi/heymarket-openapi-3.1.2.yaml DELETE /v2/lists/{id} Delete a v2 list. > [!WARNING] > > Deleting a list removes the list record. Confirm the list ID before sending this request. # Paginate lists (v1) Source: https://developers.heymarket.com/api-reference/lists/list api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/lists Paginate lists with the legacy v1 cursor-based endpoint. > [!WARNING] > > This endpoint is deprecated and will be removed in a future release. Use [Paginate lists](/api-reference/lists/paginate-v2) instead. `POST /v2/lists/paginate` takes the same request body and returns the same response shape. Use this endpoint to retrieve lists page by page. ## Filter by list ID Use `filters`, not `filter`, to filter list pagination results. The only supported list pagination filter is `id`. ```json theme={null} { "filters": { "id": [21152] } } ``` `filters.id` accepts an array of list IDs. A single ID matches that list. Multiple IDs return lists whose `id` is in the array. The legacy `filter` string field is ignored by this endpoint. # Lists API overview Source: https://developers.heymarket.com/api-reference/lists/overview Organize contacts into named lists for broadcast messaging. Each contact receives messages individually and can reply privately to your team. Lists organize multiple contacts for broadcast messaging. Each contact on a list receives the message individually and can reply privately. You can create and manage lists programmatically to keep your contact groups in sync with your own data sources. ## The List object **id** `integer` Unique identifier for the list. **name** `string` Display name of the list. **local\_id** `string` Client-provided unique identifier. Use this to map Heymarket lists to records in your own system. **targets** `object` Object of ListTarget objects, keyed by phone number. Each value is an object with `f` (first name) and `l` (last name). **team\_id** `integer` Identifier of the team that owns this list. **creator\_id** `integer` Identifier of the user who created the list. **shared** `boolean` Whether the list is shared with all members of the team. **created** `string` ISO 8601 timestamp when the list was created. **updated** `string` ISO 8601 timestamp when the list was last updated. **rev** `integer` Revision number. Increments on each update. **archived** `boolean` Whether the list has been archived. > [!NOTE] > > Use the v2 endpoints for new integrations. The v1 list pagination, count, and search endpoints are deprecated and will be removed in a future release. ## Endpoints | Method | Endpoint | Description | | - | - | - | | `POST` | `/v2/lists/paginate` | Paginate lists | | `POST` | `/v2/lists/count` | Count lists that match the same pagination filters | | `POST` | `/v2/lists/search` | Search lists by name | | `POST` | `/v2/lists` | Create a list | | `PUT` | `/v2/lists/{id}` | Update a list | | `DELETE` | `/v2/lists/{id}` | Delete a list | ### Deprecated endpoints | Method | Endpoint | Replacement | | - | - | - | | `POST` | `/v1/lists` | `POST /v2/lists/paginate` | | `POST` | `/v1/lists/count` | `POST /v2/lists/count` | | `POST` | `/v1/search/lists` | `POST /v2/lists/search` | > [!WARNING] > > There is no `GET /v2/lists` and no singular `/v2/list` path. Use the plural `/v2/lists` paths, and read lists through the `paginate`, `count`, and `search` sub-paths. # Paginate lists Source: https://developers.heymarket.com/api-reference/lists/paginate-v2 api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v2/lists/paginate Paginate lists with the v2 cursor-based endpoint. Use this endpoint to retrieve lists page by page. It replaces the deprecated `POST /v1/lists` and takes the same request body and response shape. Send an empty JSON body to return the 30 most recently updated live lists. ## Filter by list ID Use `filters`, not `filter`, to filter list pagination results. The only supported filter is `id`, and it must be an array. ```json theme={null} { "limit": 50, "filters": { "id": [21152, 21153] } } ``` A single ID matches that list. Multiple IDs return lists whose `id` is in the array. A request with more than 100 IDs is rejected with `too_many_ids`. Any other filter key, or a non-array `id`, is rejected with `invalid_filter`. ## Page through results Set `date` to the `updated` value of the last list you received to fetch the next page. When `ascending` is `true`, `date` returns lists updated after that timestamp instead. # Search lists (v1) Source: https://developers.heymarket.com/api-reference/lists/search api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/search/lists Search lists by name with the deprecated v1 endpoint. > [!WARNING] > > This endpoint is deprecated and will be removed in a future release. Use [Search lists](/api-reference/lists/search-v2) instead. Use this endpoint to find lists by name. `q` is required. Pass `user_id` to scope the search to the lists visible to one team member's user groups. # Search lists Source: https://developers.heymarket.com/api-reference/lists/search-v2 api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v2/lists/search Search the lists in your team by name. Use this endpoint to find lists by name. It replaces the deprecated `POST /v1/search/lists`. `q` is required. An empty `q` is rejected with `invalid_query`. ## Search as a team member By default the search runs as the team owner. Pass `user_id` to search as another team member, which limits results to the lists visible to that member's user groups. ```json theme={null} { "q": "spring", "user_id": 4212 } ``` A `user_id` outside your team is rejected with `403 not_team_member`. # Update list Source: https://developers.heymarket.com/api-reference/lists/update api-reference/openapi/heymarket-openapi-3.1.2.yaml PUT /v2/lists/{id} Update a v2 list by adding, removing, or replacing recipients. Use this endpoint to rename a list or change its membership. # Count messages Source: https://developers.heymarket.com/api-reference/messages/count api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/messages/all/count Get the count of team messages that match the pagination filters. Use this endpoint when you need a total count before running a sync, export, or reporting job. # Get message history from a conversation Source: https://developers.heymarket.com/api-reference/messages/get-history api-reference/openapi/heymarket-openapi-3.1.2.yaml GET /v1/messages Get the 50 most recent messages from a conversation by phone number and inbox. Use this endpoint when you need recent message history for a single phone-number conversation in a specific inbox. > [!NOTE] > > For team-wide exports or analytics jobs, use the paginated all-messages endpoint instead. # List all messages Source: https://developers.heymarket.com/api-reference/messages/list-all api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/messages/all Paginate messages across your team. Use this endpoint for team-wide message exports, sync jobs, and analytics pipelines that need to walk through message history. > [!NOTE] > > Sending an empty JSON body returns the most recent messages. To continue pagination, pass the last returned message timestamp as `created_at` in the next request. # Send SMS and MMS messages Source: https://developers.heymarket.com/api-reference/messages/send api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/message/send Send individual SMS or MMS, broadcast to a contact list, or post a private internal comment. Use this endpoint to send messages from a Heymarket inbox. It supports individual messages, list broadcasts, MMS media, templates, surveys, and private internal comments. > [!NOTE] > > `creator_id` is the member ID returned by `GET /v1/inboxes` or `GET /v1/team`, not necessarily your general user ID. > [!NOTE] > > `local_id` is required when sending a broadcast with `list_id`. Use a unique value per broadcast send. # Send email messages Source: https://developers.heymarket.com/api-reference/messages/send-email api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/email/send Send email from a Heymarket inbox or reply in an existing email conversation. Use this endpoint to send email through a connected Heymarket inbox. Start a new conversation with `targets`, or reply in an existing thread with `convo_id`. > [!NOTE] > > The `text` field supports HTML. Include `raw_text` when you want a plain-text fallback for email clients that do not render HTML. # Create scheduled message Source: https://developers.heymarket.com/api-reference/schedule api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/schedule Schedule a message for future delivery. Use this endpoint to queue a message for future delivery. > [!NOTE] > > Check the OpenAPI operation description for timing constraints such as lead time and interval alignment. # Delete scheduled message Source: https://developers.heymarket.com/api-reference/schedule-delete api-reference/openapi/heymarket-openapi-3.1.2.yaml DELETE /v1/schedule/{id} Delete a scheduled message by ID. Use this endpoint to cancel a scheduled message before it sends. # Get scheduled message Source: https://developers.heymarket.com/api-reference/schedule-get api-reference/openapi/heymarket-openapi-3.1.2.yaml GET /v1/schedule/{id} Get a scheduled message by ID. Use this endpoint to inspect a scheduled message before delivery. # Update scheduled message Source: https://developers.heymarket.com/api-reference/schedule-update api-reference/openapi/heymarket-openapi-3.1.2.yaml PUT /v1/schedule/{id} Update a scheduled message by ID. Use this endpoint to change a scheduled message before it sends. # List surveys Source: https://developers.heymarket.com/api-reference/surveys api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/surveys Paginate surveys. Use this endpoint to retrieve survey IDs for message sends. # Get survey Source: https://developers.heymarket.com/api-reference/surveys-get api-reference/openapi/heymarket-openapi-3.1.2.yaml GET /v1/survey/{id} Get a survey by ID. Use this endpoint to retrieve one survey. # List tags Source: https://developers.heymarket.com/api-reference/tags api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/tags Paginate contact tags. Use this endpoint to retrieve contact tags for the team. # Create tag Source: https://developers.heymarket.com/api-reference/tags-create api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/tag Create a contact tag. Use this endpoint to create a tag that can be assigned to contacts. # Delete tag Source: https://developers.heymarket.com/api-reference/tags-delete api-reference/openapi/heymarket-openapi-3.1.2.yaml DELETE /v1/tag/{id} Delete a contact tag by ID. > [!WARNING] > > Deleting a tag removes it from the team. Confirm the tag ID before sending this request. # Get tag Source: https://developers.heymarket.com/api-reference/tags-get api-reference/openapi/heymarket-openapi-3.1.2.yaml GET /v1/tag/{id} Get a contact tag by ID. Use this endpoint to retrieve one tag. # Get team Source: https://developers.heymarket.com/api-reference/team api-reference/openapi/heymarket-openapi-3.1.2.yaml GET /v1/team Retrieve the current team and its members. Use this endpoint to inspect team metadata and member IDs. # Create template Source: https://developers.heymarket.com/api-reference/templates/create api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/template Create a reusable message template. Use this endpoint to create a template with text and optional media content. # Delete template Source: https://developers.heymarket.com/api-reference/templates/delete api-reference/openapi/heymarket-openapi-3.1.2.yaml DELETE /v1/template/{id} Delete a template by ID. > [!WARNING] > > Deleting a template is permanent. Confirm the template ID before sending this request. # Get template Source: https://developers.heymarket.com/api-reference/templates/get api-reference/openapi/heymarket-openapi-3.1.2.yaml GET /v1/template/{id} Get a template by ID. Use this endpoint to retrieve a single template. # List templates Source: https://developers.heymarket.com/api-reference/templates/list api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/templates Paginate message templates. Use this endpoint to retrieve templates page by page. # Template operations Source: https://developers.heymarket.com/api-reference/templates/manage Use the split template operation pages for generated request and response details. Template endpoints are split into one page per operation so the OpenAPI specification owns request fields, response fields, and examples. > **[List templates](/api-reference/templates/list)** > > Paginate templates in your team. > **[Create template](/api-reference/templates/create)** > > Create a reusable message template. > **[Get template](/api-reference/templates/get)** > > Retrieve one template by ID. > **[Update template](/api-reference/templates/update)** > > Update template content or metadata. > **[Delete template](/api-reference/templates/delete)** > > Delete a template by ID. # Templates API overview and objects Source: https://developers.heymarket.com/api-reference/templates/overview Understand the Template object, merge token syntax, and the full set of endpoints for creating and managing reusable message templates. Templates are pre-defined messages with text and optional media. They support merge tokens that auto-populate contact fields when sending, so you can personalize messages at scale without writing custom copy for every recipient. ## The Template object **id** `integer` Unique identifier for the template. **name** `string` Display name of the template. **local\_id** `string` Client-provided unique identifier. Use this to map templates to records in your own system. **content** `object` A GalleryContent object containing the message text and optional images. See the [Content object](#the-gallerycontent-object) section below. **team\_id** `integer` Identifier of the team that owns this template. **creator\_id** `integer` Identifier of the user who created the template. **shared** `boolean` Whether the template is shared with all members of the team. **created** `string` ISO 8601 timestamp when the template was created. **updated** `string` ISO 8601 timestamp when the template was last updated. **rev** `integer` Revision number. Increments on each update. **archived** `boolean` Whether the template has been archived. ## The GalleryContent object **text** `string` The message body. May contain merge tokens such as `{{first_name}}`. **gallery** `object[]` Array of GalleryEntry objects representing images attached to the message. **GalleryEntry properties** **url** `string` URL of the image as stored by Heymarket. **original\_url** `string` Original source URL of the image before upload. **annotation\_text** `string` Optional caption or annotation text displayed with the image. **annotation\_position** `string` Position of the annotation relative to the image. ## Merge tokens Merge tokens let you personalize template text using contact field values. When a message is sent, Heymarket replaces each token with the corresponding value from the recipient's contact record. | Token | Replaced with | | - | - | | `{{first_name}}` | Contact's first name | | `{{last_name}}` | Contact's last name | | `{{custom_field_name}}` | Value of a custom contact field | ## Endpoints | Method | Endpoint | Description | | - | - | - | | `POST` | `/v1/template` | Create a template | | `GET` | `/v1/template/{id}` | Get a template by ID | | `PUT` | `/v1/template/{id}` | Update a template | | `DELETE` | `/v1/template/{id}` | Delete a template | | `POST` | `/v1/templates` | List templates | # Update template Source: https://developers.heymarket.com/api-reference/templates/update api-reference/openapi/heymarket-openapi-3.1.2.yaml PUT /v1/template/{id} Update a template by ID. Use this endpoint to update template content or metadata. # Get users Source: https://developers.heymarket.com/api-reference/users api-reference/openapi/heymarket-openapi-3.1.2.yaml GET /v1/users/get Look up team users by phone or email. Use this endpoint to look up team member records. > [!WARNING] > > This legacy endpoint uses a JSON request body with `GET`. Some API clients do not support GET request bodies. # Update users Source: https://developers.heymarket.com/api-reference/users-update api-reference/openapi/heymarket-openapi-3.1.2.yaml POST /v1/users/update Update team user fields and roles. Use this endpoint to update one or more team users. # Receive webhook event Source: https://developers.heymarket.com/api-reference/webhooks api-reference/openapi/heymarket-openapi-3.1.2.yaml webhook heymarketEvent Schema for the Heymarket webhook event callback. Use this page to review the generated payload schema for webhook deliveries. # Authenticate with the Heymarket API Source: https://developers.heymarket.com/authentication Create API Secrets, generate short-lived JWTs, and authenticate Heymarket API requests. Every request to the Heymarket API must be authenticated. Heymarket supports two authentication methods: * **API Secret JWT authentication.** Recommended for new integrations. Generate a short-lived JWT from an API Secret ID and API Secret Key, then send the JWT as a Bearer token. * **Team API key authentication.** Legacy authentication for existing integrations. API key authentication is planned for deprecation. ## Create an API Secret Log in to [Heymarket](https://app.heymarket.com) and go to **Settings > Integrations > API**. Generate an API Secret and copy both values: * **Secret ID**: Used as the JWT `iss` value. * **Secret Key**: Used with the Secret ID to sign the JWT. > [!WARNING] > > The Secret Key is shown only when it is generated. Copy and store it securely before closing the dialog. > [!WARNING] > > Generate signed JWTs only from trusted server-side code. Do not expose your Secret ID, Secret Key, signed JWTs, or legacy API key in browser JavaScript, mobile apps, public repositories, logs, or screenshots. ## Generate a signed JWT Use your API Secret ID and Secret Key to sign a short-lived JSON Web Token (JWT). Send the signed JWT as a Bearer token in the `Authorization` header. **1. Build the JWT header** Use the `HS256` algorithm. ```json theme={null} { "alg": "HS256", "typ": "JWT" } ``` **2. Build the JWT payload** Set `iss` to your API Secret ID and `iat` to the current Unix timestamp in seconds. ```json theme={null} { "iss": "YOUR_API_SECRET_ID", "iat": 1713225600 } ``` **3. Construct the signing secret** Concatenate your API Secret ID and API Secret Key with `||` between them. ```text theme={null} YOUR_API_SECRET_ID||YOUR_API_SECRET_KEY ``` **4. Sign and send the JWT** Sign the JWT with HMAC-SHA256 using the combined secret. Pass the resulting JWT as a Bearer token. ```bash theme={null} curl https://api.heymarket.com/v1/inboxes \ -H "Authorization: Bearer YOUR_SIGNED_JWT" ``` Tokens expire 5 minutes after the `iat` timestamp. Generate a new JWT per request, or cache it briefly for less than 5 minutes. ## Python example This example uses only the Python standard library. ```python theme={null} import base64 import hashlib import hmac import json import time SECRET_ID = "YOUR_API_SECRET_ID" SECRET_KEY = "YOUR_API_SECRET_KEY" def b64(data): return base64.urlsafe_b64encode(data).rstrip(b"=") header = b64(json.dumps({"alg": "HS256", "typ": "JWT"}, separators=(",", ":")).encode()) payload = b64(json.dumps({"iss": SECRET_ID, "iat": int(time.time())}, separators=(",", ":")).encode()) signing_input = header + b"." + payload signing_secret = f"{SECRET_ID}||{SECRET_KEY}".encode() signature = b64(hmac.new(signing_secret, signing_input, hashlib.sha256).digest()) print((signing_input + b"." + signature).decode()) ``` ## Check your credentials Use `GET /v1/inboxes` as a low-risk check that your signed JWT works: ```bash theme={null} curl https://api.heymarket.com/v1/inboxes \ -H "Authorization: Bearer YOUR_SIGNED_JWT" ``` If the request succeeds, the response includes inbox IDs you can use with message-sending endpoints. ## Rotate an API Secret The API settings page supports up to two API Secrets. Use the second slot to rotate without interrupting active traffic. **1. Generate a second API Secret** Create a new API Secret in **Settings > Integrations > API** and store the new Secret ID and Secret Key securely. **2. Deploy the new secret** Update your server-side integration to sign JWTs with the new Secret ID and Secret Key. **3. Verify the new secret** Call `GET /v1/inboxes` with a JWT signed by the new secret. **4. Revoke the old secret** After the new secret is deployed and verified, revoke the old API Secret. ## Legacy API key authentication Existing integrations may still authenticate with a team API key while migration is planned. ```bash theme={null} curl https://api.heymarket.com/v1/inboxes \ -H "Authorization: Bearer YOUR_API_KEY" ``` > [!WARNING] > > API key authentication is planned for deprecation. New integrations should use API Secret JWT authentication. ## Request IDs Every API response includes an `X-Request-Id` header. Record this value when you encounter an error. Heymarket support uses it to look up the specific request. ```text theme={null} X-Request-Id: a1b2c3d4-e5f6-7890-abcd-ef1234567890 ``` # Contacts Source: https://developers.heymarket.com/concepts/contacts Understand how Heymarket contacts store customer identity, channels, custom fields, tags, ownership, and messaging status. Contacts are the people your team messages in Heymarket. A contact can store names, phone numbers, email addresses, custom fields, tags, owner assignment, and messaging status. When you send or receive a message, Heymarket uses the channel identifier, such as a phone number or email address, to associate that activity with a contact and conversation. ## How contacts fit into the API Contacts connect several API workflows: * **Messages** use a contact's phone number, email address, or existing conversation. * **Conversations** are usually tied to one contact and one inbox. * **Lists** group contacts or phone-number targets for broadcasts. * **Templates** can use contact values as merge tokens. * **Tags and custom fields** help segment, search, and personalize contacts. ## Contact identifiers The API exposes both older integer IDs and newer v2 contact identifiers. Use the identifier expected by the endpoint you are calling. | Surface | Identifier | Notes | | - | - | - | | v1 contact endpoints | Integer contact ID | Used by endpoints such as `GET /v1/contact/{id}`. | | v2 contact endpoints | Contact UUID as `contactID` | Used by endpoints such as `GET /v2/contact/{contactID}`. Do not pass the v1 integer `id` to these paths. | | SMS and MMS message endpoints | Phone number, conversation ID, list ID, or group targets | The required target depends on the send flow. | | Email message endpoint | Email `targets` or existing `convo_id` | Email uses `POST /v1/email/send`, not `POST /v1/message/send`. | ## Custom fields Custom fields let your team store account numbers, plan names, lifecycle stages, or other data that belongs on the contact. To retrieve the custom field IDs defined for your team, call `POST /v1/contact-fields`. When creating or updating a contact, pass custom values as an object keyed by field ID: ```json theme={null} { "custom": { "12": "Enterprise", "47": "Account-9823" } } ``` ## Contact channels v2 contact endpoints support contact channels. A channel is a specific way to reach a contact, such as a phone number or email address. Use contact channels when a customer can be reached through multiple identifiers and you need to manage those identifiers as part of one contact record. ## Tags and status Tags categorize contacts for filtering and routing. Each contact can have up to five tags. Contact status describes whether a contact can be messaged. For example, contacts can be active, blocked, subscribed, or unsubscribed. Check status before sending if your workflow depends on opt-in or block state. > [!NOTE] > > Phone numbers must use E.164 digits without the leading plus sign. For example, use `14155553434` instead of `+1 (415) 555-3434`. ## Related pages > **[Manage contacts](/guides/manage-contacts)** > > Create, update, delete, search, and list contacts. > **[Contacts reference](/api-reference/contacts/overview)** > > Review contact endpoints and request details. # Conversations Source: https://developers.heymarket.com/concepts/conversations Understand how Heymarket conversations group messages by contact, inbox, assignment, read state, and open or closed status. A conversation is a messaging thread between your team and a customer. Conversations group messages, preserve context, and let your team manage ownership and status. Most conversations are tied to an inbox and a contact. When a message is sent or received, Heymarket associates it with the relevant conversation. ## Conversation relationships Conversations sit between contacts, messages, and inboxes: * **Inbox** - the sending or receiving channel your team uses. * **Contact** - the customer or person in the thread. * **Messages** - the individual inbound, outbound, email, MMS, private, or scheduled messages in the thread. * **Members** - team members who can access or act on the conversation. ## Lifecycle Conversations can be open or closed. Open conversations usually need attention. Closed conversations are resolved or no longer active. You can also mark conversations as read or unread, reassign them to another team member, and transfer them to another inbox. ## Assignment and transfer Use assignment when responsibility changes between team members inside the same inbox. Use transfer when a conversation should move to a different inbox, such as moving a sales conversation to support. ## Listing conversations Conversation lists are paginated with `POST /v1/conversations`. To retrieve older conversations, use the `updated` timestamp from the last conversation in the current response as the next `date` cursor. ```json theme={null} { "id": 0, "filters": { "inboxes": [42] }, "date": "2026-04-12T15:30:00Z" } ``` This lets you walk backward through conversation history without loading every conversation at once. ## Finding the conversation to reply to Inbound message webhook payloads include message and conversation data in `event_data`. Store the conversation ID from webhook events or message-history responses when your integration needs to reply in the same thread with `chat_id`. > [!TIP] > > Use filtering by inbox, closed status, and unread status to keep sync jobs small and focused. ## Related pages > **[Conversations reference](/api-reference/conversations/list)** > > List, retrieve, open, close, reassign, and transfer conversations. > **[Messages](/concepts/messages)** > > Learn how individual messages relate to conversations. > **[Webhook event reference](/api-reference/webhooks)** > > Review the event payload shape used for inbound replies. # Inboxes Source: https://developers.heymarket.com/concepts/inboxes Understand how Heymarket inboxes map to team channels, phone numbers, member access, and sender IDs used by message requests. An inbox is a team channel that sends and receives messages. Inboxes usually map to one or more phone numbers or email channels and have their own settings, members, and conversations. When you send a message through the API, you provide the inbox ID that should send the message. ## Inbox IDs Call `GET /v1/inboxes` before sending messages. The response includes each inbox available to your API credentials. Use the inbox `id` as `inbox_id` in message, conversation, and scheduling requests. ```bash theme={null} curl https://api.heymarket.com/v1/inboxes \ -H "Authorization: Bearer YOUR_SIGNED_JWT" ``` ## Sender IDs Some write endpoints require `creator_id`. For message sending, `creator_id` is the member ID that should appear as the sender. Use a member ID returned by `GET /v1/inboxes` or `GET /v1/team` that has access to the target inbox. > [!WARNING] > > Do not assume your general user ID is valid for every inbox. Use a member ID that belongs to the inbox you are sending from. ## Multiple inboxes If your team has multiple inboxes, always pass `inbox_id` explicitly. This keeps messages, conversations, and scheduled sends associated with the intended channel. ## Related pages > **[Authentication](/authentication)** > > Check credentials with `GET /v1/inboxes`. > **[Inboxes reference](/api-reference/inboxes)** > > Review inbox response fields. > **[Team reference](/api-reference/team)** > > Retrieve team metadata and member IDs. # Lists, templates, and scheduled sends Source: https://developers.heymarket.com/concepts/lists-templates-scheduling Understand the reusable Heymarket objects that support broadcasts, personalized messages, scheduled delivery, tags, and surveys. Lists, templates, scheduled sends, tags, and surveys help you build repeatable messaging workflows without hardcoding every recipient or message body. ## Lists A list groups recipients for broadcast messaging. When you send to a `list_id`, each recipient receives an individual message and can reply privately. Lists can contain lightweight targets with phone numbers and optional name values. Those values can be used for personalization when no full Heymarket contact exists for the phone number. ## Templates A template is saved message content that can include text, media, and merge tokens. Use templates when teams need consistent wording or when your integration sends the same type of message repeatedly. Templates can use contact values as merge tokens, such as first name, last name, or custom field values. ## Scheduled sends Scheduled sends queue a message for future delivery. Use scheduled sends for reminders, follow-ups, appointment messages, or campaigns that should go out at a specific time. You can retrieve, update, or delete scheduled messages before delivery. When creating a scheduled send, provide `inbox_id`, `execute_at`, `content`, and one target family such as `phone_number`, `conversation_id`, `list_id`, or `targets`. `execute_at` must use RFC 3339 format, be at least 15 minutes in the future, and align to a 15-minute interval. Provide `user_id` when the scheduled message should be sent as a specific inbox member. ## Tags Tags categorize contacts. They can support segmentation, filtering, routing, and reporting workflows. Each tag belongs to your team and has an ID, name, and color. ## Surveys Surveys let you send standardized questions and collect customer responses. Use surveys when your workflow needs structured feedback instead of free-form replies. ## Related pages > **[Broadcast messages](/guides/broadcast-messages)** > > Send a message to a contact list. > **[Lists reference](/api-reference/lists/overview)** > > Review list endpoints. > **[Templates reference](/api-reference/templates/overview)** > > Review template endpoints. > **[Schedule reference](/api-reference/schedule)** > > Review scheduled message endpoints. > **[Tags reference](/api-reference/tags)** > > Retrieve tags for segmentation and routing. > **[Surveys reference](/api-reference/surveys)** > > Retrieve surveys for structured feedback workflows. # Messages Source: https://developers.heymarket.com/concepts/messages Understand Heymarket message types, send targets, media, templates, broadcasts, scheduled sends, and private comments. Messages are the individual units of communication in Heymarket. A message can be an outbound SMS, MMS, email, broadcast, scheduled send, inbound reply, or private internal comment. Every message belongs to a conversation. Outbound messages are sent from an inbox and attributed to a sender member. ## Send targets The message send endpoint supports several target patterns. Choose the target that matches your workflow. | Target | Use when | | - | - | | `phone_number` | Sending an individual SMS or MMS to a phone number. | | `chat_id` | Replying inside an existing conversation. | | `list_id` | Broadcasting to a contact list. | | `targets` | Sending group MMS to multiple phone numbers. | Email uses a separate endpoint: `POST /v1/email/send`. For a new email conversation, provide email `targets` with `to:` or `cc:` prefixes. To reply in an existing email conversation, provide `convo_id`. ## Message content Message content can come from: * `text` for a direct message body. * `template_id` for a saved template. * `media_url` for a public media attachment. Use `private: true` only as a behavior flag for an internal comment visible to your team. When sending to a list, include a unique `local_id` so the send can be tracked consistently. A `local_id` is also useful for retry-sensitive sends and webhook reconciliation when your integration needs its own stable identifier. ## SMS, MMS, and email Use `POST /v1/message/send` for SMS, MMS, broadcasts, and private comments. Use `POST /v1/email/send` for email messages. MMS requires a publicly accessible `media_url`. Carrier limits can affect supported file size and delivery behavior. ## Scheduled messages Scheduled messages are queued for a future send time. Use scheduled sends when a workflow needs delayed delivery or campaign timing. Scheduled messages can be retrieved, updated, or deleted before they are sent. ## Related pages > **[Send a message](/guides/send-message)** > > Follow the first-message workflow. > **[Messages reference](/api-reference/messages/send)** > > Review message send fields and examples. > **[Email send reference](/api-reference/messages/send-email)** > > Review email-specific target fields. # Webhooks Source: https://developers.heymarket.com/concepts/webhooks Understand how Heymarket webhooks send real-time event notifications to your integration. Webhooks send event notifications from Heymarket to your server. Use them when your integration needs to react to inbound messages, outbound message updates, conversation changes, or other team activity without polling the API. ## How webhooks fit into the API The REST API lets your integration fetch and mutate Heymarket data. Webhooks notify your integration when something changes. A typical integration uses both: * Webhooks to receive real-time events. * REST API calls to look up related objects or perform follow-up actions. Use webhooks for real-time workflows such as inbound replies, opt-outs, and conversation assignment changes. Use REST API calls when you need to backfill history, reconcile missed events, or run a periodic sync. ## Receiver requirements Your webhook receiver should: * Accept HTTPS `POST` requests from Heymarket. * Parse JSON request bodies. * Validate the webhook `token` value. * Return a 2xx response quickly after receiving the event. * Move slow work to a queue or background job. * Log event IDs or request metadata for troubleshooting. ## Security Keep webhook URLs private. Do not commit them to public code, expose them in client-side applications, or reuse them across unrelated environments. Validate the webhook token before processing events. If you also include an unguessable token in the URL path, treat it as an extra control rather than the only validation step. ## Related pages > **[Webhook guide](/guides/webhooks)** > > Configure and receive webhook events. > **[Messages](/concepts/messages)** > > Understand the message objects commonly sent in webhook payloads. > **[Webhook event reference](/api-reference/webhooks)** > > Review the generated event payload schema. # Send broadcast messages Source: https://developers.heymarket.com/guides/broadcast-messages Create contact lists and send broadcast messages where each recipient gets an individual thread in Heymarket. Broadcasts send the same message to a list of recipients. Each recipient receives an individual message and replies privately in their own conversation. ## Prerequisites Before you send a broadcast, collect: * A signed JWT generated from an API Secret. * An `inbox_id` for the sending inbox. * A `creator_id` for a sender member in that inbox. * A contact list, or the contact IDs, phone numbers, or email addresses for a new list. Confirm that recipients are eligible to receive the message before you send. Review opt-in status, list membership, and phone-number formatting in your source system or Heymarket. ## Steps **1. Create the list** Create a list with `POST /v2/lists`. Provide a `title`, a unique `local_id`, and at least one recipient source. ```bash theme={null} curl -X POST https://api.heymarket.com/v2/lists \ -H "Authorization: Bearer YOUR_SIGNED_JWT" \ -H "Content-Type: application/json" \ -d '{ "title": "April Reminders", "local_id": "april-reminders-2026", "contacts": [98765, 98766], "phones": ["15105553344", "15105553355"] }' ``` Store the returned list `id`. `contacts` are existing Heymarket contact IDs. `phones` and `emails` add lightweight targets when you do not already have contact IDs. **2. Send to the list** Send the broadcast with `POST /v1/message/send`. When you provide `list_id`, you must also provide a unique message `local_id`. ```bash theme={null} curl -X POST https://api.heymarket.com/v1/message/send \ -H "Authorization: Bearer YOUR_SIGNED_JWT" \ -H "Content-Type: application/json" \ -d '{ "inbox_id": 42, "creator_id": 7, "list_id": 501, "local_id": "broadcast-april-reminders-001", "text": "Hi {{first_name}}, your appointment reminder is ready." }' ``` **3. Track replies** Replies arrive as individual conversations in the sending inbox. Track them in the app, poll conversation or message endpoints, or receive webhook events. Inbound message webhooks use the `message_recieved` event type. ## Update a list Use `PUT /v2/lists/{id}` to change list membership. ```bash theme={null} curl -X PUT https://api.heymarket.com/v2/lists/501 \ -H "Authorization: Bearer YOUR_SIGNED_JWT" \ -H "Content-Type: application/json" \ -d '{ "add_contacts": [98770, 98771], "remove_contacts": [98765], "add_phones": ["15105553366"], "remove_phones": ["15105553355"] }' ``` You can add or remove contacts, phone numbers, and email addresses. You can also replace membership by passing `contacts`, `phones`, or `emails`. ## Broadcast rules * Use a unique `local_id` for each list and each broadcast send. * Use E.164 digits without the leading plus sign for phone numbers. * Use templates when broadcast copy should be managed in Heymarket. * Group MMS with `targets` is separate from list broadcasts, and toll-free sending numbers are not supported for group MMS. > [!NOTE] > > A list broadcast sends separate one-to-one messages. It does not create a shared group thread for every list member. ## Related pages > **[Lists and templates](/concepts/lists-templates-scheduling)** > > Understand lists, templates, tags, surveys, and scheduled sends. > **[Lists reference](/api-reference/lists/overview)** > > Review list create, update, and delete endpoints. > **[Webhook guide](/guides/webhooks)** > > Receive replies to broadcast messages. # Create and manage contacts Source: https://developers.heymarket.com/guides/manage-contacts Create, update, search, list, and delete Heymarket contacts, including custom fields, tags, and channel-based lookup. Contacts are the people your team messages. Use contact endpoints to keep customer identity, phone numbers, email addresses, tags, ownership, opt-out state, and custom fields in sync with your system. ## Choose v1 or v2 contacts Use the contact surface that matches the identifiers you have. | Use case | Endpoint family | | - | - | | Create or update a basic phone-based contact with an integer contact ID | v1 contact endpoints | | Retrieve or update a contact by UUID, or manage phone/email channels on one contact | v2 contact endpoints | | Find a contact when you only know a phone number or email address | `POST /v2/contacts/search` | The v1 endpoints use integer `id` values. The v2 endpoints use the contact UUID as `contactID`. ## Create a contact Create a v1 contact with `POST /v1/contact`. A phone number is required and must use E.164 digits without the leading plus sign. ```bash theme={null} curl -X POST https://api.heymarket.com/v1/contact \ -H "Authorization: Bearer YOUR_SIGNED_JWT" \ -H "Content-Type: application/json" \ -d '{ "phone": "15105553344", "first": "Jordan", "last": "Rivera", "email": "jordan.rivera@example.com", "tags": [ { "tag_id": 55 }, { "tag_id": 56 } ] }' ``` The response includes the contact `id`, revision, and UUID. ```json theme={null} { "id": 98765, "rev": 1, "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" } ``` ## Update a contact Use `PUT /v1/contact/{id}` to update an existing v1 contact. Include `phone` in the request body. ```bash theme={null} curl -X PUT "https://api.heymarket.com/v1/contact/98765?overwrite=false" \ -H "Authorization: Bearer YOUR_SIGNED_JWT" \ -H "Content-Type: application/json" \ -d '{ "phone": "15105553344", "first": "Jordan", "custom": { "123": "Premium", "456": "2026-06-01" } }' ``` The `overwrite` query parameter controls custom-field updates: * `overwrite=false` merges the provided custom fields with existing values. * `overwrite=true` replaces existing custom field values with the object you provide. > [!NOTE] > > Contact names are overwritten during update regardless of the `overwrite` value. ## Work with custom fields Custom field keys are numeric field IDs, not field names. Get the IDs before writing custom values. ```bash theme={null} curl -X POST https://api.heymarket.com/v1/contact-fields \ -H "Authorization: Bearer YOUR_SIGNED_JWT" \ -H "Content-Type: application/json" \ -d '{}' ``` Example response: ```json theme={null} [ { "id": 123, "title": "Plan" }, { "id": 456, "title": "Renewal Date" } ] ``` Then pass those IDs as string keys in the contact `custom` object. ## Search by phone or email Use `POST /v2/contacts/search` when you have a channel value and need the matching contact. ```bash theme={null} curl -X POST https://api.heymarket.com/v2/contacts/search \ -H "Authorization: Bearer YOUR_SIGNED_JWT" \ -H "Content-Type: application/json" \ -d '{ "contact_channels": [ { "channel_type": "phone", "channel_ids": ["15105553344"] } ] }' ``` You can omit `channel_type` when Heymarket can infer it from the value format. ## Add a contact channel Use `POST /v2/contact/{contactID}/channel` to add another phone number, email address, or supported channel to an existing v2 contact. ```bash theme={null} curl -X POST https://api.heymarket.com/v2/contact/a1b2c3d4-e5f6-7890-abcd-ef1234567890/channel \ -H "Authorization: Bearer YOUR_SIGNED_JWT" \ -H "Content-Type: application/json" \ -d '{ "channel_type": "email", "channel_id": "jordan.rivera@example.com" }' ``` ## List and delete contacts Use `POST /v1/contacts` to paginate contacts. Pass the previous page's last timestamp as `date` to retrieve the next page. For example, if the last contact on the current page has `updated: "2026-04-12T15:30:00Z"`, use that timestamp as the next `date` cursor. ```bash theme={null} curl -X POST https://api.heymarket.com/v1/contacts \ -H "Authorization: Bearer YOUR_SIGNED_JWT" \ -H "Content-Type: application/json" \ -d '{ "date": "2026-04-01T00:00:00Z" }' ``` Delete a contact with `DELETE /v1/contact/{id}`. ```bash theme={null} curl -X DELETE https://api.heymarket.com/v1/contact/98765 \ -H "Authorization: Bearer YOUR_SIGNED_JWT" ``` ## Related pages > **[Contacts](/concepts/contacts)** > > Understand contact identifiers, channels, tags, and status. > **[Contacts reference](/api-reference/contacts/overview)** > > Review contact endpoints and field-level details. > **[Search contacts reference](/api-reference/contacts/search)** > > Look up contacts by phone number, email address, or another channel value. > **[Contact channel reference](/api-reference/contacts/channels)** > > Add a channel to an existing v2 contact. # Send SMS and MMS messages Source: https://developers.heymarket.com/guides/send-message Send individual messages, MMS attachments, templates, broadcasts, and private comments with POST /v1/message/send. Use `POST /v1/message/send` when your integration needs to send a message from a Heymarket inbox. You can send to a phone number, reply in an existing conversation, send to a list, attach media, use a template, or add a private internal comment. ## Prerequisites Before you send a message, collect: * A signed JWT generated from an API Secret. * The `inbox_id` for the sending inbox. * The `creator_id` for the sender member. * The target, such as `phone_number`, `chat_id`, `list_id`, or `targets`. > [!WARNING] > > `creator_id` is the member ID returned by `GET /v1/inboxes` or `GET /v1/team`. Do not assume it is the same as your general user ID. ## Steps **1. Get the inbox and sender IDs** Call `GET /v1/inboxes` and choose the inbox that should send the message. Use the inbox `id` as `inbox_id`. ```bash theme={null} curl https://api.heymarket.com/v1/inboxes \ -H "Authorization: Bearer YOUR_SIGNED_JWT" ``` Use a member ID from that inbox, or from `GET /v1/team`, as `creator_id`. **2. Choose the target** Pick one target family for the send. Do not combine target families in one request. | Target family | Fields | Use when | | - | - | - | | Individual SMS/MMS | `phone_number` or `chat_id` | Sending to one phone number or replying inside one conversation. | | List broadcast | `list_id` and `local_id` | Sending separate one-to-one messages to a contact list. | | Group MMS | `targets` | Sending one group MMS conversation to multiple phone numbers. | **3. Send the message** Send the request with `inbox_id`, `creator_id`, a target, and message content from `text`, `template_id`, or `media_url`. Add `private: true` only when creating an internal comment inside an existing conversation. ```bash theme={null} curl -X POST https://api.heymarket.com/v1/message/send \ -H "Authorization: Bearer YOUR_SIGNED_JWT" \ -H "Content-Type: application/json" \ -d '{ "inbox_id": 42, "creator_id": 7, "phone_number": "15105553344", "text": "Hi! Your order has shipped and will arrive by Friday." }' ``` **4. Store the response ID** A successful request returns the created message `id`, the send `date`, and `gallery_url` when applicable. ```json theme={null} { "id": 9980041, "date": "2026-04-16T14:22:00Z", "gallery_url": "https://example.com/media/receipt.png" } ``` Store the message `id` or your own `local_id` if you need to reconcile webhook events, poll message history, or retry-sensitive work. ## Examples ```bash Individual SMS theme={null} curl -X POST https://api.heymarket.com/v1/message/send \ -H "Authorization: Bearer YOUR_SIGNED_JWT" \ -H "Content-Type: application/json" \ -d '{ "inbox_id": 42, "creator_id": 7, "phone_number": "15105553344", "text": "Hi! Your order has shipped and will arrive by Friday." }' ``` ```bash MMS with media theme={null} curl -X POST https://api.heymarket.com/v1/message/send \ -H "Authorization: Bearer YOUR_SIGNED_JWT" \ -H "Content-Type: application/json" \ -d '{ "inbox_id": 42, "creator_id": 7, "phone_number": "15105553344", "text": "Here is your receipt.", "media_url": "https://example.com/receipt.png" }' ``` ```bash Template theme={null} curl -X POST https://api.heymarket.com/v1/message/send \ -H "Authorization: Bearer YOUR_SIGNED_JWT" \ -H "Content-Type: application/json" \ -d '{ "inbox_id": 42, "creator_id": 7, "phone_number": "15105553344", "template_id": 301 }' ``` ```bash Private comment theme={null} curl -X POST https://api.heymarket.com/v1/message/send \ -H "Authorization: Bearer YOUR_SIGNED_JWT" \ -H "Content-Type: application/json" \ -d '{ "inbox_id": 42, "creator_id": 7, "chat_id": 123456, "text": "Customer asked about their renewal date.", "private": true, "author": "Support Bot" }' ``` ## Control the filename for an individual MMS The Heymarket API does not accept a separate filename parameter for MMS media. The headers below belong to the HTTP response from `media_url`, not to your `POST /v1/message/send` request. When you send an individual MMS with `phone_number` or `chat_id`, the MMS provider fetches the file from `media_url` in a separate request. Configure the file host, such as your object storage, CDN, or application download endpoint, to return these response headers: ```http theme={null} Content-Type: application/pdf Content-Disposition: inline; filename="receipt.pdf" ``` Use a stable ASCII filename no longer than 20 characters. Limit the filename to letters, numbers, hyphens, underscores, and periods. ## Request rules * Use E.164 digits without the leading plus sign for phone numbers, such as `15105553344`. * For an individual SMS or MMS, provide `phone_number` or `chat_id`. * For broadcasts, provide `list_id` and a unique `local_id`. * For MMS, `media_url` must be publicly accessible. * For group MMS with `targets`, toll-free sending numbers are not supported. * Use `POST /v1/email/send` for email. Email uses `targets` or `convo_id`, not the SMS/MMS target fields on this page. ## Related pages > **[Messages](/concepts/messages)** > > Understand message targets, content, and delivery models. > **[Send message reference](/api-reference/messages/send)** > > Review all request fields and response fields. > **[Message history reference](/api-reference/messages/get-history)** > > Retrieve messages for a conversation when you need to verify delivery or replies. > **[Webhook guide](/guides/webhooks)** > > Receive inbound replies and message events in real time. # Receive webhook events Source: https://developers.heymarket.com/guides/webhooks Configure a webhook receiver for Heymarket events, validate the payload token, and acknowledge deliveries quickly. Webhooks let Heymarket send event notifications to your server when messages, conversations, opt-in state, or call activity changes. Use webhooks for workflows that need to react without polling the REST API. ## How delivery works Heymarket sends an HTTPS `POST` request to your configured webhook URL. Your receiver should parse the JSON body, validate the token for the webhook configuration, persist what it needs, and return a 2xx response quickly. If processing may take more than a short moment, acknowledge the webhook first and move the work to a queue. Make processing idempotent with `type` plus a stable ID inside `event_data` when one is present, so repeated deliveries do not create duplicate work. ## Steps **1. Create an HTTPS receiver** Create a public HTTPS endpoint that accepts `POST` requests with JSON bodies. ```python webhook receiver theme={null} from flask import Flask, request app = Flask(__name__) @app.route("/webhooks/heymarket", methods=["POST"]) def handle_heymarket_event(): payload = request.get_json() if not payload: return "", 400 if payload.get("token") != "WEBHOOK_VERIFICATION_TOKEN": return "", 401 # Queue follow-up work before returning if processing will be slow. print(payload["type"], payload["id"]) return "", 200 ``` **2. Configure the webhook URL** Add the HTTPS endpoint in the Heymarket app webhook settings. Store the webhook verification token from that configuration and compare it with the `token` value in every payload. > [!NOTE] > > For app configuration steps, see the Heymarket Help Center article: [Getting Started with Webhooks](https://help.heymarket.com/hc/en-us/articles/4416494130701-Getting-Started-with-Webhooks). Webhook URLs are configured in the Heymarket app. The public REST API reference documents webhook payloads, but it does not provide a webhook-management endpoint. **3. Handle the event payload** Webhook payloads include `type`, `id`, `token`, and `event_data`. ```json theme={null} { "type": "message_recieved", "id": "6f1d5b0c-2a4e-4c8f-9b3a-7d2e1f4a8c60", "token": "WEBHOOK_VERIFICATION_TOKEN", "event_data": { "id": 9980041, "text": "What are your hours?", "target": "15105553344", "phone": "14155550188", "date": "2026-04-16T14:22:00Z" } } ``` The envelope `id` is a UUID generated for the event, not a webhook configuration ID. Every webhook configuration that receives the same event receives the same `id`, so pair it with `type` to deduplicate deliveries. The `event_data` shape depends on the event type. Message events send a message object, conversation events send a conversation object, and opt-in or opt-out events send an unsubscribe-state object. ## Event types Webhook `type` values include: * `message_sent` * `message_recieved` * `chat_reassigned` * `chat_closed` * `chat_opened` * `chat_pending` * `chat_transferred` * `target_opt_out` * `target_opt_in` * `target_double_opt_in` * `target_double_opt_restricted` * `incoming_call` > [!NOTE] > > The current API spelling is `message_recieved`. Match that exact value when branching on inbound-message events. ## Message event payload `message_sent` and `message_recieved` events send a message object as `event_data`. For the full field list, see the [webhook event reference](/api-reference/webhooks). When you write a receiver for message events, plan for these behaviors: * Most fields are omitted when empty. Treat a missing key as an empty or zero value rather than requiring it. * `user` identifies the sending team member and is omitted on incoming messages. Use its presence to tell outbound from inbound. * `super` is the conversation ID. The full conversation is also embedded as `conversation`. * Group messages set `targets` with the individual phone numbers and `sender` with the sending number. On those events `target` is a single `|`-separated string. * `status` and `raw_error` apply to outbound messages. For group messages, per-target failures arrive in `target_errors`, keyed by target. * `hidden` marks a message whose content is withheld, so content fields can be empty even though the message exists. > [!NOTE] > > The `flagged` field appears in the message schema but is not populated on webhook deliveries. ### Example An inbound SMS delivers a payload like this. ```json theme={null} { "type": "message_recieved", "id": "6f1d5b0c-2a4e-4c8f-9b3a-7d2e1f4a8c60", "token": "WEBHOOK_VERIFICATION_TOKEN", "event_data": { "id": 9980041, "target": "15105553344", "text": "What are your hours?", "length": 20, "date": "2026-04-16T14:22:00Z", "updated": "2026-04-16T14:22:00Z", "super": 4417723, "team_id": 51204, "phone": "14155550188", "channel": "sms", "hidden": false, "conversation": { "id": 4417723, "inbox": 83125, "assigned": 6602, "target": "15105553344", "channel": "sms", "status": "active", "created": "2026-03-02T18:04:11Z", "updated": "2026-04-16T14:22:00Z", "unread_users": [6602], "tags": [] } } } ``` ## Security * Keep webhook URLs private. * Validate the `token` value before processing the event. * Do not expose webhook URLs in client-side code or public repositories. * Use separate webhook URLs for separate environments. * Log event type, webhook ID, and processing outcome for troubleshooting. ## Related pages > **[Webhooks concept](/concepts/webhooks)** > > Understand where webhooks fit with REST API calls. > **[Messages reference](/api-reference/messages/send)** > > Send follow-up messages after webhook events. > **[Webhook event reference](/api-reference/webhooks)** > > Review the generated webhook payload schema. # Heymarket API Source: https://developers.heymarket.com/index Use the Heymarket REST API to send messages, manage contacts, organize conversations, and connect Heymarket with your own systems. The Heymarket API lets you build customer messaging workflows on top of the same inboxes, contacts, conversations, lists, and templates your team uses in Heymarket. > **[Read the overview](/introduction)** > > Learn how the API is organized and what you can build with it. > **[Authentication](/authentication)** > > Create an API Secret and generate signed JWTs. > **[Send a message](/guides/send-message)** > > Follow the first-message guide after you have an inbox ID and sender ID. > **[API reference](/api-reference/contacts/overview)** > > Browse endpoint details for contacts, messages, conversations, and more. ## Quick start You need a Heymarket account and an API Secret before making API calls. **1. Create an API Secret** Log in to [Heymarket](https://app.heymarket.com) and go to **Settings > Integrations > API** to generate an API Secret. **2. Generate a signed JWT** Follow the [authentication guide](/authentication) to generate a short-lived JWT from your API Secret ID and Secret Key. **3. Authenticate requests** Pass your signed JWT as a Bearer token in every request: ```text theme={null} Authorization: Bearer YOUR_SIGNED_JWT ``` **4. Confirm access** Call `GET /v1/inboxes` to verify authentication and find the inbox IDs available to your team. ```bash theme={null} curl https://api.heymarket.com/v1/inboxes \ -H "Authorization: Bearer YOUR_SIGNED_JWT" ``` **5. Send a message** Use an inbox ID and sender member ID from your account when sending a message. ```bash theme={null} curl -X POST https://api.heymarket.com/v1/message/send \ -H "Authorization: Bearer YOUR_SIGNED_JWT" \ -H "Content-Type: application/json" \ -d '{ "inbox_id": 42, "creator_id": 7, "phone_number": "14155551234", "text": "Hello from the Heymarket API!" }' ``` **6. Keep building** Browse the [API Reference](/api-reference/contacts/overview) to discover contacts, conversations, lists, templates, and more. ## Product links > **[Sign in](https://app.heymarket.com)** > > Open Heymarket to get credentials, inbox IDs, and team member IDs. > **[Book a demo](https://www.heymarket.com/demo/)** > > Talk with Heymarket about your messaging use case. > **[Visit the website](https://www.heymarket.com/)** > > Learn more about Heymarket products, integrations, and pricing. > **[Contact sales](https://www.heymarket.com/contact-sales/)** > > Ask about plans, availability, and account setup. # Get started with the Heymarket API Source: https://developers.heymarket.com/introduction An overview of the Heymarket REST API: what it is, what you can build with it, the base URL, JSON encoding requirements, and mandatory HTTPS. Heymarket is a business messaging platform for SMS, MMS, and email conversations. The Heymarket API gives you programmatic access to customer-facing messaging workflows, including sending messages, managing contacts, organizing lists, and tracking conversations. Use the API when you need to connect Heymarket to a CRM, ecommerce system, internal tool, data pipeline, or custom customer workflow. ## What you can do With the Heymarket API you can: * **Send messages** - send SMS, MMS, and email messages to individual contacts or lists * **Manage contacts** - create, update, search, delete, and enrich contact records * **Track conversations** - open, close, reassign, and transfer customer conversations * **Organize lists** - build and manage contact lists for broadcast messaging * **Use templates** - create and send saved message templates * **Schedule messages** - queue messages to send at a specific time * **Receive webhooks** - subscribe to real-time events for inbound messages and status changes ## Base URL Send all API requests to: ```text theme={null} https://api.heymarket.com ``` ## JSON encoding Request bodies must be JSON-encoded. Most object responses are JSON; some endpoints and errors return plain text or string values as shown in the API reference. Set `Content-Type: application/json` on every request that includes a body. ```bash theme={null} Content-Type: application/json ``` ## HTTPS requirement The Heymarket API accepts requests over HTTPS. Requests made over plain HTTP or without authentication fail. ## Authentication Every endpoint in the OpenAPI reference requires authentication. API Secret JWT authentication is recommended for new integrations. See [Authentication](/authentication) for all supported methods. ## Next steps > **[Authentication](/authentication)** > > Create an API Secret and learn how to authenticate every request. > **[Rate limits](/rate-limits)** > > Understand rate limits, request IDs, and how to handle errors. > **[Send a message](/guides/send-message)** > > Follow a step-by-step guide to send your first SMS or MMS. > **[API reference](/api-reference/contacts/overview)** > > Browse the full reference for all available endpoints. # Connect AI tools with the Heymarket MCP server Source: https://developers.heymarket.com/mcp-server Connect Claude, Cursor, and other AI clients to Heymarket over the Model Context Protocol (MCP) to manage contacts, send messages, triage conversations, and run supervised broadcast campaigns. The Model Context Protocol (MCP) lets AI tools and agents connect to Heymarket in a secure, standardized way. Heymarket hosts a remote MCP server, so a connected client can read your inboxes, search contacts and messages, send messages, triage conversations, and draft broadcast campaigns on behalf of an authenticated team member. > [!NOTE] > > The Heymarket MCP server is in beta. Endpoints, tools, and authentication may change. Try it on a non-critical team first and share feedback with Heymarket. **October 2026** MCP no longer needs to be enabled by Heymarket: * **On by default** - every team has MCP on unless someone turns it off * **Team MCP switch** - owners and admins can turn MCP off for the whole team under **Administration > Team**; existing tokens work again when it is turned back on * **Locked when Heymarket turns MCP off** - Heymarket can turn MCP off for a team; the team's MCP switch then shows **Off** and stays locked until Heymarket support lifts the lock * **New 403 codes** - `mcp_team_expired`, `mcp_disabled_by_heymarket`, and `mcp_disabled_by_admin` explain why a valid token was refused, alongside `mcp_permission_required`; they replace `mcp_not_enabled` * **MCP status in the API** - [Get team](/api-reference/team) now returns `mcp_disabled` and `mcp_disabled_by_heymarket` See [Turn MCP on or off for your team](#turn-mcp-on-or-off-for-your-team). **September 2026** Access to the MCP server is now controlled per role: * **New MCP Access permission** - a team member can create a Personal Access Token and connect an MCP client only when their role includes **MCP Access** * **Included for Owner and Admin** - the Member role does not include it; admins can grant it to members through a custom role * **Checked on every request** - turning the permission off for a role stops its members' existing tokens on their next request See [Who can use MCP](#who-can-use-mcp). **August 2026** The tool surface grew from 12 to 34 tools. New in this release: * **Supervised broadcasts** - draft a campaign to a list, review the recipient count, and submit it for human approval before anything sends * **Contact lists** - create and manage the lists broadcasts send to * **Templates and team members** - discover message templates and look up teammates * **Triage and search** - list and count conversations across inboxes, full-text search over message bodies, and a conversation activity timeline * **Richer contact search** - filter by tags, custom fields, lists, opt-out status, and assigned user * **Token creation opened to members** - any team member (not just admins) can now create a Personal Access Token ## What you can do Once a client is connected, you can ask it to: * **Browse inboxes** - list the inboxes you belong to and look up inbox details * **Manage contacts** - create, retrieve, update, delete, and search contacts, including by tag, custom field, list, opt-out status, or assigned user * **Send messages** - send a message to a contact through an inbox * **Read and search history** - pull a conversation's message history or full-text search message bodies across your inboxes * **Triage conversations** - list conversations longest-waiting first, count them by assignee, inbox, or status, and see a conversation's assignment history * **Work conversations** - assign a conversation to a team member or transfer it to another inbox * **Run broadcasts with approval** - build an audience list, draft a broadcast, and submit it for a human admin to approve before it sends The MCP server exposes Heymarket actions only. The AI model itself lives in your client (Claude Desktop, Cursor, and so on), not in Heymarket. ## Endpoint and transport Connect your client to the Heymarket MCP server at: ```text theme={null} https://mcp.heymarket.com/mcp ``` The server uses the **Streamable HTTP** transport and speaks JSON-RPC over HTTP. Clients that support remote MCP servers can connect to this URL directly. Clients that speak only stdio (such as Claude Desktop) connect through the open-source [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) bridge, which runs the remote server as a local command. ## Authentication The MCP server authenticates each request with a **Personal Access Token (PAT)**. A PAT is a long-lived credential scoped to one team member and one team. Every MCP request sends it as a bearer token: ```text theme={null} Authorization: Bearer hm_pat_your_token ``` ### Who can use MCP A team member can create a token for a team and connect a client to it only when both of these are true: * **MCP is on for the team.** MCP is on for every team by default. A team owner or admin can turn it off, and so can Heymarket. MCP is also unavailable while the team's subscription has expired or the team is suspended. See [Turn MCP on or off for your team](#turn-mcp-on-or-off-for-your-team). * **Your role in that team includes the MCP Access permission.** | Role | MCP Access | | - | - | | Owner | Included | | Admin | Included | | Member | Not included. An admin can grant it with a custom role, as described below. | | Contact and Guest | Not included. MCP tools also refuse custom roles duplicated from Contact or Guest, even with MCP Access turned on. | | Custom roles | Set per role under **Administration > Roles**. | Heymarket checks both conditions on every MCP request, not only when you create a token. If an admin turns off MCP Access for your role, moves you to a role without it, or turns MCP off for the team, your existing token stops working within a few seconds. The same happens when Heymarket turns MCP off for the team or the team's subscription expires. The token is blocked, not revoked, and it works again once the cause is cleared. ### Grant MCP access to a role System roles such as Owner, Admin, and Member cannot be modified, so to give members MCP access, create a custom role that includes it. You need the **Roles** permission to manage roles, and creating custom roles depends on your Heymarket plan. **1. Open Roles** In [Heymarket](https://app.heymarket.com), click the **Administration** icon in the left sidebar, then select the **Roles** tab. **2. Duplicate the Member role** Open the **⋯** menu on the **Member** row, select **Duplicate**, and click **Copy** to confirm. The new role is named **Copy of Member**. To rename it, open its **⋯** menu and select **Edit**. **3. Turn on MCP Access** Open the new role, check **MCP Access**, and click **Save Role**. **4. Assign the role** On the **Users** tab, assign the new role to each team member who needs MCP access. ### Turn MCP on or off for your team A team owner or admin can turn MCP off for the whole team, for example to pause every connected AI client at once. Only members whose role is the Owner or Admin system role can change this setting. Custom roles cannot, including roles duplicated from Admin. **1. Open the Team tab** In [Heymarket](https://app.heymarket.com), click the **Administration** icon in the left sidebar, then select the **Team** tab. **2. Find the MCP Access section** The **MCP (Model Context Protocol)** switch in the **MCP Access** section is the team's MCP switch. It shows whether MCP is **On** or **Off** for the team. **3. Turn MCP off** Turn the switch off. In the **Turn off MCP for this team?** dialog, click **Turn Off** to confirm. The message "MCP access updated." confirms the change. While MCP is off for the team: * MCP clients connected to the team stop working within a few seconds. Their requests return `403 Forbidden` with the code `mcp_disabled_by_admin`. * No one can generate a new token for the team. * Existing tokens are kept, not revoked. To turn MCP back on, turn the same switch on. No confirmation is needed. Existing tokens work again within a few seconds, and connected clients do not need to be reconfigured. To read the setting through the Heymarket API, call [Get team](/api-reference/team). `mcp_disabled` is `true` when a team owner or admin has turned MCP off, and `mcp_disabled_by_heymarket` is `true` when Heymarket has. MCP is off when either field is `true`. Neither field shows an expired subscription, a suspended team, or whether a member's role includes MCP Access. > [!NOTE] > > Heymarket can also turn MCP off for a team, for example, at your request. While it is off, the team's MCP switch shows **Off** and is locked, with the tooltip "MCP was disabled for this team by Heymarket. Contact Heymarket support to turn it back on." Team owners and admins cannot turn MCP on while the switch is locked. MCP requests return `403 Forbidden` with the code `mcp_disabled_by_heymarket`. When Heymarket lifts the lock, the switch returns to your team's own setting. If a team owner or admin had turned MCP off before, it stays off until they turn it on. ### Create a Personal Access Token You create a PAT from your profile in the Heymarket app. You can create one for any team where MCP is on and your role includes MCP Access. **1. Open your profile** Log in to [Heymarket](https://app.heymarket.com), click your avatar in the bottom-left to open the account dropdown, and select **Profile**. **2. Scroll to Personal Access Tokens** On your profile page, scroll down past **About** and **Two-Factor Authentication** to the **Personal Access Tokens** section. Any existing tokens are listed here with their name, team, masked token value, creation date, and last-used timestamp. **3. Click Generate token** Click the **Generate token** button in the top-right of that section. **4. Fill in the token details** In the **Generate personal access token** modal: * **Token name** - enter something descriptive, such as `Claude Desktop on laptop`, so you recognize its purpose later. * **Team** - select the team the token should be scoped to. Each token is tied to exactly one team. Teams you already hold a token for are disabled in the dropdown, because you can only hold one active token per team. Other teams you cannot create a token for are also disabled and labeled with the reason. See **Creating a token is blocked** under [Troubleshooting](#troubleshooting). **5. Generate and copy the token** Click **Generate token**. A **Token created** confirmation appears showing the token name, team, and the full token value starting with `hm_pat_`. Click **Copy** right away — the full value is not shown again after you close the dialog. **6. Store it and close** Paste the token somewhere secure — a password manager, or directly into the `HEYMARKET_PAT` value of your MCP client configuration (covered in [Connect a client](#connect-a-client)) — then click **Done**. > [!WARNING] > > The full token is shown only when you create it. Heymarket stores it hashed and cannot show it again. Store it only in your MCP client configuration, and treat it like a password. ### Token scope and limits * **One team per token.** A PAT grants access to the single team it was created in, using your role and inbox access in that team. * **MCP Access required.** Your role in the token's team must include MCP Access, both when you create the token and on every request it makes. See [Who can use MCP](#who-can-use-mcp). * **MCP on for the team.** The token works only while MCP is on for its team. See [Who can use MCP](#who-can-use-mcp). * **Tools follow your role.** A token acts at your role. Most tools require a Member, Admin, or Owner role, and some require Admin or Owner. See [Available tools](#available-tools). * **One active token per team member.** Each member can hold one active PAT per team. To replace a token, revoke the old one first, then create a new one. * **No expiry.** A PAT stays valid until you revoke it. Revoke it from the **Personal Access Tokens** section of your profile if a device is lost or a team member leaves. Revocation takes effect on the next request. ## Connect a client Add the Heymarket server to your client's MCP configuration, then restart the client. Replace `hm_pat_REPLACE_WITH_YOUR_TOKEN` with the token you created. Clients that run the `mcp-remote` bridge need [Node.js](https://nodejs.org) 18 or later so they can invoke `npx`. **Claude Desktop** Open your `claude_desktop_config.json` file: | OS | Path | | - | - | | macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` | | Windows | `%APPDATA%\Claude\claude_desktop_config.json` | | Linux | `~/.config/Claude/claude_desktop_config.json` | Add the `heymarket` server, then fully quit and reopen Claude Desktop: ```json theme={null} { "mcpServers": { "heymarket": { "command": "npx", "args": [ "-y", "mcp-remote", "https://mcp.heymarket.com/mcp", "--header", "Authorization: Bearer ${HEYMARKET_PAT}" ], "env": { "HEYMARKET_PAT": "hm_pat_REPLACE_WITH_YOUR_TOKEN" } } } } ``` Keep the token in `env` rather than inline in `args` so it does not appear in process listings. After restarting, open a new chat and look for the tools indicator listing the Heymarket tools. **Claude Code** Claude Code connects to remote MCP servers over HTTP, so it does not need the bridge. Add the server with one command: ```bash theme={null} claude mcp add --transport http heymarket https://mcp.heymarket.com/mcp \ --header "Authorization: Bearer hm_pat_REPLACE_WITH_YOUR_TOKEN" ``` Verify the connection: ```bash theme={null} claude mcp list ``` **Cursor** Add the server to your Cursor MCP configuration at `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per project), then reload Cursor: ```json theme={null} { "mcpServers": { "heymarket": { "url": "https://mcp.heymarket.com/mcp", "headers": { "Authorization": "Bearer hm_pat_REPLACE_WITH_YOUR_TOKEN" } } } } ``` **ChatGPT** The ChatGPT desktop app connects to remote MCP servers over HTTP, so it does not need the bridge. ChatGPT on the web cannot add a custom MCP server. **1. Open MCP settings** In the ChatGPT desktop app, open **Settings**, select **Plugins**, then select the **MCPs** tab. **2. Add a server** Click **Add** and choose to add an MCP server. Select **Streamable HTTP** as the server type. **3. Fill in the server details** | Field | Value | | - | - | | **Name** | `heymarket`, or a name that identifies the team, such as `heymarket-support` | | **URL** | `https://mcp.heymarket.com/mcp` | | **Bearer token env var** | Leave empty | | **Headers** | Click **Add header**. Set the key to `Authorization` and the value to `Bearer hm_pat_REPLACE_WITH_YOUR_TOKEN`. | | **Headers from environment variables** | Leave empty | **4. Save and restart** Click **Save**, then click **Restart** when prompted, or quit and reopen the app. **5. Verify the connection** Start a new chat and type `/mcp`. The Heymarket server is listed as enabled. > [!WARNING] > > Do not paste the token into **Bearer token env var**. That field takes the name of an environment variable, not the token itself. If you enter the token there, ChatGPT looks for an environment variable with that name, finds none, and every request returns `401 Unauthorized`. Each token is scoped to one team. To connect more than one team, create a token for each team and add a separate server for each one. **Other clients** Any MCP client that supports a stdio command can connect through the `mcp-remote` bridge. Use the same shape as the Claude Desktop config: ```json theme={null} { "mcpServers": { "heymarket": { "command": "npx", "args": [ "-y", "mcp-remote", "https://mcp.heymarket.com/mcp", "--header", "Authorization: Bearer ${HEYMARKET_PAT}" ], "env": { "HEYMARKET_PAT": "hm_pat_REPLACE_WITH_YOUR_TOKEN" } } } } ``` Clients that support remote HTTP MCP servers directly can connect to `https://mcp.heymarket.com/mcp` and send the `Authorization: Bearer` header without the bridge. ## Available tools The server advertises 34 tools. Your client discovers them automatically after it connects. Every tool requires a Member, Admin, or Owner role. `delete_contact`, `delete_list`, and `approve_broadcast` require Admin or Owner. A custom role counts as the role it was duplicated from, and a role created with the **Create** button on the **Roles** tab counts as Member. ### Inboxes | Tool | What it does | Writes data | | - | - | - | | `list_inboxes` | List the inboxes you are a member of. Membership-scoped, not team-wide. | No | | `get_inbox` | Fetch details for one inbox, including whether it is currently in quiet hours. | No | ### Conversations | Tool | What it does | Writes data | | - | - | - | | `list_conversations` | List conversations for triage, longest-waiting first, in one inbox or across every inbox you belong to. Filter by assignee, contact, status, needs-reply, and time window. | No | | `count_conversations` | Count conversations in one aggregate query, grouped by assigned user, inbox, or status, with the same filters as `list_conversations`. | No | | `get_conversation` | Fetch details for a conversation. | No | | `get_conversation_activity` | Timeline of a conversation's assignment, unassignment, and inbox-transfer events. | No | | `assign_conversation` | Assign a conversation to a team member. | Yes | | `transfer_conversation` | Transfer a conversation to another inbox. | Yes | ### Messages | Tool | What it does | Writes data | | - | - | - | | `send_message` | Send a message to a contact through an inbox. | Yes | | `get_message_history` | Get one conversation's messages, by conversation ID or by inbox plus the contact's phone or email, with time-window filters and cursor paging. | No | | `search_messages` | Full-text search over message bodies across the inboxes you belong to, scoped by inbox and time window. | No | ### Contacts | Tool | What it does | Writes data | | - | - | - | | `create_contact` | Create a contact with a communication channel. | Yes | | `get_contact` | Retrieve a contact's full record: channels, tags, notes, and custom field values. | No | | `update_contact` | Update a contact's standard fields. Omitted fields keep their stored values. | Yes | | `delete_contact` | Delete a contact. Admin or owner only. | Yes | | `search_contacts` | Search contacts by free text (name, phone, or exact email), or filter by tag, custom fields, list, opt-out status, and assigned user. | No | | `list_custom_fields` | List the team's custom contact field definitions, for use in `search_contacts` filters. | No | | `list_tags` | List the team's contact tags, for use in `search_contacts` filters. | No | ### Lists | Tool | What it does | Writes data | | - | - | - | | `list_lists` | List the contact lists you can use, with member counts. | No | | `get_list` | Get a list by ID or name: live member count and whether an active campaign uses it. | No | | `create_list` | Create a new, empty contact list. | Yes | | `add_contacts_to_list` | Add up to 500 contacts to a list per call. | Yes | | `remove_contacts_from_list` | Remove up to 500 contacts from a list per call. | Yes | | `rename_list` | Rename a contact list. | Yes | | `delete_list` | Delete a contact list. Refuses when an active campaign uses the list; contacts themselves are never deleted. Admin or owner only. | Yes | ### Templates and team members | Tool | What it does | Writes data | | - | - | - | | `list_templates` | List the team's message templates. | No | | `get_template` | Get a template's full content by ID or name. | No | | `list_users` | List the team's members with their roles, to discover assignees for `assign_conversation`. | No | ### Broadcasts | Tool | What it does | Writes data | | - | - | - | | `create_broadcast` | Create a draft broadcast to a list. Nothing is sent; returns the opt-out-filtered recipient count and a message preview for review. | Yes | | `get_broadcast` | Get a broadcast's status and recipient count. | No | | `list_broadcasts` | List the team's broadcasts, newest first, filtered by status, inbox, or creation time. | No | | `send_broadcast` | Submit a draft for sending. The broadcast moves to pending approval; it does not send until a human approves. | Yes | | `approve_broadcast` | Approve a pending broadcast so it actually sends. Admin or owner only. | Yes | | `cancel_broadcast` | Cancel a pending or scheduled broadcast, returning it to draft. | Yes | > [!NOTE] > > Tools that write data run only with your approval. Most MCP clients ask before invoking a tool that sends a message or changes a contact, list, or conversation, unless you allow that tool for the session. ## How broadcasts stay supervised Every broadcast sent through MCP requires human approval — an AI agent cannot mass-message on its own. The flow has three gates: **1. Draft** `create_broadcast` creates a draft and sends nothing. It returns the recipient count with opted-out contacts already filtered, sample recipients, and a preview of the message so a person can review what would go out. **2. Confirm the count** `send_broadcast` requires `expected_recipient_count` to match the count shown on the draft. If the live count has changed — for example, contacts opted out since drafting — the call is rejected with the new count, forcing a fresh review. On success the broadcast moves to **pending approval**; still nothing sends. **3. Human approval** A team **admin or owner** approves the pending broadcast with `approve_broadcast` (or rejects it with `cancel_broadcast`, which returns it to draft). Only after approval does the broadcast send. Retries are safe throughout: `create_broadcast` and `create_list` accept an optional `idempotency_key`, and resubmitting or re-approving an already-processed broadcast returns success with `idempotent_replay: true` instead of acting twice. ## Prompts The server also publishes two MCP prompts — guided workflows your client can offer alongside the tools: * **`launch_campaign`** - walks the client through building an audience, composing a message, creating a draft broadcast, reviewing the filtered recipient count with you, and submitting it for approval * **`review_pending_broadcast`** - renders a pre-send review of a broadcast awaiting approval: recipients, message, and the exact approval step. Run it before approving. ## Rate limits The server limits how often you can call it. When you reach a limit, the server returns HTTP `429 Too Many Requests` with a `Retry-After: 60` header. Wait that many seconds before you retry. * **Requests from a valid token.** A token can send about 120 requests a minute. All of a team's tokens together can send about 600 a minute. Slow down if you reach either limit. * **Invalid tokens (`401 Unauthorized`).** After about 30 failed attempts in a minute from the same IP address or with the same token, requests return `429`. Fix or replace the token before you retry. * **Refused valid tokens (`403 Forbidden`).** After about 30 refusals in a minute for the same token, requests return `429`. Fix the cause named by the `code`. Do not replace the token. These refusals do not count against your other tokens. ## Security best practices * Store the token only in your MCP client configuration. Do not commit it to source control or paste it into shared documents. * A PAT acts as your account's access for the team it was created in, at your role. Anyone with the token can act as you in that team. * Revoke a token immediately from the **Personal Access Tokens** section of your profile when a device is lost, a team member leaves, or you suspect the token leaked. Revocation takes effect on the next request. * Rotate a token by revoking the old one and creating a new one. There is no separate refresh step. * To cut off MCP for everyone in a role at once, turn off **MCP Access** on that role. Their tokens stop working within a few seconds. * To cut off MCP for the whole team at once, turn off the team's MCP switch. See [Turn MCP on or off for your team](#turn-mcp-on-or-off-for-your-team). Tokens are kept and work again when you turn MCP back on. Before you turn it back on, ask the owner of any token that may have leaked to revoke it from the **Personal Access Tokens** section of their profile. ## Troubleshooting > **The client does not show the Heymarket tools** > > Confirm the configuration file is valid JSON and points to `https://mcp.heymarket.com/mcp`. Most clients read MCP configuration only at startup, so fully quit and reopen the client after editing it. In Claude Desktop, check **Settings > Developer** for MCP server startup errors. > **Requests return 401 Unauthorized** > > The token is missing, malformed, or revoked. Confirm the `Authorization` header is `Bearer hm_pat_...` with no extra spaces or line breaks, and that the token has not been revoked. If you are unsure, create a new token and update your client configuration. > > Requests also return `401 Unauthorized` if you are no longer a member of the token's team, or if the team is suspended. You cannot create a new token for a suspended team. Contact Heymarket support. > **Requests return 403 Forbidden** > > The token is valid, but the server refused it for its team. The JSON response body says why: > > ```json theme={null} > { > "error": "a team admin has turned off mcp for this team", > "code": "mcp_disabled_by_admin" > } > ``` > > Match on `code`. The `error` text is for people and may change. The `code` is one of: > > * `mcp_team_expired` - the team's subscription has expired. Ask whoever manages billing for your team to renew it. > * `mcp_disabled_by_heymarket` - Heymarket has turned MCP off for the team. Contact Heymarket support. > * `mcp_disabled_by_admin` - a team owner or admin has turned MCP off for the team. Ask them to turn it back on (see [Turn MCP on or off for your team](#turn-mcp-on-or-off-for-your-team)). > * `mcp_permission_required` - your role in that team does not include MCP Access. Ask a team admin to grant it (see [Grant MCP access to a role](#grant-mcp-access-to-a-role)). > > When more than one applies, the server reports the first one in this list. You do not need a new token: your existing one works again once the cause is fixed. Until then, stop retrying with it. Repeated refusals are throttled, as described in [Rate limits](#rate-limits). > **Requests return 429 Too Many Requests** > > The server is throttling your requests. Check which response you got before the `429`: > > * `401 Unauthorized` - the token is bad or revoked. Fix or replace it. > * `403 Forbidden` - the token is valid but was refused. Fix the cause given by its `code`. > * Neither - the token, or the team's tokens together, sent too many requests. Slow down. > > Wait for the `Retry-After` period, 60 seconds, before trying again. See [Rate limits](#rate-limits). > **Creating a token is blocked** > > You can create a Personal Access Token for a team only when MCP is on for that team and your role there includes MCP Access. In the **Team** dropdown of the **Generate personal access token** modal, each team you cannot create a token for is disabled and labeled with the reason. Your profile can also show a message about it. > > * **subscription expired**, or "This team's subscription has expired. Renew it to use MCP." - ask whoever manages billing for your team to renew it. > * **MCP disabled by Heymarket**, or "MCP has been disabled for this team by Heymarket. Contact Heymarket support." - contact Heymarket support. > * **MCP turned off by a team admin**, or "A team admin has turned off MCP for this team." - ask a team owner or admin to turn MCP back on. > * **ask a team admin for MCP access**, or "MCP access isn't enabled for your role yet." or "Ask a team admin to enable MCP access for your role." - ask a team admin to grant MCP Access to your role (see [Grant MCP access to a role](#grant-mcp-access-to-a-role)). > * **team suspended** - no one can use MCP or create tokens for a suspended team. Contact Heymarket support. > > Each member can also hold only one active token per team. If you already have one for that team, revoke it before creating a new one. > **The MCP Access section is missing from the Team tab** > > Only members whose role is the Owner or Admin system role see the **MCP Access** section on the **Team** tab. Custom roles do not see it, even roles duplicated from Admin. Ask a team owner or admin to change the setting for you. # Rate limits and request IDs Source: https://developers.heymarket.com/rate-limits Understand the Heymarket API's 500 req/min rate limit, how to use X-Request-Id when contacting support, and how to handle HTTP error codes. The Heymarket API enforces rate limits to keep the service stable. If your integration sends requests too quickly, the API returns an error and you must slow down before retrying. Every response also includes a request ID that helps Heymarket support trace individual calls. ## Rate limit The public API limit is **500 requests per minute**. When you exceed this limit, the API responds with HTTP `429 Too Many Requests`. > [!TIP] > > Use exponential backoff when you receive a `429` response. Wait briefly, retry, and increase the wait time after each failed retry until the request succeeds or reaches your retry limit. > [!WARNING] > > Be careful when retrying write requests. If a request may have reached Heymarket before your client received an error, retrying can create duplicate side effects unless the endpoint supports an idempotency field such as `local_id`. ## Request IDs Every response from the Heymarket API includes an `X-Request-Id` header: ```text theme={null} X-Request-Id: a1b2c3d4-e5f6-7890-abcd-ef1234567890 ``` Log this value alongside your own application logs. When you contact Heymarket support about an issue, include the `X-Request-Id` so the team can find the exact request. ## Error codes The table below covers the most common HTTP status codes returned by the API. | Status code | Name | Description | | - | - | - | | `400` | `bad_data` | The request body is malformed or contains invalid fields. Fix the request before retrying. | | `401` | Unauthorized | The request is missing valid authentication credentials. Check the `Authorization` header and signed JWT. | | `403` | Denied | The credentials are valid, but the team or sender does not have access to perform the requested action. | | `429` | Rate limit exceeded | You have sent more than 500 requests in the current minute. Wait and retry with exponential backoff. | | `500` | Server error | An unexpected error occurred on the Heymarket side. Retry the request after a brief delay. If the error persists, contact support with the `X-Request-Id`. | > [!NOTE] > > A `400` error will not resolve by retrying. Read the error response body for details about which field failed validation, fix your request, and then send it again. > [!WARNING] > > If you receive repeated `500` responses, include the `X-Request-Id` header value when you contact support so the team can investigate.