I’m trying to create a document attachment in Business Central using this API:
However, I always get the following error:
Read called with an open stream or textreader. Please close any open streams or text readers before calling Read. CorrelationId: bee212fd-2651-45e1-b57c-84926816a7ea
I’ve already tried several variations of base64 encoding, but no luck so far. Here's the current version of my function (written in vue3, using Axios):
const attachFileToBCModule = async (payload) => {
const { module, recordId, file, config } = payload;
const attachmentEndpoint = replaceUrlPlaceholders(attachmentEndpointFormat, config);
try {
const token = await acquireToken('businesscentral');
// Process file completely before making API call
const fileContent = await file.arrayBuffer();
const uint8Array = new Uint8Array(fileContent);
const binaryString = Array.from(uint8Array, byte => String.fromCharCode(byte)).join('');
const base64Content = btoa(binaryString);
const fileName = file.name;
const fileSize = file.size;
const uploadUrl2 = `${attachmentEndpoint}/documentAttachments`;
const uploadResponse2 = await axios.post(
uploadUrl2,
{
fileName: fileName,
byteSize: fileSize,
attachmentContent: base64Content,
parentId: recordId,
parentType: 'Sales Order',
},
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
}
);
return {
status: 201,
message: `Attachment uploaded to ${module} successfully.`,
data: { upload2: uploadResponse2.data },
};
} catch (err) {
console.error('Error uploading attachment:', err?.response?.data || err.message);
return {
status: err.response?.status || 400,
error: err.response?.data?.error?.message || err.response?.data || 'Attachment upload failed',
};
}
};
For reference, I also tried the other endpoint:
That one works fine—but it uploads the file to Incoming Documents, not the Attachments panel in the Sales Order. From what I understand, the correct endpoint is /documentAttachments.
I found a solution!
To resolve the issue, I created a custom API for Document Attachment in Business Central.
Here’s what I did:
Go to the Web Services module in Business Central.
Add a new Web Service:
Object Type: Page
Object ID: 30080
(this is the ID of the Document Attachment
page)
Service Name: DocumentAttachments
(or any name you prefer for the endpoint)
After publishing this, you can now consume the API using a POST and PATCH request. Here’s the revised code I used:
const attachV2DocumentAttachments = async (payload) => {
const { module, createdOrder, documentFile, config } = payload
const v2DocumentAttachmentsEndpoint = replaceUrlPlaceholders(v2DocumentAttachmentsEndpointFormat, config)
const attachmentUrl = `${v2DocumentAttachmentsEndpoint}`
try {
const token = await acquireToken('businesscentral')
// Step 1: Create attachment record with metadata only
const attachmentRecord = {
parentId: createdOrder.id,
fileName: documentFile.name,
parentType: 'Sales Order'
}
console.log('Creating attachment record:', attachmentRecord)
const createResponse = await axios.post(
attachmentUrl,
attachmentRecord,
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
}
)
console.log('createResponse', createResponse)
const attachmentId = createResponse.data.id
console.log('Attachment record created with ID:', attachmentId)
// Step 2: Upload the actual file content
const v2DocumentAttachmentsEndpoint = replaceUrlPlaceholders(v2DocumentAttachmentsEndpointFormat, config)
const v2DocumentAttachmentsURL = `${v2DocumentAttachmentsEndpoint}(${attachmentId})/attachmentContent`
const uploadResponse = await axios.patch(
v2DocumentAttachmentsURL,
documentFile,
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': documentFile.type || 'application/octet-stream',
'If-Match': '*'
},
}
)
return {
status: 201,
message: `Attachment uploaded to ${module} successfully.`,
data: {
attachment: createResponse.data,
upload: uploadResponse.data
},
}
} catch (err) {
console.error('Error uploading attachment:', err?.response?.data || err.message)
return {
status: err.response?.status || 400,
error: err.response?.data?.error?.message || err.response?.data || 'Attachment upload failed',
}
}
}