curl --request POST \
--url https://productlane.com/api/v2/threads/bulk-update \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"ids": [
"<string>"
],
"external_ids": [
"<string>"
],
"text": "<string>",
"title": "<string>",
"assignee_id": "<string>",
"contact_id": "<string>",
"company_id": "<string>",
"tag_ids": [
"<string>"
],
"project_id": "<string>",
"snoozed_until": "2023-11-07T05:31:56Z",
"closed_loop": true,
"ai_draft_html": "<string>",
"ai_draft_sources": "<array>",
"clear_ai_draft_error": true,
"notify": {
"slack": true,
"email": true
}
}
'import requests
url = "https://productlane.com/api/v2/threads/bulk-update"
payload = {
"ids": ["<string>"],
"external_ids": ["<string>"],
"text": "<string>",
"title": "<string>",
"assignee_id": "<string>",
"contact_id": "<string>",
"company_id": "<string>",
"tag_ids": ["<string>"],
"project_id": "<string>",
"snoozed_until": "2023-11-07T05:31:56Z",
"closed_loop": True,
"ai_draft_html": "<string>",
"ai_draft_sources": "<array>",
"clear_ai_draft_error": True,
"notify": {
"slack": True,
"email": True
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
ids: ['<string>'],
external_ids: ['<string>'],
text: '<string>',
title: '<string>',
assignee_id: '<string>',
contact_id: '<string>',
company_id: '<string>',
tag_ids: ['<string>'],
project_id: '<string>',
snoozed_until: '2023-11-07T05:31:56Z',
closed_loop: true,
ai_draft_html: '<string>',
ai_draft_sources: '<array>',
clear_ai_draft_error: true,
notify: {slack: true, email: true}
})
};
fetch('https://productlane.com/api/v2/threads/bulk-update', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://productlane.com/api/v2/threads/bulk-update",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'ids' => [
'<string>'
],
'external_ids' => [
'<string>'
],
'text' => '<string>',
'title' => '<string>',
'assignee_id' => '<string>',
'contact_id' => '<string>',
'company_id' => '<string>',
'tag_ids' => [
'<string>'
],
'project_id' => '<string>',
'snoozed_until' => '2023-11-07T05:31:56Z',
'closed_loop' => true,
'ai_draft_html' => '<string>',
'ai_draft_sources' => '<array>',
'clear_ai_draft_error' => true,
'notify' => [
'slack' => true,
'email' => true
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://productlane.com/api/v2/threads/bulk-update"
payload := strings.NewReader("{\n \"ids\": [\n \"<string>\"\n ],\n \"external_ids\": [\n \"<string>\"\n ],\n \"text\": \"<string>\",\n \"title\": \"<string>\",\n \"assignee_id\": \"<string>\",\n \"contact_id\": \"<string>\",\n \"company_id\": \"<string>\",\n \"tag_ids\": [\n \"<string>\"\n ],\n \"project_id\": \"<string>\",\n \"snoozed_until\": \"2023-11-07T05:31:56Z\",\n \"closed_loop\": true,\n \"ai_draft_html\": \"<string>\",\n \"ai_draft_sources\": \"<array>\",\n \"clear_ai_draft_error\": true,\n \"notify\": {\n \"slack\": true,\n \"email\": true\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://productlane.com/api/v2/threads/bulk-update")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"ids\": [\n \"<string>\"\n ],\n \"external_ids\": [\n \"<string>\"\n ],\n \"text\": \"<string>\",\n \"title\": \"<string>\",\n \"assignee_id\": \"<string>\",\n \"contact_id\": \"<string>\",\n \"company_id\": \"<string>\",\n \"tag_ids\": [\n \"<string>\"\n ],\n \"project_id\": \"<string>\",\n \"snoozed_until\": \"2023-11-07T05:31:56Z\",\n \"closed_loop\": true,\n \"ai_draft_html\": \"<string>\",\n \"ai_draft_sources\": \"<array>\",\n \"clear_ai_draft_error\": true,\n \"notify\": {\n \"slack\": true,\n \"email\": true\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://productlane.com/api/v2/threads/bulk-update")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"ids\": [\n \"<string>\"\n ],\n \"external_ids\": [\n \"<string>\"\n ],\n \"text\": \"<string>\",\n \"title\": \"<string>\",\n \"assignee_id\": \"<string>\",\n \"contact_id\": \"<string>\",\n \"company_id\": \"<string>\",\n \"tag_ids\": [\n \"<string>\"\n ],\n \"project_id\": \"<string>\",\n \"snoozed_until\": \"2023-11-07T05:31:56Z\",\n \"closed_loop\": true,\n \"ai_draft_html\": \"<string>\",\n \"ai_draft_sources\": \"<array>\",\n \"clear_ai_draft_error\": true,\n \"notify\": {\n \"slack\": true,\n \"email\": true\n }\n}"
response = http.request(request)
puts response.read_body{
"updated": [
"<string>"
],
"failed": [
{
"id": "<string>",
"code": "<string>",
"message": "<string>"
}
]
}{
"message": "<string>",
"code": "<string>",
"issues": [
{
"message": "<string>"
}
]
}Update threads in bulk
Apply one patch to up to 100 threads. The batch counts as a single write against the rate limit and reports per-thread rejects in failed. Requires the threads:write scope.
curl --request POST \
--url https://productlane.com/api/v2/threads/bulk-update \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"ids": [
"<string>"
],
"external_ids": [
"<string>"
],
"text": "<string>",
"title": "<string>",
"assignee_id": "<string>",
"contact_id": "<string>",
"company_id": "<string>",
"tag_ids": [
"<string>"
],
"project_id": "<string>",
"snoozed_until": "2023-11-07T05:31:56Z",
"closed_loop": true,
"ai_draft_html": "<string>",
"ai_draft_sources": "<array>",
"clear_ai_draft_error": true,
"notify": {
"slack": true,
"email": true
}
}
'import requests
url = "https://productlane.com/api/v2/threads/bulk-update"
payload = {
"ids": ["<string>"],
"external_ids": ["<string>"],
"text": "<string>",
"title": "<string>",
"assignee_id": "<string>",
"contact_id": "<string>",
"company_id": "<string>",
"tag_ids": ["<string>"],
"project_id": "<string>",
"snoozed_until": "2023-11-07T05:31:56Z",
"closed_loop": True,
"ai_draft_html": "<string>",
"ai_draft_sources": "<array>",
"clear_ai_draft_error": True,
"notify": {
"slack": True,
"email": True
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
ids: ['<string>'],
external_ids: ['<string>'],
text: '<string>',
title: '<string>',
assignee_id: '<string>',
contact_id: '<string>',
company_id: '<string>',
tag_ids: ['<string>'],
project_id: '<string>',
snoozed_until: '2023-11-07T05:31:56Z',
closed_loop: true,
ai_draft_html: '<string>',
ai_draft_sources: '<array>',
clear_ai_draft_error: true,
notify: {slack: true, email: true}
})
};
fetch('https://productlane.com/api/v2/threads/bulk-update', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://productlane.com/api/v2/threads/bulk-update",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'ids' => [
'<string>'
],
'external_ids' => [
'<string>'
],
'text' => '<string>',
'title' => '<string>',
'assignee_id' => '<string>',
'contact_id' => '<string>',
'company_id' => '<string>',
'tag_ids' => [
'<string>'
],
'project_id' => '<string>',
'snoozed_until' => '2023-11-07T05:31:56Z',
'closed_loop' => true,
'ai_draft_html' => '<string>',
'ai_draft_sources' => '<array>',
'clear_ai_draft_error' => true,
'notify' => [
'slack' => true,
'email' => true
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://productlane.com/api/v2/threads/bulk-update"
payload := strings.NewReader("{\n \"ids\": [\n \"<string>\"\n ],\n \"external_ids\": [\n \"<string>\"\n ],\n \"text\": \"<string>\",\n \"title\": \"<string>\",\n \"assignee_id\": \"<string>\",\n \"contact_id\": \"<string>\",\n \"company_id\": \"<string>\",\n \"tag_ids\": [\n \"<string>\"\n ],\n \"project_id\": \"<string>\",\n \"snoozed_until\": \"2023-11-07T05:31:56Z\",\n \"closed_loop\": true,\n \"ai_draft_html\": \"<string>\",\n \"ai_draft_sources\": \"<array>\",\n \"clear_ai_draft_error\": true,\n \"notify\": {\n \"slack\": true,\n \"email\": true\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://productlane.com/api/v2/threads/bulk-update")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"ids\": [\n \"<string>\"\n ],\n \"external_ids\": [\n \"<string>\"\n ],\n \"text\": \"<string>\",\n \"title\": \"<string>\",\n \"assignee_id\": \"<string>\",\n \"contact_id\": \"<string>\",\n \"company_id\": \"<string>\",\n \"tag_ids\": [\n \"<string>\"\n ],\n \"project_id\": \"<string>\",\n \"snoozed_until\": \"2023-11-07T05:31:56Z\",\n \"closed_loop\": true,\n \"ai_draft_html\": \"<string>\",\n \"ai_draft_sources\": \"<array>\",\n \"clear_ai_draft_error\": true,\n \"notify\": {\n \"slack\": true,\n \"email\": true\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://productlane.com/api/v2/threads/bulk-update")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"ids\": [\n \"<string>\"\n ],\n \"external_ids\": [\n \"<string>\"\n ],\n \"text\": \"<string>\",\n \"title\": \"<string>\",\n \"assignee_id\": \"<string>\",\n \"contact_id\": \"<string>\",\n \"company_id\": \"<string>\",\n \"tag_ids\": [\n \"<string>\"\n ],\n \"project_id\": \"<string>\",\n \"snoozed_until\": \"2023-11-07T05:31:56Z\",\n \"closed_loop\": true,\n \"ai_draft_html\": \"<string>\",\n \"ai_draft_sources\": \"<array>\",\n \"clear_ai_draft_error\": true,\n \"notify\": {\n \"slack\": true,\n \"email\": true\n }\n}"
response = http.request(request)
puts response.read_body{
"updated": [
"<string>"
],
"failed": [
{
"id": "<string>",
"code": "<string>",
"message": "<string>"
}
]
}{
"message": "<string>",
"code": "<string>",
"issues": [
{
"message": "<string>"
}
]
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body
Thread ids, at most 100. The whole batch counts as one write against the rate limit.
1 - 100 elementsCaller-owned identifiers. Replaces the full set.
1512UNKNOWN, LOW, MEDIUM, HIGH Workspace user id. Pass null to unassign.
Pass null to detach. Also propagates onto the thread's contact.
Replaces the thread's full tag set.
When snoozed, snoozed_until is required.
open, snoozed, done With status=done, marks the thread as fully closed.
HTML body prefilled into the composer for the next outbound reply. Setting this stamps ai_draft_generated_at and clears ai_draft_started_at + ai_draft_error_at. Pass null (or an empty string) to clear the draft.
Sources the AI consulted while generating the draft. Surfaces in the composer's "Drafted by AI" tooltip. Shape: { type, title, identifier?, url? }[]. Pass null to clear.
When true, clears ai_draft_error_at. Use this to dismiss a stuck error pill in the composer without writing a new draft.
Side-channel notifications to fire alongside the write (Slack and/or email).
Show child attributes
Show child attributes