Batch API Request JavaScript

I’m making my first attempt at using the batch API to submit parallel requests, but it keeps giving me a bad request error. In the code pasted below, the first section works perfectly, but when I try to format it in such a way that the batch API will accept it, something is being done wrong. I’m sure it’s something in that “actions” array that I’m not doing correctly, please advise.

const headers =
  {
    "Accept": "application/json",
    "Authorization": "Bearer " + personalAccessToken
  };

  /** THIS FIRST SECTION IS HOW I WOULD NORMALLY CREATE A NEW TASK AND IT WORKS NO PROBLEM */

    const url1 = baseURL + '/tasks'

    const payload1 =
    {
      name: 'Test Task 1',
      projects: projectGID,
    }

    const options1 =
    {
      method: "POST",
      headers: headers,
      payload: payload1,
      muteHttpExceptions: true
    }

    const resp1 = UrlFetchApp.fetch(url1, options1)

    Logger.log(resp1)

  /** THIS IS MY ATTEMPT AT A BATCH CALL BUT I'M DEFINITELY FORMATTING SOMETHING INCORRECTLY */

    const url2 = baseURL + '/batch'

    const payload2 =
    {
      actions:
      [
        {
          data:
          {
            name: 'Test Task 1',
            projects: projectGID
          },
          method: "post",
          relative_path: "/tasks"
        },
        {
          data:
          {
            name: 'Test Task 2',
            projects: projectGID
          },
          method: "post",
          relative_path: "/tasks"
        }
      ]
    }

    const options2 =
    {
      method: "POST",
      headers: headers,
      payload: payload2,
      muteHttpExceptions: true
    }

    const resp2 = UrlFetchApp.fetch(url2, options2)

    Logger.log(resp2)

Hi,

The doc says Like other POST endpoints, the body should contain a data envelope. Inside this envelope should be a single actions field, containing a list of “action” objects. Each action represents a standard request to an existing endpoint in the Asana API.

and it looks like you did it the other around, placing data wrappers into an actions wrapper.

Does this help?

1 Like

The variable const payload2 is the main data envelope. Inside that envelope there is a single actions field, containing a list of “action” objects, as specified in the doc. Each “action” object has the fields specified in the doc: data, method, and relative_path.

I tried to follow the exact structure specified in the right hand side of this section of the API docs. I’m not sure what I should change in my code, I tried wrapping the actions field in another “data” object, but the error received when I do this is “At least one action must be specified.” Assuming this is happening because the API is reading into that new parent “data” object as if it’s meant to be the actions field and then not detecting an array of objects within, so it stops reading there.

Sorry if I misread the code then. I don’t have other ideas :confused: in cases like this, @Phil_Seeman and I usually recommend going back to a simple Postman request. If it works, then you know the documentation is accurate and you can analyse forward…

@ChandlerCatron,

I think @Bastien_Siebman was correct about the syntax and the data element - if you look at the Batch API sample in the Asana Postman collection, there is a data element at the top level which you’re missing (in addition to the data elements lower down):

{
    "data": {
        "actions": [
            {
                "relative_path": "/tasks/123",
                "method": "get",
                "data": {
                    "assignee": "me",
                    "workspace": "1337"
                },
                "options": {
                    "limit": 50,
                    "offset": 84205823,
                    "fields": [
                        "name",
                        "gid",
                        "notes",
                        "completed"
                    ]
                }
            },
            {
                "relative_path": "/tasks/123",
                "method": "get",
                "data": {
                    "assignee": "me",
                    "workspace": "1337"
                },
                "options": {
                    "limit": 50,
                    "offset": 54713839,
                    "fields": [
                        "name",
                        "gid",
                        "notes",
                        "completed"
                    ]
                }
            }
        ]
    }
}

If I make this edit, my code is as follows:

const headers =
  {
    "Accept": "application/json",
    "Authorization": "Bearer " + personalAccessToken
  };

    const url = baseURL + '/batch'

    const payload =
    {
      "data":
      {
        "actions":
        [
          {
            "relative_path": "/tasks",
            "method": "post",
            "data":
            {
              "name": "Test Task 1",
              "projects": projectGID
            },
            "options":
            {
              "fields":
              [
                "name",
                "gid"
              ]
            }
          },
          {
            "relative_path": "/tasks",
            "method": "post",
            "data":
            {
              "name": "Test Task 2",
              "projects": projectGID
            },
            "options":
            {
              "fields":
              [
                "name",
                "gid"
              ]
            }
          }
        ]
      }
    }

    const options =
    {
      method: "POST",
      headers: headers,
      payload: payload,
      muteHttpExceptions: true
    }

    const resp = UrlFetchApp.fetch(url, options)

    Logger.log(resp)

When I run this code, the error I receive is this:

{
 "errors":
 [
  {
   "message" : "Batch requests must contain at least one action",
   "help" : "For more information on API status codes and how to handle them, read the docs on errors: https://asana.com/developers/documentation/getting-started/errors"
  }
 ]
}

Looks like in addition to adding the data wrapper around the actions field, using JSON.stringify() on my payload object as well as re-defining contentType: “application/json” in the options object did the trick. Both of these edits needed to be made, not completely sure why given these things didn’t need to be done in all my other single API requests, but it works! Thank you both for your help.

Correct code is as follows:

const headers =
  {
    "Accept": "application/json",
    "Authorization": "Bearer " + personalAccessToken
  };

    const url = baseURL + '/batch'

    const payload =
    {
      "data":
      {
        "actions":
        [
          {
            "relative_path": "/tasks",
            "method": "post",
            "data":
            {
              "name": "Test Task 1",
              "projects": projectGID
            },
            "options":
            {
              "fields":
              [
                "name",
                "gid"
              ]
            }
          },
          {
            "relative_path": "/tasks",
            "method": "post",
            "data":
            {
              "name": "Test Task 2",
              "projects": projectGID
            },
            "options":
            {
              "fields":
              [
                "name",
                "gid"
              ]
            }
          }
        ]
      }
    }

    const options =
    {
      method: "POST",
      headers: headers,
      payload: JSON.stringify(payload), //modified
      muteHttpExceptions: true,
      contentType: "application/json" //added
    }

    const resp = UrlFetchApp.fetch(url, options)

    Logger.log(resp)
3 Likes