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 JSONpayload:forces Apps Script to send form-encoded data, unless you explicitly usecontentTypeandmethod: '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:
payloaddefaults to form-databodyis 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
payloadandbody
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
payloadtobodyfor 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
payloadbehaves differently fromfetch() - The documentation is confusing and inconsistent
- They assume PUT/POST behave the same across environments
- They trust that
JSON.stringify(payload)insidepayload: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.