Hi hanks83,
In my experience building private apps for HubSpot, the 400 error you’re encountering is usually related to how the payload is being sent. The code structure looks solid, but there’s a subtle issue with how hubspot.fetch handles the body parameter.
The Problem
When using hubspot.fetch from the HubSpot SDK, you’re correct that it automatically handles JSON.stringify, but you need to ensure you’re passing the body correctly. The 400 error typically indicates the API is rejecting the request format.
Here’s what’s likely causing the issue
Your percentage_complete property might have formatting requirements. Number properties in HubSpot often need to be sent as strings, but without special formatting like .toFixed() applied to the raw value before conversion.
Try This Solution
const valueToSend = “0.50”;
const url = `https://api.hubspot.com/crm/v3/objects/deals/${dealId}\`;
const payload = {
properties: {
percentage_complete: valueToSend
}
};
const res = await hubspot.fetch(url, {
method: “PATCH”,
headers: { “Content-Type”: “application/json” },
body: JSON.stringify(payload)
});
Alternative Approach
If the above doesn’t work, the property name might need exact casing. HubSpot property names are case-sensitive and use specific internal names. Check your property settings in HubSpot to confirm the exact internal name. It might be percentageComplete or percentage_complete.
Debugging Steps
1. Log the exact error response to see what HubSpot is rejecting
2. Verify the property exists and is editable via the API
3. Check that your private app has the correct write scopes for deals
4. Try sending the value as a number directly without toFixed: valueToSend = 0.50
Common Gotcha
If you created this as a custom property, make sure it’s set to “Number” type in HubSpot property settings, not “Text”. This affects how the API expects the value.
Let me know what error details you’re seeing and I can help troubleshoot further.