How to Create a Task with an Attached File
Scope:
disk,tasksWho can execute the method: users with access to the Drive and Task sections
If you are developing integrations for Bitrix24 using AI tools (Codex, Claude Code, Cursor), connect the MCP server so that the assistant uses the official REST documentation.
Bitrix24 has two types of file fields:
- File. This field is not linked to Drive; files are uploaded directly via a Base64 format string
- File (Drive). This field is linked to Drive and stores a Drive object ID. Base64 format is not processed in this field, so the file must first be uploaded to Bitrix24 Drive
To create a task with a file, perform the following two methods sequentially:
- disk.folder.uploadfile — uploads a file to Drive
- tasks.task.add — creates a task
1. Upload a File to Bitrix24 Drive
To upload a file to Drive, use the disk.folder.uploadfile method with the following parameters:
id— specify the value of1739— the Drive folder ID where the file will be uploadeddata— specify the filenameNAME; the file will be saved on Bitrix24 Drive with this namefileContent— pass the file in the format['filename.extension', 'file as a Base64 encoded string']
Uploading a file to Drive is a required step because the UF_TASK_WEBDAV_FILES field in tasks only accepts Drive file IDs.
How to Use Examples in Documentation
import { B24Hook } from '@bitrix24/b24jssdk'
const $b24 = B24Hook.fromWebhookUrl('https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/')
const response = await $b24.actions.v2.call.make({
method: 'disk.folder.uploadfile',
params: {
id: 1739,
data: {
NAME: 'ava555.jpg'
},
fileContent: [
'avatar.jpg',
'/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAQDAwQDAwQEAwQ///+dAYq6YFKoAv/AFnAa6ArKv8AAtFJVppxCEAulxQ2DWgfMR//2Q=='
]
},
requestId: 'disk-uploadfile'
})
if (!response.isSuccess) {
throw new Error(response.getErrorMessages().join('; '))
}
const result = response.getData().result
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
use Symfony\Component\EventDispatcher\EventDispatcher;
$serviceBuilder = (new ServiceBuilderFactory(new EventDispatcher(), $log))
->initFromWebhook('https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/');
$result = $serviceBuilder->getDiskScope()->folder()->uploadFile(
1739,
['NAME' => 'ava555.jpg'],
[
'avatar.jpg',
'/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAQDAwQDAwQEAwQ///+dAYq6YFKoAv/AFnAa6ArKv8AAtFJVppxCEAulxQ2DWgfMR//2Q=='
]
);
echo '<PRE>';
print_r($result->getFile());
echo '</PRE>';
from b24pysdk import BitrixWebhook, Client
client = Client(
BitrixWebhook(
domain="your-domain.bitrix24.com",
webhook_token="user_id/webhook_key",
)
)
result = client.disk.folder.uploadfile(
bitrix_id=1739,
data={
"NAME": "ava555.jpg",
},
file_content=[
"avatar.jpg",
"/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAQDAwQDAwQEAwQ///+dAYq6YFKoAv/AFnAa6ArKv8AAtFJVppxCEAulxQ2DWgfMR//2Q==",
],
).response.result
// fileContent is the file transport in Bitrix24: an array of two elements,
// [file name, content in base64]. The request body is JSON anyway, so a regular
// []string is serialized exactly as the method expects: neither multipart nor manual
// url encoding is needed. Base64 inflates the data by about a third —
// this path is for small files.
res, err := core.Call(ctx, "disk.folder.uploadfile", b24.Params{
"id": folderID,
"data": b24.Params{"NAME": "ava555.jpg"},
"fileContent": []string{"ava555.jpg", base64.StdEncoding.EncodeToString(content)},
// Re-running the example must not fail because of a name collision.
"generateUniqueName": true,
})
if err != nil {
return fmt.Errorf("disk.folder.uploadfile: %w", err)
}
var file struct {
// ID is the ID of the DRIVE OBJECT, and it is exactly what fields of type
// "file (Drive)" accept.
ID b24.ID `json:"ID"`
// FILE_ID is the internal file ID. If you substitute it into a field
// of the task, the file either will not attach or the wrong one will.
FileID b24.ID `json:"FILE_ID"`
Name string `json:"NAME"`
}
if err := json.Unmarshal(res.Result, &file); err != nil {
return fmt.Errorf("parse the uploaded file: %w", err)
}
As a result of uploading the file to Drive, you will receive two different file ID values:
FILE_ID:28073— the internal file ID valueID:6687— the Drive object ID; use this value in methods when working with "File (Drive)" type fields. If you pass the valueFILE_IDin a request to change a "File (Drive)" field, the file will either not be attached to the task because no Drive object exists with that ID, or the wrong file will be attached
{
"result": {
"ID": 6687,
"NAME": "ava555.jpg",
"CODE": null,
"STORAGE_ID": "1",
"TYPE": "file",
"PARENT_ID": "1739",
"DELETED_TYPE": 0,
"GLOBAL_CONTENT_VERSION": 1,
"FILE_ID": 28073,
"SIZE": "405559",
"CREATE_TIME": "2024-11-01T17:00:55+03:00",
"UPDATE_TIME": "2024-11-01T17:00:55+03:00",
"DELETE_TIME": null,
"CREATED_BY": "1",
"UPDATED_BY": "1",
"DELETED_BY": null,
"DOWNLOAD_URL": "https://your-domain.bitrix24.com/rest/download.json?sessid=9dd90ed5a58ccc41af81f5f0043739db&token=disk%7CaWQ9NjY4NyZfPTJ5ZXdvN2Fsb09SMGw1b0FHTkRMSGR5MFJkN1pLTjNS%7CImRvd25sb2FkfGRpc2t8YVdROU5qWTROeVpmUFRKNVpYZHZOMkZzYjA5U01HdzFiMEZIVGtSTVNHUjVNRkprTjFwTFRqTlN8OWRkOTBlZDVhNThjY2M0MWFmODFmNWYwMDQzNzM5ZGIi.Lup1vDbibL6twiCPfCMFnLSoDLleNX0cfMHGv5PFaJw%3D",
"DETAIL_URL": "https://your-domain.bitrix24.com/company/personal/user/1/disk/file/Created files/New folder for process test/ava555.jpg"
}
}
2. Create a Task with a File
To create a task, use the tasks.task.add method with the following parameters:
UF_TASK_WEBDAV_FILES— specify the value ofn6687. This is the file ID from the previous method's result, to which we add a prefixnto upload the file to the fieldTITLE— the task name, a required field. The task will not be created without a nameCREATED_BY— the task creator ID; this field cannot be empty. If left blank, the person sending the request will automatically become the creatorRESPONSIBLE_ID— the task assignee ID, a required field. The task will not be created without an assignee
const response = await $b24.actions.v2.call.make({
method: 'tasks.task.add',
params: {
fields: {
TITLE: 'task for test',
RESPONSIBLE_ID: 1,
UF_TASK_WEBDAV_FILES: [
'n6687'
]
}
},
requestId: 'task-add'
})
if (!response.isSuccess) {
throw new Error(response.getErrorMessages().join('; '))
}
const result = response.getData().result
$result = $serviceBuilder->core->call(
'tasks.task.add',
[
'fields' => [
'TITLE' => 'task for test',
'RESPONSIBLE_ID' => 1,
'UF_TASK_WEBDAV_FILES' => [
'n6687'
]
]
]
)->getResponseData()->getResult();
echo '<PRE>';
print_r($result);
echo '</PRE>';
result = client.tasks.task.add(
fields={
"TITLE": "task for test",
"RESPONSIBLE_ID": 1,
"UF_TASK_WEBDAV_FILES": [
"n6687",
],
}
).response.result
// The "n" prefix before a Drive object ID means "attach exactly
// this existing object". The method will not accept a bare number. The field is always
// an array, even when there is a single file.
res, err = core.Call(ctx, "tasks.task.add", b24.Params{
"fields": b24.Params{
"TITLE": "task for test",
"CREATED_BY": userID,
"RESPONSIBLE_ID": userID,
"UF_TASK_WEBDAV_FILES": []string{"n" + strconv.FormatInt(int64(file.ID), 10)},
},
})
if err != nil {
return fmt.Errorf("tasks.task.add: %w", err)
}
// tasks.* wraps the response in an object with the task key — unlike crm.*.add,
// which responds with a bare ID. And here the ID arrives
// AS A STRING ("3711"): b24.ID parses both spellings, a plain int does not.
var out struct {
Task struct {
ID b24.ID `json:"id"`
Title string `json:"title"`
} `json:"task"`
}
if err := json.Unmarshal(res.Result, &out); err != nil {
return fmt.Errorf("parse the created task: %w", err)
}
We have created a task with ID 3711.
{
"result": {
"task": {
"id": "3711",
"parentId": null,
"title": "task for test",
"description": "",
"mark": null,
"priority": "1",
"multitask": "N",
"notViewed": "N",
"replicate": "N",
"stageId": "0",
"createdBy": "1",
"createdDate": "2024-11-02T10:06:08+02:00",
"responsibleId": "1",
"changedBy": "1",
"changedDate": "2024-11-02T10:06:08+02:00",
"statusChangedBy": null,
"closedBy": null,
"closedDate": null,
"activityDate": "2024-11-02T10:06:08+02:00",
"dateStart": null,
"deadline": null,
"startDatePlan": null,
"endDatePlan": null,
"guid": "{c2794da9-c7fe-404d-a709-ddab4578717a}",
"xmlId": null,
"commentsCount": null,
"serviceCommentsCount": null,
"allowChangeDeadline": "N",
"allowTimeTracking": "N",
"taskControl": "N",
"addInReport": "N",
"forkedByTemplateId": null,
"timeEstimate": "0",
"timeSpentInLogs": null,
"matchWorkTime": "N",
"forumTopicId": null,
"forumId": null,
"siteId": "s1",
"subordinate": "Y",
"exchangeModified": null,
"exchangeId": null,
"outlookVersion": "1",
"viewedDate": null,
"sorting": null,
"durationFact": null,
"isMuted": "N",
"isPinned": "N",
"isPinnedInGroup": "N",
"flowId": null,
"descriptionInBbcode": "Y",
"status": "2",
"statusChangedDate": "2024-11-02T10:06:08+02:00",
"durationPlan": null,
"durationType": "days",
"favorite": "N",
"groupId": "0",
"auditors": [],
"accomplices": [],
"checklist": [],
"group": [],
"creator": {
"id": "1",
"name": "Viola",
"link": "/company/personal/user/1/",
"icon": "https://your-domain.bitrix24.com/b13743910/resize_cache/2267/c0120a8d7c10d63c83e32398d1ec4d9e/main/c7b/c7bd44b1babaa5448125dd97d038ce1b/d5fb56b94dc2c3cd8c006a2c595a4895.jpg",
"workPosition": ""
},
"responsible": {
"id": "1",
"name": "Viola",
"link": "/company/personal/user/1/",
"icon": "https://your-domain.bitrix24.com/b13743910/resize_cache/2267/c0120a8d7c10d63c83e32398d1ec4d9e/main/c7b/c7bd44b1babaa5448125dd97d038ce1b/d5fb56b94dc2c3cd8c006a2c595a4895.jpg",
"workPosition": ""
},
"accomplicesData": [],
"auditorsData": [],
"newCommentsCount": 0,
"action": {
"accept": false,
"decline": false,
"complete": true,
"approve": false,
"disapprove": false,
"start": true,
"pause": false,
"delegate": true,
"remove": true,
"edit": true,
"defer": true,
"renew": false,
"create": true,
"changeDeadline": true,
"checklistAddItems": true,
"addFavorite": true,
"deleteFavorite": false,
"rate": true,
"edit.originator": false,
"checklist.reorder": true,
"elapsedtime.add": true,
"dayplan.timer.toggle": false,
"edit.plan": true,
"checklist.add": true,
"favorite.add": true,
"favorite.delete": false
},
"checkListTree": {
"nodeId": 0,
"fields": {
"id": null,
"copiedId": null,
"entityId": null,
"userId": 1,
"createdBy": null,
"parentId": null,
"title": "",
"sortIndex": null,
"displaySortIndex": "",
"isComplete": false,
"isImportant": false,
"completedCount": 0,
"members": [],
"attachments": []
},
"action": [],
"descendants": []
},
"checkListCanAdd": true
}
}
}
The resulting output does not contain information about the task's files. To verify whether the file was successfully attached to the task, call the tasks.task.get method, specifying the UF_TASK_WEBDAV_FILES field in SELECT.
As a result of tasks.task.get, you will receive the ID of the record representing the attachment of the Drive file to the task — this is the link ID that connects the task and the Drive file. To retrieve information about the file using the link ID, use the disk.attachedObject.get method.
Code Example
import { B24Hook } from '@bitrix24/b24jssdk'
const $b24 = B24Hook.fromWebhookUrl('https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/')
// Function for uploading a file
async function uploadFileToDisk() {
// Folder ID where you want to upload the file
const folderId = 'your_folder_ID';
// File name and its content in Base64 format
const fileName = 'your_file_name';
const fileContentBase64 = 'your_file_content_Base64';
// Calling the disk.folder.uploadfile method
const response = await $b24.actions.v2.call.make({
method: 'disk.folder.uploadfile',
params: {
id: folderId,
data: {
NAME: fileName
},
fileContent: [
fileName,
fileContentBase64
]
},
requestId: 'disk-uploadfile'
});
if (!response.isSuccess) {
console.error('Error while uploading the file:', response.getErrorMessages().join('; '));
return;
}
console.log('File uploaded successfully!', response.getData().result);
const fileId = response.getData().result.ID; // Using the ID from the result
await createTaskWithFile(fileId);
}
// Function for creating a task with an attached file
async function createTaskWithFile(fileId) {
// Task parameters
const taskTitle = 'your_task_name';
const taskDescription = 'your_task_description';
const responsibleId = 'your_responsible_ID';
// Calling the tasks.task.add method
const response = await $b24.actions.v2.call.make({
method: 'tasks.task.add',
params: {
fields: {
TITLE: taskTitle,
DESCRIPTION: taskDescription,
RESPONSIBLE_ID: responsibleId,
UF_TASK_WEBDAV_FILES: ['n' + fileId] // Adding prefix 'n' to the file ID
}
},
requestId: 'task-add'
});
if (!response.isSuccess) {
console.error('Error while creating the task:', response.getErrorMessages().join('; '));
return;
}
console.log('Task created successfully!', response.getData().result);
}
// Calling the function to upload a file and create a task
await uploadFileToDisk();
$b24.destroy();
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
use Bitrix24\SDK\Core\Exceptions\BaseException;
use Symfony\Component\EventDispatcher\EventDispatcher;
$serviceBuilder = (new ServiceBuilderFactory(new EventDispatcher(), $log))
->initFromWebhook('https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/');
// Function for uploading a file
function uploadFileToDisk($serviceBuilder) {
// Folder ID where you want to upload the file
$folderId = 'your_folder_ID';
// File name that you want to upload
$fileName = 'your_file_name';
// Path to the file on your file system
$filePath = '/path/to/your/file';
// Reading the file content and encoding it in Base64
$fileContentBase64 = base64_encode(file_get_contents($filePath));
// Calling the disk.folder.uploadfile method
try {
$result = $serviceBuilder->getDiskScope()->folder()->uploadFile(
(int)$folderId,
['NAME' => $fileName],
[
$fileName,
$fileContentBase64
]
);
} catch (BaseException $e) {
echo 'Error while uploading the file: ' . $e->getMessage();
return;
}
echo 'File uploaded successfully!';
$fileId = $result->getId(); // Using the ID from the result
createTaskWithFile($serviceBuilder, $fileId);
}
// Function for creating a task with an attached file
function createTaskWithFile($serviceBuilder, $fileId) {
// Task parameters
$taskTitle = 'your_task_name';
$taskDescription = 'your_task_description';
$responsibleId = 'your_responsible_ID';
// Calling the tasks.task.add method
try {
$serviceBuilder->core->call(
'tasks.task.add',
[
'fields' => [
'TITLE' => $taskTitle,
'DESCRIPTION' => $taskDescription,
'RESPONSIBLE_ID' => $responsibleId,
'UF_TASK_WEBDAV_FILES' => ['n' . $fileId] // Adding prefix 'n' to the file ID
]
]
);
} catch (BaseException $e) {
echo 'Error while creating the task: ' . $e->getMessage();
return;
}
echo 'Task created successfully!';
}
// Calling the function to upload a file and create a task
uploadFileToDisk($serviceBuilder);
from b24pysdk import BitrixWebhook, Client
from b24pysdk.errors import BitrixAPIError
def upload_file_to_drive(client):
folder_id = "your_folder_ID"
file_name = "your_file_name"
file_content_base64 = "your_file_content_Base64"
try:
result = client.disk.folder.uploadfile(
bitrix_id=folder_id,
data={"NAME": file_name},
file_content=[file_name, file_content_base64],
).response.result
except BitrixAPIError as error:
print(f"File upload error: {error}")
else:
print("File uploaded successfully!")
file_id = result["ID"]
create_task_with_file(client, file_id)
def create_task_with_file(client, file_id):
task_title = "your_task_name"
task_description = "your_task_description"
responsible_id = "your_responsible_ID"
try:
client.tasks.task.add(
fields={
"TITLE": task_title,
"DESCRIPTION": task_description,
"RESPONSIBLE_ID": responsible_id,
"UF_TASK_WEBDAV_FILES": [f"n{file_id}"],
},
).response
except BitrixAPIError as error:
print(f"Task creation error: {error}")
else:
print("Task created successfully!")
client = Client(
BitrixWebhook(
domain="your-domain.bitrix24.com",
webhook_token="user_id/webhook_key",
)
)
upload_file_to_drive(client)
// Setup in an empty directory — go get will not work without go mod init:
//
// go mod init example && go get github.com/bitrix24/b24gosdk
//
// Run:
//
// export B24_WEBHOOK_URL='https://your-portal.bitrix24.com/rest/1/token/' && go run .
//
// The example is self-contained: it finds a folder on Drive itself, uploads a file there,
// creates a task with this file, verifies the attachment, and cleans up after itself.
// It runs on any portal, nothing needs to be edited.
package main
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"os"
"strconv"
b24 "github.com/bitrix24/b24gosdk"
)
func main() {
if err := run(context.Background()); err != nil {
log.Fatal(err)
}
}
func run(ctx context.Context) error {
// The webhook path is a secret, so it comes from the environment, not from the code.
core := b24.NewClient(os.Getenv("B24_WEBHOOK_URL")).Core()
// --- setup: whose Drive and which folder
userID, err := currentUser(ctx, core)
if err != nil {
return err
}
folderID, err := rootFolder(ctx, core, userID)
if err != nil {
return err
}
// --- step 1: upload the file to Drive
content := []byte("Quarterly report.\nCreated by the b24gosdk example.\n")
// fileContent is the file transport in Bitrix24: an array of two elements,
// [file name, content in base64]. The request body is JSON anyway, so a regular
// []string is serialized exactly as the method expects: neither multipart nor manual
// url encoding is needed. Base64 inflates the data by about a third —
// this path is for small files.
res, err := core.Call(ctx, "disk.folder.uploadfile", b24.Params{
"id": folderID,
"data": b24.Params{"NAME": "ava555.jpg"},
"fileContent": []string{"ava555.jpg", base64.StdEncoding.EncodeToString(content)},
// Re-running the example must not fail because of a name collision.
"generateUniqueName": true,
})
if err != nil {
return fmt.Errorf("disk.folder.uploadfile: %w", err)
}
var file struct {
// ID is the ID of the DRIVE OBJECT, and it is exactly what fields of type
// "file (Drive)" accept.
ID b24.ID `json:"ID"`
// FILE_ID is the internal file ID. If you substitute it into a field
// of the task, the file either will not attach or the wrong one will.
FileID b24.ID `json:"FILE_ID"`
Name string `json:"NAME"`
}
if err := json.Unmarshal(res.Result, &file); err != nil {
return fmt.Errorf("parse the uploaded file: %w", err)
}
defer del(ctx, core, "disk.file.delete", b24.Params{"id": file.ID})
fmt.Printf("file %q uploaded: ID=%d, FILE_ID=%d\n", file.Name, file.ID, file.FileID)
// --- step 2: create a task with this file
// The "n" prefix before a Drive object ID means "attach exactly
// this existing object". The method will not accept a bare number. The field is always
// an array, even when there is a single file.
res, err = core.Call(ctx, "tasks.task.add", b24.Params{
"fields": b24.Params{
"TITLE": "task for test",
"CREATED_BY": userID,
"RESPONSIBLE_ID": userID,
"UF_TASK_WEBDAV_FILES": []string{"n" + strconv.FormatInt(int64(file.ID), 10)},
},
})
if err != nil {
return fmt.Errorf("tasks.task.add: %w", err)
}
// tasks.* wraps the response in an object with the task key — unlike crm.*.add,
// which responds with a bare ID. And here the ID arrives
// AS A STRING ("3711"): b24.ID parses both spellings, a plain int does not.
var out struct {
Task struct {
ID b24.ID `json:"id"`
Title string `json:"title"`
} `json:"task"`
}
if err := json.Unmarshal(res.Result, &out); err != nil {
return fmt.Errorf("parse the created task: %w", err)
}
defer del(ctx, core, "tasks.task.delete", b24.Params{"taskId": out.Task.ID})
fmt.Printf("task %d %q created\n", out.Task.ID, out.Task.Title)
// --- check: the file is actually attached
return checkAttachment(ctx, core, out.Task.ID)
}
// --- helpers: data setup, verification, and cleanup
func currentUser(ctx context.Context, core *b24.Core) (b24.ID, error) {
res, err := core.Call(ctx, "user.current", nil, b24.WithIdempotent())
if err != nil {
return 0, fmt.Errorf("user.current: %w", err)
}
var u struct {
ID b24.ID `json:"ID"`
}
if err := json.Unmarshal(res.Result, &u); err != nil {
return 0, err
}
return u.ID, nil
}
// rootFolder returns the root folder of the user's personal storage, and if
// it does not exist — the shared portal storage. The page substitutes a ready-made number here
// for the folder; on someone else's portal such a number does not exist, so the example looks it up.
func rootFolder(ctx context.Context, core *b24.Core, userID b24.ID) (b24.ID, error) {
for _, filter := range []b24.Params{
{"ENTITY_TYPE": "user", "ENTITY_ID": userID},
{"ENTITY_TYPE": "common"},
} {
res, err := core.Call(ctx, "disk.storage.getlist",
b24.Params{"filter": filter}, b24.WithIdempotent())
if err != nil {
return 0, fmt.Errorf("disk.storage.getlist: %w", err)
}
var storages []struct {
Name string `json:"NAME"`
RootObjectID b24.ID `json:"ROOT_OBJECT_ID"`
}
if err := json.Unmarshal(res.Result, &storages); err != nil {
return 0, err
}
for _, s := range storages {
if s.RootObjectID != 0 {
fmt.Printf("storage %q, root folder %d\n", s.Name, s.RootObjectID)
return s.RootObjectID, nil
}
}
}
return 0, fmt.Errorf("the webhook cannot see any storage on Drive")
}
// checkAttachment demonstrates what the page describes: in the tasks.task.add response
// there is no information about the files, it has to be requested separately.
func checkAttachment(ctx context.Context, core *b24.Core, taskID b24.ID) error {
res, err := core.Call(ctx, "tasks.task.get", b24.Params{
"taskId": taskID,
"select": []string{"ID", "TITLE", "UF_TASK_WEBDAV_FILES"},
}, b24.WithIdempotent())
if err != nil {
return fmt.Errorf("tasks.task.get: %w", err)
}
// The portal responds with a different name than the one requested: UF_TASK_WEBDAV_FILES
// arrives as ufTaskWebdavFiles. UnwrapFold compares names ignoring
// case and underscores, so renaming does not break it.
raw, ok := b24.UnwrapFold(res.Result, "task", "UF_TASK_WEBDAV_FILES")
if !ok || b24.IsEmpty(raw) {
return fmt.Errorf("the file was not attached to task %d", taskID)
}
// The value is the list of IDs of the LINK between the task and the Drive file, not
// the IDs of the files themselves.
var attachIDs []b24.ID
if err := json.Unmarshal(raw, &attachIDs); err != nil {
return fmt.Errorf("parse attachments: %w", err)
}
fmt.Printf("attachments attached to the task: %d (link IDs %v)\n",
len(attachIDs), attachIDs)
return nil
}
// del removes what was created. A cleanup error is printed but not returned: it must not
// mask the real error of the scenario.
func del(ctx context.Context, core *b24.Core, method string, params b24.Params) {
if _, err := core.Call(ctx, method, params); err != nil {
fmt.Fprintf(os.Stderr, "cleanup, %s: %v