How to Create a Task with an Attached File

Scope: disk, task

Who can execute the methods: to complete the whole scenario, you need permission to add a file to a Drive folder and create a task

Choose a tool for developing with an AI agent:

  • use Alaio Vibecode to build an app for Bitrix24 from a task description without knowing any programming language. The agent writes the code and deploys the app to a server, with no manual hosting setup
  • use the MCP server to develop a REST API integration in your own project. The agent refers to the official REST documentation

Bitrix24 has two types of file fields:

  • File. This field is not linked to Drive. Files are uploaded directly through a Base64 format string
  • File (Drive). This field is linked to Drive. The field stores the 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 these two methods in sequence:

  1. disk.folder.uploadFile — uploads a file to Drive
  2. tasks.task.add — creates a task

Before You Start

You need the following to run the example:

  • an inbound webhook with the disk and task scopes
  • the Drive folder identifier folderId where the file should be uploaded. You can get a folder using disk.storage.getchildren or disk.folder.getchildren
  • the task assignee identifier RESPONSIBLE_ID
  • the file to attach to the task
  • the file name with an extension, for example ava555.jpg
  • the file content in Base64 format without the data:*/*;base64, prefix

The webhook executes requests with the permissions of the user who created it. Do not publish the webhook secret in client-side code or repositories. Store it in environment variables.

For server-side JS examples with B24Hook, use Node.js 18, 20, 22, or later. For new projects, use 22 or later. B24JsSDK is an ES module: save the code in a .mjs file or add "type": "module" to package.json.

For b24pysdk examples, use Python 3.9 or later.

The Go examples assume that ctx and core are already created, the file has been read into the content variable, folderID and userID are known, and base64, encoding/json, fmt, strconv, and github.com/bitrix24/b24gosdk are imported.

1. Upload the File to Bitrix24 Drive

Use the disk.folder.uploadFile method with the following parameters:

  • id — specify the value 1739, the identifier of the Drive folder where the file is uploaded
  • data — specify the file name in NAME. The file will be saved in Bitrix24 Drive with this name
  • fileContent — pass the file in the format ['file_name.extension', 'file as a Base64-encoded string']

Uploading the file to Drive is required because the UF_TASK_WEBDAV_FILES field in tasks accepts only Drive file IDs.

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 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
        
from b24pysdk import BitrixWebhook, Client
        
        token = BitrixWebhook(
            domain="your-domain.bitrix24.com",
            webhook_token="user_id/webhook_key",
        )
        client = Client(token)
        
        result = client.disk.folder.uploadfile(
            bitrix_id=1739,
            data={
                "NAME": "ava555.jpg",
            },
            file_content=[
                "avatar.jpg",
                "/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAQDAwQDAwQEAwQ///+dAYq6YFKoAv/AFnAa6ArKv8AAtFJVppxCEAulxQ2DWgfMR//2Q==",
            ],
        ).response.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>';
        
// fileContent is the Bitrix24 file transport: an array with two elements,
        // [file name, Base64 content]. The request body is already JSON, so a regular
        // []string is serialized exactly as the method expects: no multipart or manual
        // URL encoding is required. Base64 increases the data size by about one third,
        // so use this path for small files.
        res, err := core.Call(ctx, "disk.folder.uploadFile", b24.Params{
            "id":          folderID,
            "data":        b24.Params{"NAME": "report.txt"},
            "fileContent": []string{"report.txt", base64.StdEncoding.EncodeToString(content)},
            // Rerunning the example should not fail because of matching names.
            "generateUniqueName": true,
        })
        if err != nil {
            return fmt.Errorf("disk.folder.uploadFile: %w", err)
        }
        
        var file struct {
            // ID is the Drive object identifier accepted by fields of the File (Drive) type.
            ID b24.ID `json:"ID"`
            // FILE_ID is the internal file identifier. If it is passed to the task field,
            // the file will either not be attached or a different file will be attached.
            FileID b24.ID `json:"FILE_ID"`
            Name   string `json:"NAME"`
        }
        if err := json.Unmarshal(res.Result, &file); err != nil {
            return fmt.Errorf("decode uploaded file: %w", err)
        }
        

As a result of uploading the file to Drive, you get two different file ID values:

  • FILE_ID: 28073 — the internal file ID
  • ID: 6687 — the Drive object ID. Use this value when working with File (Drive) fields

If you pass FILE_ID instead of ID in a request that updates a File (Drive) field, the file will either not be attached because there is no Drive object with that ID, or a different file will be attached.

{
            "result": {
                "ID": 6687,
                "NAME": "ava555.jpg",
                "TYPE": "file",
                "PARENT_ID": "1739",
                "FILE_ID": 28073,
                "SIZE": "405559"
            }
        }
        

2. Create the Task with the File

Use the tasks.task.add method with the following parameters:

  • UF_TASK_WEBDAV_FILES — specify the value n6687. This is the file ID from the result of the previous method with the n prefix added for uploading the file into the field
  • TITLE — the task title, a required field. The task will not be created without a title
  • RESPONSIBLE_ID — the 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 = client.tasks.task.add(
            fields={
                "TITLE": "task for test",
                "RESPONSIBLE_ID": 1,
                "UF_TASK_WEBDAV_FILES": [
                    "n6687",
                ],
            }
        ).response.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>';
        
// The "n" prefix before the Drive object identifier means "attach this existing
        // object". The method will not accept a bare number. The field is always an
        // array, even when there is only one file.
        res, err = core.Call(ctx, "tasks.task.add", b24.Params{
            "fields": b24.Params{
                "TITLE":                "Task with file (b24gosdk)",
                "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 a task object, unlike crm.*.add, which returns
        // a bare identifier. The identifier is returned as a string ("3711"): b24.ID
        // handles both formats, while a regular 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("decode created task: %w", err)
        }
        

The task is created with ID 3711.

{
            "result": {
                "task": {
                    "id": "3711",
                    "title": "task for test",
                    "responsibleId": "1",
                    "status": "2"
                }
            }
        }
        

The response does not contain task file details. To verify that the file was attached successfully, call the tasks.task.get method with the UF_TASK_WEBDAV_FILES field in SELECT.

The tasks.task.get method returns the ID of the record that represents the link between the Drive file and the task. To get file data by that link ID, use the disk.attachedObject.get method.

Check the Result

const checkResponse = await $b24.actions.v2.call.make({
            method: 'tasks.task.get',
            params: {
                taskId: 3711,
                select: ['ID', 'TITLE', 'UF_TASK_WEBDAV_FILES']
            },
            requestId: 'task-get-check'
        })
        
        if (!checkResponse.isSuccess) {
            throw new Error(checkResponse.getErrorMessages().join('; '))
        }
        
        const task = checkResponse.getData().result.task
        const attachmentId = task.ufTaskWebdavFiles[0]
        
        const fileResponse = await $b24.actions.v2.call.make({
            method: 'disk.attachedObject.get',
            params: {
                id: attachmentId
            },
            requestId: 'disk-attached-object-get'
        })
        
        if (!fileResponse.isSuccess) {
            throw new Error(fileResponse.getErrorMessages().join('; '))
        }
        
        console.log(fileResponse.getData().result)
        
task = client.tasks.task.get(
            bitrix_id=3711,
            select=["ID", "TITLE", "UF_TASK_WEBDAV_FILES"],
        ).response.result["task"]
        
        attachment_id = task["ufTaskWebdavFiles"][0]
        
        file = token.call_method(
            "disk.attachedObject.get",
            {
                "id": attachment_id,
            },
        )["result"]
        
        print(file)
        
$task = $serviceBuilder->core->call(
            'tasks.task.get',
            [
                'taskId' => 3711,
                'select' => ['ID', 'TITLE', 'UF_TASK_WEBDAV_FILES']
            ]
        )->getResponseData()->getResult()['task'];
        
        $attachmentId = $task['ufTaskWebdavFiles'][0];
        
        $file = $serviceBuilder->core->call(
            'disk.attachedObject.get',
            [
                'id' => $attachmentId
            ]
        )->getResponseData()->getResult();
        
        print_r($file);
        
res, err = core.Call(ctx, "tasks.task.get", b24.Params{
            "taskId": out.Task.ID,
            "select": []string{"ID", "TITLE", "UF_TASK_WEBDAV_FILES"},
        })
        if err != nil {
            return fmt.Errorf("tasks.task.get: %w", err)
        }
        
        var taskCheck struct {
            Task struct {
                ID                b24.ID   `json:"id"`
                Title             string   `json:"title"`
                UfTaskWebdavFiles []b24.ID `json:"ufTaskWebdavFiles"`
            } `json:"task"`
        }
        if err := json.Unmarshal(res.Result, &taskCheck); err != nil {
            return fmt.Errorf("decode task: %w", err)
        }
        if len(taskCheck.Task.UfTaskWebdavFiles) == 0 {
            return fmt.Errorf("the task has no attached files")
        }
        
        res, err = core.Call(ctx, "disk.attachedObject.get", b24.Params{
            "id": taskCheck.Task.UfTaskWebdavFiles[0],
        })
        if err != nil {
            return fmt.Errorf("disk.attachedObject.get: %w", err)
        }
        
        var attachment struct {
            ID         b24.ID `json:"ID"`
            ObjectID   b24.ID `json:"OBJECT_ID"`
            EntityType string `json:"ENTITY_TYPE"`
            EntityID   b24.ID `json:"ENTITY_ID"`
            Name       string `json:"NAME"`
        }
        if err := json.Unmarshal(res.Result, &attachment); err != nil {
            return fmt.Errorf("decode attached file: %w", err)
        }
        

The ufTaskWebdavFiles field contains the identifiers of links between the task and Drive files. This is not the file ID itself, but the attachment ID.

{
            "result": {
                "task": {
                    "id": "3711",
                    "title": "task for test",
                    "ufTaskWebdavFiles": [
                        423
                    ]
                }
            }
        }
        

To get file data, pass the value 423 to the id parameter of the disk.attachedObject.get method. The scenario is successful if these response fields are returned:

  • ID — the file attachment identifier
  • OBJECT_ID — the file identifier in Drive
  • ENTITY_TYPE — the object type to which the file is attached. For a task, this value is tasks_task
  • ENTITY_ID — the task identifier
  • NAME — the attached file name

Errors and Diagnostics

If the method returns an error, check the request data.

Error

Cause and solution

ERROR_NOT_FOUND in disk.folder.uploadFile

The folder with the specified id was not found

DISK_BASE_SERVICE_22001

The file name was not passed in data.NAME

ERROR_COULD_NOT_SAVE_FILE

The file could not be saved. Check free space in Drive and the Base64 data

ACCESS_DENIED

The webhook user does not have permission to add the file to the folder

ERROR_CORE in tasks.task.add

Check TITLE, RESPONSIBLE_ID, and required custom task fields

An empty UF_TASK_WEBDAV_FILES value during verification

FILE_ID was passed instead of the Drive object ID, or the n prefix was not added

Repeat the scenario from the step that returned the error. If the file has already been uploaded to Drive, do not upload it again: fix the task parameters and repeat only the tasks.task.add call.

What to Consider

  • In the UF_TASK_WEBDAV_FILES field, pass the Drive object ID from the disk.folder.uploadFile response, not FILE_ID
  • When creating the task, add the n prefix to the Drive object ID, for example n6687
  • The UF_TASK_WEBDAV_FILES field is always passed as an array, even if there is only one file
  • Re-running the example creates a new task and can upload a new file with the same name if unique names are enabled in the upload request

Continue Learning