HubSpot API

HubSpot Flows v4 API Errors: Working Action IDs and 400/500 Fixes

Fabio Basone
Fabio Basone
-

If you're scripting HubSpot workflows via POST /automation/v4/flows and you've hit a 500 that looks like a server problem, a 400 about [type] that looks like a missing property, or a 400 on IS_NOT_EQUAL_TO that reads like an operator typo — you're not doing it wrong. HubSpot's Flows v4 API has three well-hidden traps (plus a fourth on enrolment logic), each of which we've hit in production.

Everything below is scoped. In our tests against a HubSpot Marketing Hub Pro portal on 30/07/2026 (74 live flows enumerated), these were the behaviours observed. HubSpot can change any of this at any time — retest against your own tier before shipping.

Cheat sheet

At a glance — symptom, cause, fix

Four errors, each with a fix. Jump straight to the detail:

  • HTTP 500, empty body, when POSTing a workflow with a list action. Gotcha 1 → Cause: legacy actionTypeId "0-13". Fix: use the split IDs "0-63809083" (add) or "0-63863438" (remove), with fields.listId and fields.targetObject.
  • HTTP 400 "Some required fields were not set: [type]" — with type in the body. Gotcha 2 → Cause: type doesn't match objectTypeId. Fix: use "CONTACT_FLOW" for object 0-1, "PLATFORM_FLOW" for object 0-3.
  • HTTP 400 on IS_NOT_EQUAL_TO against an enum property. Gotcha 3 → Cause: Flows v4's enum operator allowlist is narrower than CRM Search. Fix: use IS_NONE_OF with a single-element values array; add includeObjectsWithNoValueSet: true if nulls should also match.
  • HTTP 400 "Unified events filter branches cannot have any nested filter branches". Gotcha 4 → Cause: nested filterBranches[] inside a UNIFIED_EVENTS entry. Fix: promote each condition to its own top-level eventFilterBranches[] entry (implicitly OR'd).

Setup

Environment and how to read the claim scope

Tested against:

  • HubSpot Marketing Hub Pro / Ops Hub Pro
  • POST/GET /automation/v4/flows
  • Private App token with automation scope
  • Verified 30/07/2026 against a portal with 74 live flows

Behaviour on Starter or Enterprise, or on future API revisions, has not been separately tested. Retest for the tier you're building on.

Every non-obvious claim below is labelled:

  • [Documented] — HubSpot's public docs state it; the citation is in the text.
  • [Observed] — we tested it in the exact environment above on 30/07/2026. Scope the claim to those tests, not to "HubSpot" as a whole.
  • [Inference] — the pattern is strongly implied by what we tested but not directly verified.

Action IDs

Gotcha 1 — legacy "add to list" action returns HTTP 500

[Observed] on 30/07/2026: actionTypeId "0-13" with operation "ADD" / "REMOVE" no longer works on Flows v4. It returns HTTP 500 with an empty body — not a useful 4xx.

Minimal failing request:

gotcha-1-failing.sh
curl -sS -X POST https://api.hubapi.com/automation/v4/flows \
-H "Authorization: Bearer <PRIVATE_APP_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"type": "PLATFORM_FLOW",
"objectTypeId": "0-3",
"flowType": "WORKFLOW",
"isEnabled": false,
"name": "test-legacy-list-action",
"actions": [{
"actionTypeId": "0-13",
"operation": "ADD",
"listId": "42"
}]
}'

Sanitised error response:

HTTP/1.1 500 Internal Server Error
<empty body, or a generic "internal error" message>

Corrected request:

gotcha-1-corrected.sh
curl -sS -X POST https://api.hubapi.com/automation/v4/flows \
-H "Authorization: Bearer <PRIVATE_APP_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"type": "PLATFORM_FLOW",
"objectTypeId": "0-3",
"flowType": "WORKFLOW",
"isEnabled": false,
"name": "test-new-list-action",
"actions": [{
"actionTypeId": "0-63809083",
"fields": {
"listId": "42",
"targetObject": "{{ enrolled_object }}"
}
}]
}'

Successful response fragment:

{
"id": "<flow-id>",
"type": "PLATFORM_FLOW",
"objectTypeId": "0-3",
"isEnabled": false,
"actions": [{
"actionTypeId": "0-63809083",
"fields": { "listId": "42", "targetObject": "{{ enrolled_object }}" }
}]
}

HubSpot auto-injects targetObject, but include it explicitly so the action body is self-describing.

Discriminator

Gotcha 2 — type is required and must match the object

[Observed] on 30/07/2026: omitting type 400s with "Some required fields were not set: [type]", which reads like a missing property but is actually a missing top-level discriminator. Worse: type must match objectTypeId, and if it doesn't, the error blames objectTypeId.

Minimal failing request:

gotcha-2-failing.sh
curl -sS -X POST https://api.hubapi.com/automation/v4/flows \
-H "Authorization: Bearer <PRIVATE_APP_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"type": "PLATFORM_FLOW",
"objectTypeId": "0-1",
"flowType": "WORKFLOW",
"isEnabled": false,
"name": "test-mismatched-type",
"actions": []
}'

Sanitised error response:

HTTP/1.1 400 Bad Request
{
"status": "error",
"message": "Expected an object type other than \"0-1\" objectType but got \"0-1\""
}

The error blames objectTypeId. objectTypeId is correct — 0-1 is a contact and that's what we're building. type is the culprit.

Corrected request:

gotcha-2-corrected.sh
curl -sS -X POST https://api.hubapi.com/automation/v4/flows \
-H "Authorization: Bearer <PRIVATE_APP_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"type": "CONTACT_FLOW",
"objectTypeId": "0-1",
"flowType": "WORKFLOW",
"isEnabled": false,
"name": "test-correct-type",
"actions": []
}'

Successful response fragment:

{
"id": "<flow-id>",
"type": "CONTACT_FLOW",
"objectTypeId": "0-1",
"isEnabled": false
}

Stage flows for client sign-off with isEnabled: false — no records enrol, nothing fires, reversal is DELETE /automation/v4/flows/{id}.

Operator allowlist

Gotcha 3 — enum operators are a smaller allowlist than CRM Search

[Observed] on 30/07/2026: IS_NOT_EQUAL_TO is valid in the CRM Search API. On Flows v4 ENUMERATION properties, it 400s.

Minimal failing request (fragment):

{
"property": "lifecyclestage",
"operator": "IS_NOT_EQUAL_TO",
"value": "customer"
}

Sanitised error response:

HTTP/1.1 400 Bad Request
{
"status": "error",
"message": "Operator IS_NOT_EQUAL_TO is not supported for property lifecyclestage"
}

Corrected request (fragment):

{
"property": "lifecyclestage",
"operator": "IS_NONE_OF",
"values": ["customer"],
"includeObjectsWithNoValueSet": true
}

Set includeObjectsWithNoValueSet: true if null values should also match — usually what you want on a "not equal" semantic.

Enrolment logic

Gotcha 4 — unified-events filter branches cannot nest

[Observed] on 30/07/2026, across three unified-events enrolment filters: nested filterBranches[] under a UNIFIED_EVENTS entry return HTTP 400 with the exact text "Unified events filter branches cannot have any nested filter branches".

Minimal failing request (fragment):

"eventFilterBranches": [{
"filterBranch": {
"filterBranchOperator": "OR",
"filterBranches": [
{ /* form X + prop_A IS_NONE_OF ["value"] */ },
{ /* form X + prop_B IS_ANY_OF [...] */ }
]
}
}]

Corrected request (fragment). Promote each condition to a top-level entry — eventFilterBranches[] is OR'd implicitly:

"eventFilterBranches": [
{ "filterBranch": { /* form X + prop_A IS_NONE_OF ["value"] */ } },
{ "filterBranch": { /* form X + prop_B IS_ANY_OF [...] */ } }
]

Allowlists

Compatibility cheat-sheet

Working action IDs — list operations

  • Add record to static list: actionTypeId "0-63809083", fields.listId + fields.targetObject: "{{ enrolled_object }}".
  • Remove record from static list: actionTypeId "0-63863438", same fields.
  • Legacy alter-list-membership actionTypeId "0-13": returns HTTP 500 [Observed 30/07/2026] — do not use.

type ↔ objectTypeId discriminator

  • 0-1 (contact) → "CONTACT_FLOW" — verified [Observed 30/07/2026].
  • 0-3 (deal) → "PLATFORM_FLOW" — verified [Observed 30/07/2026].
  • 0-2 (company), 0-5 (ticket) — not tested. Enumerate /automation/v4/flows?objectTypeId=<id> before assuming.

Supported operators on ENUMERATION properties (Flows v4)

  • Supported: IS_ANY_OF / IS_NONE_OF, IS_EXACTLY / IS_NOT_EXACTLY, CONTAINS_ALL / DOES_NOT_CONTAIN_ALL, HAS_EVER_* / HAS_NEVER_*.
  • Not supported: IS_EQUAL_TO / IS_NOT_EQUAL_TO (400 — even though CRM Search accepts these).
  • STRING, NUMBER and DATETIME property operators were not tested in this pass — enumerate a portal's live flows before assuming.

Unified-events branch nesting

  • Allowed: multiple top-level entries in eventFilterBranches[] (implicitly OR'd).
  • Not allowed: nested filterBranches[] inside one UNIFIED_EVENTS entry — 400 with the exact text above.

Retest recipe

Verification

  1. Create a flow using each pattern above via POST /automation/v4/flows with isEnabled: false.
  2. GET it back — the response mirrors the request body when accepted.
  3. Enable one in the HubSpot UI and enrol a test record; the flow runs.
  4. Delete via DELETE /automation/v4/flows/{id} when done.

Last verified: 30/07/2026 on a HubSpot Marketing Hub Pro portal with 74 live flows enumerated.

Before believing any "HubSpot can't do X via the API" claim — including in older articles — enumerate /automation/v4/flows, GET each live flow, count actionTypeId frequency. The portal's own live flows are the authoritative capability inventory. Copying JSON from a working live flow beats guessing a schema; that's how we corrected our own earlier "no internal-notification action exists" belief (actionTypeId "0-8" for internal email and "0-9" for in-app notification are both in production use).

Elemra can help

Need this fixed on your portal?

If you're wrangling Flows v4 and hitting these errors on a live portal, Elemra can audit your HubSpot workflows — we identify supported live action schemas, rewrite the failing calls into the working shapes above, and stage every change disabled for sign-off before it enrols anyone.