What does the PUT /d2l/api/le/(version)/(orgUnitId)/content/topics/(topicId)/file want? If a...

Christopher.M.74 Posts: 6 🌱
edited November 2022 in Development

What does the PUT /d2l/api/le/(version)/(orgUnitId)/content/topics/(topicId)/file want? If a working example is available it would be very help, but I currently have no idea what to actually provide this route.

I am building an app using react and the "superagent-d2l-session-auth" npm library


the route method looks like this (feel free to take it)


----- (Typescript)


  callPutRoute = (apiData:any, orgUnitId:string, topicId:string, fileString:string) => {

    return new Promise((resolve, reject) => {

       var request = require('superagent'),

       auth = require('superagent-d2l-session-auth')();


      let route = this.routeTemplate.replace("[API_VER]", apiData.LE_version)

      route = route.replace("[ORG_UNIT_ID]", orgUnitId)

      route = route.replace("[TOPIC_ID]", topicId)


      let reqData = fileString // lets assume this is a bunch of html

      // is it like widget data where I need this format "{Data:fileString}"?

      // do I use on the the templates here? https://docs.valence.desire2learn.com/basic/fileupload.html#simple-uploads





        .set('Content-Type', 'multipart/mixed;boundary=xxBOUNDARYxx')  // is this required?


        .end((err:any, response:any) => {

          if (err !== null) {

            console.error("PUT replaceTopicFile call failed");


            return reject(err);


          console.log("PUT replaceTopicFile call success");










  • Viktor.H.147
    Viktor.H.147 Posts: 41
    edited November 2022

    This route uses the simple file upload process (https://docs.valence.desire2learn.com/basic/fileupload.html#simple-uploads); there are a number of examples in the docs in the linked topic above and while none of them exactly use the API route you indicate, they can all act as a guide for what the request should look like.


    A good example of providing a binary file data to Brightspace this way is the "updating the profile image" example. In the Content-Disposition part header, the API you're indicating wants you to provide "file" for the value of the name parameter (where the profile image route uses the value "profileImage").


    Note that the simple file upload pattern does not use an overall "multipart/mixed" payload; it uses the "multipart/form-data" content type.

  • Christopher.M.74
    Christopher.M.74 Posts: 6 🌱
    edited November 2022

    perhaps I need more details in this question. I've tried several of the templates mentioned in the documentation.

    what I am usually getting is a 400 error indicating I am missing a detail in the request.


    (side note, the error is linking me here http://docs.valence.desire2learn.com/res/apiprop.html#invalid-parameters, where the "invalid-parameters" anchor doesn't seem exist)


    Here's a full example of the request as it appear in chrome.




    :method: PUT

    :path: /d2l/api/le/1.41/308299/content/topics/12025662/file


    content-length: 124

    content-type: multipart/form-data; boundary=xxBOUNDARYxx


    x-csrf-token: (let's just assume this is right)


    ---- PAYLOAD ----



    Content-Disposition: form-data; name='file' 

    Content-Type: text/html


    this is some html



    ---- RESPONSE ----




      "title":"Invalid Parameters",


      "detail":"Request has missing or invalid parameters."



  • Viktor.H.147
    Viktor.H.147 Posts: 41
    edited November 2022

    Hmmm. It's possible that your part content disposition header also requires a "filename" parameter, which you're not providing, but I'm unsure that's the problem.

  • Bill.C.651
    Bill.C.651 Posts: 6
    edited November 2022

    Below is a working example of a PowerShell snippet I use to upload files.



    param ( $instance, $token, $Course, $FilePath )


    $fileName = Split-Path -Path $($FilePath) -Leaf

    $fileBin = [IO.File]::ReadAllBytes($FilePath)

    $enc = [System.Text.Encoding]::GetEncoding("iso-8859-1")

    $fileEnc = $enc.GetString($fileBin)


    $LF = "`r`n"


    $Pbody = ( 

    "Content-Type: multipart/form-data; boundary=xxBOUNDARYxx",

    "Content-Length: $($fileEnc.Length)$LF",



    "Content-Disposition: form-data; name=`"Image`"; filename=`"$fileName`"",

    "Content-Type: application/octet-stream$LF",



    ) -join $LF


    $header = @{Accept = "*/*"; 'Content-Type' = "multipart/form-data; boundary=xxBOUNDARYxx"; Authorization = "Bearer $($token.access_token)"}

    # Note: File is uploaded to "Course Offering Files" location


    $endPoint = "/courses/$($Course.Identifier)/image" 

    $uri = "https://" + $instance.uri + "/d2l/api/lp/" + $API_ver_lp + $endPoint


    Invoke-RestMethod -Method PUT -Uri $uri -Headers $header -Body $Pbody



    Hope this helps,

    Good luck

  • Christopher.M.74
    Christopher.M.74 Posts: 6 🌱
    edited November 2022


    When I originally changed header to "multipart/form-data" as Viktor suggested, I had apparently also tossed a ";" after the boundary causing the 400 errors.


    for posterity this worked (at least for txt and html)


        var xhr = new XMLHttpRequest();

        xhr.open("PUT", route, true);

        xhr.setRequestHeader("X-Csrf-Token", localStorage["XSRF.Token"]);






        var body = `--xxBOUNDARYxx`;

        body += `Content-Disposition:form-data;name="file";filename="${fileName}"`;

        body += `Content-Type:text/html; charset="UTF-8"`;

        body += `${htmlBody}`;

        body += `--xxBOUNDARYxx--`;



    Thanks for you help.

  • Bill.C.651
    Bill.C.651 Posts: 6
    edited November 2022