How to Move an Activity from One Object Type to Another
Scope:
crmWho can execute the method: users with permission to edit CRM items
If you are developing integrations for Bitrix24 using AI tools (Codex, Claude Code, Cursor), connect to the MCP server so that the assistant can utilize the official REST documentation.
Activities associated with CRM items are stored in the item card's Timeline. Moving activities may be required between items of different types: lead, deal, contact, company, invoice, SPA. For example, a customer has two e-mail addresses, but only one is saved in your Bitrix24 company card. When the customer writes an e-mail from the second, unknown address, Webmail will create a new lead instead of attaching the e-mail to the existing company card. To store customer information in one place, you can move an activity from a lead to a company card.
Moving between different object types consists of two operations: first, add the activity link to the new object, then delete the link to the old one. As a result of the scenario, the activity will appear in the company Timeline and disappear from the lead Timeline.
The crm.activity.binding.move method is not suitable here: it only moves an activity between items of the same type. If the types are different, the method will return error SOURCE_AND_TARGET_ENTITY_TYPES_ARE_NOT_EQUAL_ERROR. To move an activity between two leads or two deals, use the How to Move an Activity Between Items of the Same Type scenario.
To move an activity, we will sequentially execute four methods:
-
crm.activity.list — retrieve the activity ID
-
crm.company.list — retrieve the company ID to which the activity will be moved
-
crm.activity.binding.add — add the activity link to the company
-
crm.activity.binding.delete — delete the activity link to the lead
The order of steps 3 and 4 cannot be changed. If you delete the link to the lead first, the activity will be left without its only link, and the method will return error LAST_BINDING_CANNOT_BE_DELETED.
1. Retrieve the Activity ID
Use the crm.activity.list method with a filter:
-
OWNER_TYPE_ID— object type, specify1for a lead, -
OWNER_ID— the ID of the item from which the activity will be moved.
In the example, we move an activity from lead 1000977. Lead ID is visible in the address bar of its card, for example /crm/lead/details/1000977/, or it can be retrieved using the crm.lead.list method.
Without the select parameter, the method returns all activity fields. To reduce the response size, specify only the fields required for the scenario: ID, OWNER_TYPE_ID, OWNER_ID, SUBJECT, and DESCRIPTION.
How to Use Examples in Documentation
import { B24Hook } from '@bitrix24/b24jssdk'
const $b24 = B24Hook.fromWebhookUrl(process.env.B24_HOOK)
// B24_HOOK = 'https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/'
const result = await $b24.actions.v2.call.make({
method: "crm.activity.list",
params: {
filter:
{
"OWNER_TYPE_ID": 1,
"OWNER_ID": 1000977
},
select: [ "ID", "OWNER_TYPE_ID", "OWNER_ID", "SUBJECT", "DESCRIPTION" ]
}
});
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$logger = new Logger('b24');
$logger->pushHandler(new StreamHandler('php://stdout'));
$serviceBuilder = (new ServiceBuilderFactory(new EventDispatcher(), $logger))
->initFromWebhook(getenv('B24_HOOK'));
// B24_HOOK = 'https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/'
$activities = $serviceBuilder->getCRMScope()->activity()->list(
[],
[
'OWNER_TYPE_ID' => 1,
'OWNER_ID' => 1000977,
],
[
'ID', 'OWNER_TYPE_ID', 'OWNER_ID', 'SUBJECT', 'DESCRIPTION'
],
0
)->getActivities();
import os
from b24pysdk import BitrixWebhook, Client
client = Client(
BitrixWebhook(
domain="your-domain.bitrix24.com",
webhook_token=os.environ["B24_HOOK_TOKEN"],
)
)
# B24_HOOK_TOKEN = 'user_id/webhook_key'
result = client.crm.activity.list(
filter={
"OWNER_TYPE_ID": 1,
"OWNER_ID": 1000977,
},
select=["ID", "OWNER_TYPE_ID", "OWNER_ID", "SUBJECT", "DESCRIPTION"],
).response.result
As a result, we will retrieve all activities associated with the specified item.
{
"result": [
{
"ID": "7685",
"OWNER_TYPE_ID": "1",
"OWNER_ID": "1000977",
"SUBJECT": "for leads",
"DESCRIPTION": "<div>first email</div>\r\n"
}
],
"total": 1
}
We will save the activity ID: 7685. We will pass this value into the parameter activityId in steps 3 and 4.
2. Retrieve the Company ID
Use the crm.company.list method with a filter:
TITLE— the company name.
To limit the returned fields, add the select parameter and specify only the ID and TITLE fields.
const result = await $b24.actions.v2.call.make({
method: "crm.company.list",
params: {
filter: { "TITLE": "Company_Name" },
select: [ "ID", "TITLE" ]
}
});
$companies = $serviceBuilder->getCRMScope()->company()->list(
[],
[
'TITLE' => 'Company_Name'
],
[
'ID', 'TITLE'
],
0
)->getCompanies();
result = client.crm.company.list(
filter={
"TITLE": "Company_Name",
},
select=["ID", "TITLE"],
).response.result
As a result, you will obtain the company ID — ID: 173. We will pass this value into the entityId parameter in step 3.
{
"result": [
{
"ID": "173",
"TITLE": "Company_Name"
}
],
"total": 1
}
3. Add the Activity Link to the Company
To link the activity and the company, use the crm.activity.binding.add method with the following parameters:
-
activityId— the activity ID obtained in step 1 using the crm.activity.list method, -
entityTypeId— the object type ID; specify4for the company, -
entityId— the company ID obtained in step 2 using the crm.company.list method.
const result = await $b24.actions.v2.call.make({
method: 'crm.activity.binding.add',
params: {
activityId: 7685,
entityTypeId: 4,
entityId: 173
}
});
// crm.activity.binding.add does not have a typed wrapper — calling via core
$result = $serviceBuilder->core->call(
'crm.activity.binding.add',
[
'activityId' => 7685,
'entityTypeId' => 4,
'entityId' => 173
]
);
result = client.crm.activity.binding.add(
activity_id=7685,
entity_type_id=4,
entity_id=173,
).response.result
As a result, you will receive true, indicating that the activity link was added successfully. The activity is now linked to two elements simultaneously — the lead and the company.
{
"result": true
}
4. Delete the Activity Link to the Lead
Use the crm.activity.binding.delete method with the following parameters:
-
activityId— the activity ID obtained in step 1 using the crm.activity.list method, -
entityTypeId— the object type ID; specify1for the lead, -
entityId— the lead ID from which the activity is being removed.
const result = await $b24.actions.v2.call.make({
method: 'crm.activity.binding.delete',
params: {
activityId: 7685,
entityTypeId: 1,
entityId: 1000977
}
});
// crm.activity.binding.delete does not have a typed wrapper — calling via core
$result = $serviceBuilder->core->call(
'crm.activity.binding.delete',
[
'activityId' => 7685,
'entityTypeId' => 1,
'entityId' => 1000977
]
);
result = client.crm.activity.binding.delete(
activity_id=7685,
entity_type_id=1,
entity_id=1000977,
).response.result
As a result, you will receive true, indicating that the activity link to the lead was deleted successfully. The transfer is complete: the activity now has only one link — to the company.
{
"result": true
}
Code Example
import { B24Hook } from '@bitrix24/b24jssdk'
import { createInterface } from 'node:readline/promises'
const $b24 = B24Hook.fromWebhookUrl(process.env.B24_HOOK)
// B24_HOOK = 'https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/'
async function call(method, params) {
const result = await $b24.actions.v2.call.make({ method, params });
if (!result.isSuccess) {
throw new Error(result.getErrorMessages().join('; '));
}
return result.getData().result;
}
// Function to execute all steps
async function transferActivityToCompany(leadId, companyName) {
// Step 1: Get the list of tasks for the specified lead
const activities = await call("crm.activity.list", {
filter: {
"OWNER_TYPE_ID": 1,
"OWNER_ID": leadId
},
select: [ "ID", "OWNER_TYPE_ID", "OWNER_ID", "SUBJECT", "DESCRIPTION" ]
});
if (activities.length === 0) {
console.log("Tasks for the specified lead were not found.");
return;
}
const activityId = activities[0].ID;
// Step 2: Search for the company by name
const companies = await call("crm.company.list", {
filter: { "TITLE": companyName },
select: [ "ID", "TITLE" ]
});
if (companies.length === 0) {
console.log("Company with the specified name was not found.");
return;
}
const companyId = companies[0].ID;
// Step 3: Create a link for the found task and company
await call('crm.activity.binding.add', {
activityId: activityId,
entityTypeId: 4,
entityId: companyId
});
console.log("Link between task and company successfully created.");
// Step 4: Delete the link between task and lead
await call('crm.activity.binding.delete', {
activityId: activityId,
entityTypeId: 1,
entityId: leadId
});
console.log("Link between task and lead successfully deleted.");
}
// Request lead ID and company name from the user
const rl = createInterface({ input: process.stdin, output: process.stdout });
const leadId = await rl.question("Enter lead ID: ");
const companyName = await rl.question("Enter company name: ");
rl.close();
// Run function
try {
await transferActivityToCompany(leadId, companyName);
} catch (error) {
console.error(error.message);
}
<?php
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$logger = new Logger('b24');
$logger->pushHandler(new StreamHandler('php://stdout'));
$serviceBuilder = (new ServiceBuilderFactory(new EventDispatcher(), $logger))
->initFromWebhook(getenv('B24_HOOK'));
// B24_HOOK = 'https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/'
// Function to execute all steps
function transferActivityToCompany($serviceBuilder, $leadId, $companyName) {
$crm = $serviceBuilder->getCRMScope();
try {
// Step 1: Get the list of tasks for the specified lead
$activities = $crm->activity()->list(
[],
[
'OWNER_TYPE_ID' => 1,
'OWNER_ID' => $leadId
],
[
'ID', 'OWNER_TYPE_ID', 'OWNER_ID', 'SUBJECT', 'DESCRIPTION'
],
0
)->getActivities();
if (empty($activities)) {
echo "Tasks for the specified lead were not found.";
return;
}
$activityId = $activities[0]->ID;
// Step 2: Search for the company by name
$companies = $crm->company()->list(
[],
['TITLE' => $companyName],
['ID', 'TITLE'],
0
)->getCompanies();
if (empty($companies)) {
echo "Company with the specified name was not found.";
return;
}
$companyId = $companies[0]->ID;
// Step 3: Create a link for the found task and company
// crm.activity.binding.add does not have a typed wrapper — calling via core
$serviceBuilder->core->call(
'crm.activity.binding.add',
[
'activityId' => $activityId,
'entityTypeId' => 4,
'entityId' => $companyId
]
);
echo "Link between task and company successfully created.";
// Step 4: Delete the link between task and lead
// crm.activity.binding.delete does not have a typed wrapper — calling via core
$serviceBuilder->core->call(
'crm.activity.binding.delete',
[
'activityId' => $activityId,
'entityTypeId' => 1,
'entityId' => $leadId
]
);
echo "Link between task and lead successfully deleted.";
} catch (\Throwable $e) {
echo 'Error: ' . $e->getMessage();
}
}
// Request lead ID and company name from the user
$leadId = readline("Enter lead ID: ");
$companyName = readline("Enter company name: ");
// Run function
transferActivityToCompany($serviceBuilder, $leadId, $companyName);
import os
from b24pysdk import BitrixWebhook, Client
from b24pysdk.errors import BitrixAPIError
def transfer_activity_to_company(client, lead_id, company_name):
try:
activity_result = client.crm.activity.list(
filter={
"OWNER_TYPE_ID": 1,
"OWNER_ID": lead_id,
},
select=["ID", "OWNER_TYPE_ID", "OWNER_ID", "SUBJECT", "DESCRIPTION"],
).response.result
except BitrixAPIError as error:
print(f"Error: {error}")
return
if not activity_result:
print("Tasks for the specified lead were not found.")
return
activity_id = activity_result[0]["ID"]
try:
company_result = client.crm.company.list(
filter={"TITLE": company_name},
select=["ID", "TITLE"],
).response.result
except BitrixAPIError as error:
print(f"Error: {error}")
return
if not company_result:
print("Company with the specified name was not found.")
return
company_id = company_result[0]["ID"]
try:
add_result = client.crm.activity.binding.add(
activity_id=activity_id,
entity_type_id=4,
entity_id=company_id,
).response.result
except BitrixAPIError as error:
print(f"Error: {error}")
return
if not add_result:
return
print("Link between task and company successfully created.")
try:
delete_result = client.crm.activity.binding.delete(
activity_id=activity_id,
entity_type_id=1,
entity_id=lead_id,
).response.result
except BitrixAPIError as error:
print(f"Error: {error}")
else:
if delete_result:
print("Link between task and lead successfully deleted.")
client = Client(
BitrixWebhook(
domain="your-domain.bitrix24.com",
webhook_token=os.environ["B24_HOOK_TOKEN"],
)
)
# B24_HOOK_TOKEN = 'user_id/webhook_key'
lead_id = int(input("Enter lead ID: "))
company_name = input("Enter company name: ")
transfer_activity_to_company(client, lead_id, company_name)
Verify the Result
Open the company card — the transferred e-mail will appear in the Timeline. The activity will no longer be in the lead card: the scenario transfers the link rather than copying it.
You can verify the result via REST using the crm.activity.binding.list method. Pass the activityId of the transferred activity to it — the method will return all activity links. After a successful transfer, only one link should remain in the response: the object type 4 and the company ID. The link to the lead, object type 1, should not be in the response.
{
"result": [
{
"entityTypeId": 4,
"entityId": 173
}
]
}
If both links remain in the response, step 4 was not executed — repeat it. If the link to the company does not appear, return to step 3.
Errors and Diagnostics
If the method returns an error, check the request data.
|
Code |
Reason and action |
|
|
You are deleting the only connection of the activity. First, perform step 3 and link the activity to a company, only then delete the connection with the lead |
|
|
The activity is already linked to a company. Step 3 is completed, proceed to step 4 |
|
|
The activity is not linked to a lead from |
|
|
Activity or CRM item not found. Check |
|
|
Activity owner not found. Check |
|
|
The user does not have permission to modify CRM items |
|
|
Mandatory parameters were not passed. Methods |
Key Considerations
- Activities between items of the same type are moved using a single method crm.activity.binding.move; a two-step scenario is not required for this
- The order of steps 3 and 4 cannot be changed: an activity must always retain at least one link
- Between steps 3 and 4, the activity is visible in the timeline of both items — both the lead and the company
- The custom activity fields
OWNER_TYPE_IDandOWNER_IDswitch to the company only after step 4, when the activity has only one link remaining. As long as there are two links, the lead remains the owner. After step 4, crm.activity.list with a filter by lead will no longer return the moved activity; look for it by company withOWNER_TYPE_IDequal to4 - The company in the scenario is only an example of a target object. To move an activity to a deal, find it using the crm.deal.list method and pass
2intoentityTypeIdof step 3. Values for other types can be found in the object types reference - The crm.company.list method by filter
TITLEmay return several companies with the same name; verify that you have selected the correct company - Rerunning the example on the same lead will not find the already moved activity: the link to the lead no longer exists, and the example will terminate with a message stating that no activities were found
Continue Learning
- Add a deal binding to a CRM object crm.activity.binding.add
- Delete the deal's connection with the CRM object crm.activity.binding.delete
- Get a List of All Deal Links crm.activity.binding.list
- Get List of Activities crm.activity.list
- How to Move an Activity Between Items of the Same Type
- How to Reschedule a Planned Activity