/automation/v4 API HTTP 400

Hi,

I’m trying to batch update workflows based on existing workflows in a different account.

I use the GET https://api.hubapi.com/automation/v4/flows/ID endpoint to get the JSON of a workflow, and then PUT https://api.hubapi.com/automation/v4/flows/ID endpoint to update another workflow with the same JSON (patching the revisionId and ID as needed).

This works for most of my requests, but is failing for around 50 out of 200 with the following errors:

- Invalid request to flow update (38)

- Invalid custom code action request (5)

- Existing classic flow must have classic enrollment settings (5)

The main one is the “Invalid request to flow update” - does anyone know how I can determine what is wrong with my request?

Hey, @shubjain :waving_hand: Welcome to our community. I couldn’t find a documentation match for these errors. I’d suggest you make a support ticket specifically for the error codes and:

  • specify you are not asking for custom code help
  • you already understand how to use the REST API endpoints
  • you only need clarity on the meaning of these errors when using the endpoint

Best,

Jaycee

Hey, thanks for your response! I actually already spoke to Support and they suggested I post here so the developers could have a look - do you know anywhere else I may be able to ask?

Hey, @shubjain I don’t, unfortunately. My instinct is to press support for an answer on the error codes, specifically because those are generated via the product and not something a community member would have access to. Where our community members can help is with looking at code issues or troubleshooting your request. If you want to share the request and response here, maybe we can, as a community, take a look.

Talk soon! — Jaycee

Hi @Jaycee_Lewis, @shubjain — I’ve been fighting this exact error for the past couple of days and I think I can add something useful to this thread, because I managed to isolate the failure condition pretty precisely.

Short version: in our portal, any request to /automation/v4/flows with "isEnabled": true gets rejected. The identical request with "isEnabled": false goes through fine. It doesn’t matter what’s in the workflow.

Some context: we’re using a private app token with the automation and tickets scopes, creating ticket-based workflows (type: PLATFORM_FLOW, objectTypeId: 0-5). The portal already has ticket workflows up and running that were enabled through the UI, so it’s not a subscription issue.

Here’s what we tried, roughly in order of desperation:

First, the obvious stuff. We created our workflow (a webhook action with list-based enrollment) with isEnabled: false — worked fine. Then a PUT flipping only that flag to true, with a fresh revisionId from a GET right before — FLOW_UPDATE_BAD_REQUEST.

Then we suspected we were dropping some field the validation needed, so we did an echo test: GET the flow, strip only createdAt, updatedAt and dataSources (as the docs recommend), flip isEnabled, PUT it straight back. Same error. So the API is rejecting its own output, which rules out anything missing on our end.

From there we started tearing the workflow apart to find the culprit. Swapped the webhook for a plain delay action — still fails. Swapped the list filters for "enrollmentCriteria": {"type": "MANUAL"} — still fails. Finally we tried creating a brand-new minimal flow (one delay, manual enrollment, nothing else) directly with isEnabled: true in the POST — FLOW_CREATION_BAD_REQUEST.

So at this point: a trivial flow that creates perfectly fine disabled cannot be created or updated as enabled, no matter what it contains. The “Update a workflow” example in the v4 docs, which shows exactly this operation, doesn’t work for us.

Correlation IDs in case anyone from HubSpot can check the server side:

  • PUT with isEnabled: true: 01a01a25-76ec-7e06-ae0e-3a0ecae08bed
  • POST minimal flow with isEnabled: true: 01a01a43-8aeb-7b12-a9e7-7cd9f867c13e

One thing that caught my eye in this thread: @shubjain’s error list included “Existing classic flow must have classic enrollment settings”. That makes me wonder whether API-created flows end up on a different flow engine version than UI-created ones, and the enable validation chokes on that combination — it would explain why the API won’t accept its own GET output. There’s also another thread where the same body works in some portals and fails in others, which smells like a portal-level flag rather than a payload problem.

I’d really appreciate it if someone could look up what’s actually failing behind those correlation IDs. The generic message gives us nothing to work with, and the support → community → support loop shubjain described leaves nowhere to go. Even just confirming “enabling PLATFORM_FLOW via API isn’t supported yet” would save people a lot of time.

Happy to share full request bodies or run more tests if it helps.

Those three errors are three different problems, so it’s worth splitting them before debugging.

“Existing classic flow must have classic enrollment settings” (5), this is a flow-type mismatch, not a payload bug. v4 distinguishes classic flows from newer ones, and the enrollment block has a different shape for each. If the source flow and the target flow aren’t the same type, the enrollment settings you’re PUTting won’t match what the target expects. Check the flow type on both ends before copying; where they differ, you can’t round-trip the enrollment block as-is.

“Invalid custom code action request” (5), custom coded actions reference secrets by name, and secrets are portal-scoped. They’re not in the flow JSON, so copying an action into a portal that doesn’t have the same secret names will fail validation. Same applies to any hubspotClient call in the code that assumes objects or properties the target portal doesn’t have. Create the secrets in the target portal first, with identical names.

“Invalid request to flow update” (38), this is the generic bucket, and when copying flows between portals it’s almost always a dangling portal-scoped ID. Patching id and revisionId isn’t enough; the flow body is full of references that only mean something in the source portal:

  • property names and IDs in filters and set-value actions
  • list IDs in IN_LIST / NOT_IN_LIST conditions
  • owner IDs in rotate and assignment actions
  • team IDs
  • pipeline and stage IDs
  • form IDs, marketing email IDs
  • custom object type IDs
  • workflow IDs in “enrol in another workflow” actions

Any single one of these pointing at something that doesn’t exist in the target gives you the same unhelpful 400.

Two things that make this findable:
Log the full response body, not just the message. v4 usually returns a context object with the field path of the offending element, and it’s easy to lose that if you’re only reading message.
If the body doesn’t narrow it, bisect. Strip the flow down to enrolment plus one trivial action, PUT that, then add actions back in halves. It sounds slow but it converges in four or five requests and it tells you exactly which action carries the bad reference.

One more thing worth checking: revisionId has to match the target’s current revision at the moment of the PUT. If anything touched those flows between your GET and your PUT, including a colleague opening and saving one in the UI, you’ll get a 400 that has nothing to do with the payload.