400 Bad Request on Content File/Topic

Jesse.H.466
Jesse.H.466 Posts: 2 New Community Member

I'm a D2L LMS Admin working on a custom tool for a page/widget, and I've hit a persistent roadblock with the Content API that I'm hoping someone can shed some light on.

The Goal

I'm trying to create a feature within a custom HTML/JavaScript tool that generates a CSV file on the client side and automatically uploads it as a new, hidden topic into a specific content module.

The Problem & Evidence from Postman

Every attempt to upload the file fails with a 400 Bad Request and the detail: "Request has missing or invalid parameters."

The most important finding is that this error is reproducible in Postman.

My Postman setup is as follows:

  • Authentication: I am using my active browser's session Cookie and X-CSRF-TOKEN in the request headers. This method works for all my other API calls, confirming authentication is succeeding.
  • Method: POST
  • URL: https://[my-lms-domain]/d2l/api/le/1.50/[orgUnitId]/content/modules/[moduleId]/structure/
  • Body (form-data): A request with a file part (containing a test_names.csv) and a topicData part (containing the required JSON string).

Here is a screenshot of my Postman setup and the response:

d2l-api-content-post-postman-screenshot.png image.png

My Method

Below is the JavaScript code from my widget. This is the exact logic I replicated in my failing Postman test.

// Assume orgUnitId, moduleId, csvBlob, and csvFileName are already defined

const token = localStorage.getItem("XSRF.Token");
const formData = new FormData();

const topicData = {
"Title": "DRAR Test CSV File",
"ShortTitle": "DRAR Test CSV",
"Type": 1,
"Url": csvFileName, // Matching the filename of the blob
"IsHidden": true,
"IsLocked": false,
};

// Add the file part and the JSON metadata part
formData.append('file', csvBlob, csvFileName);
formData.append('topicData', JSON.stringify(topicData));

const apiUrl = /d2l/api/le/1.50/${orgUnitId}/content/modules/${moduleId}/structure/;

const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': token,
'Accept': 'application/json'
},
credentials: 'include', // Automatically handle session cookies
body: formData
});

What I've Already Tried

I've spent a significant amount of time trying to resolve this and have attempted the following:

  • Reordering the formData parts (file first vs. topicData first).
  • Removing the Url property from the topicData JSON.
  • Sending the topicData as a Blob instead of a string.
  • Manually adding the Cookie header instead of using credentials: 'include'.
  • Confirming that the orgUnitId and moduleId are correct and that I have the necessary permissions (I can manually upload to the target module).

My Question for the Community

Given that this standard request fails even in a controlled tool like Postman, can anyone from D2L or the community identify what might be wrong? Is there a hidden permission, an undocumented required header, or a known issue with this endpoint that would cause this behavior?

Tagged: