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

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:
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:
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:
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:
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
- Create a flow using each pattern above via POST /automation/v4/flows with isEnabled: false.
- GET it back — the response mirrors the request body when accepted.
- Enable one in the HubSpot UI and enrol a test record; the flow runs.
- 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.