Google Apps Script PUT API call

Summary

This incident centers on a Google Apps Script PUT request failing with 400/500 errors when calling the Teamhub API. The GET request works, but the PUT request—intended to create a task—fails because Apps Script’s UrlFetchApp uses payload differently than fetch, and the API expects a raw JSON body, not form-encoded data.

Root Cause

The failure is caused by sending the request body incorrectly. In Google Apps Script:

  • payload: does NOT send raw JSON
  • payload: forces Apps Script to send form-encoded data, unless you explicitly use contentType and method: 'post'
  • For raw JSON, Apps Script requires the field "payload" to be replaced with "body"

Because of this mismatch:

  • The API receives no valid JSON body
  • The API returns 400 (bad request) or 500 (server error) depending on how it parses the malformed body

Why This Happens in Real Systems

Real APIs often:

  • Expect strict JSON formatting
  • Reject requests where the body is form-encoded instead of JSON
  • Fail with 500 when the backend attempts to parse invalid or empty JSON
  • Treat PUT/POST differently depending on the framework

Google Apps Script is unusual because:

  • payload defaults to form-data
  • body is required for raw JSON
  • Many developers assume it behaves like fetch(), but it does not

Real-World Impact

Incorrect request-body handling leads to:

  • Silent data loss (API receives empty body)
  • 400 errors when validation fails
  • 500 errors when the backend crashes parsing malformed input
  • Hours of debugging because Apps Script hides the distinction between payload and body

Example or Code (if necessary and relevant)

Below is the correct Apps Script version using body instead of payload:

function submitTicket(e) {
  var projectId = e.formInput.selected_project_id;
  var mailData = readEmail(e);

  const url = "https://api.teamhub.com/api/v1/integration/project/" + projectId + "/tasks";

  const payload = {
    title: mailData.subject,
    content: mailData.body,
    stage: "nieuw",
    createdAt: getCurrentTimeISO(),
    createdById: "changedForPost"
  };

  const options = {
    method: "put",
    contentType: "application/json",
    headers: {
      Authorization: "Bearer " + API_KEY,
      Accept: "*/*"
    },
    body: JSON.stringify(payload),
    muteHttpExceptions: true
  };

  const response = UrlFetchApp.fetch(url, options);
  Logger.log(response.getContentText());
}

How Senior Engineers Fix It

Experienced engineers recognize the mismatch immediately and apply fixes such as:

  • Switching from payload to body for raw JSON
  • Setting contentType: 'application/json'
  • Ensuring the HTTP verb matches the API spec
  • Logging the exact request body to confirm what is being sent
  • Testing with cURL or Postman to isolate whether the issue is client-side or server-side

Why Juniors Miss It

Junior developers often overlook this because:

  • Apps Script’s payload behaves differently from fetch()
  • The documentation is confusing and inconsistent
  • They assume PUT/POST behave the same across environments
  • They trust that JSON.stringify(payload) inside payload: sends JSON (it does not)
  • They do not inspect the actual wire-level request

The result is a classic case of API mismatch caused by platform-specific behavior, something seniors learn to spot quickly.

Leave a Comment