Forward-deployed / AI Knowledge Graph
Flowable

The HTTP task: calling REST APIs from a process

Motto — Most integrations are one HTTP call. The HTTP task lets the model make it declaratively — URL, headers, timeout, and failure routing all visible in the diagram.

Part of Phase 04 — Service integration & error handling.

The Problem

Writing a JavaDelegate for every REST call means a Java class, a deployment, and a code review — for what is really just configuration: method, URL, headers, timeout. On a standalone engine (Phase 1’s Docker container), you can’t even ship delegates without building a custom image. For plain request/response integrations, you want the call in the model: versioned with it, reviewable next to the flow it serves, deployable to a stock engine.

The Concept

Setting flowable:type="http" on a service task swaps its behaviour for a built-in HTTP client configured by fields:

<style> .dgm-ht{background:var(–color-surface);border:1px solid var(–color-border);border-top:3px solid var(–color-border-danger-outline); border-radius:16px;padding:28px 26px 22px;box-shadow:0 1px 3px rgb(var(–shadow-color) / 0.04),0 8px 24px rgb(var(–shadow-color) / 0.04); margin:26px 0;max-width:100%;font-family:inherit;text-align:left;position:relative} .dgm-ht-chip{position:absolute;top:-11px;left:24px;background:var(–color-background-danger-solid);color:var(–white); font-size:10.5px;font-weight:700;letter-spacing:1px;text-transform:uppercase; border-radius:20px;padding:3px 12px;box-shadow:0 2px 6px color-mix(in oklab, var(–color-background-danger-solid) 28%, transparent)} .dgm-ht h4{margin:0 0 4px;font-size:11px;font-weight:700;letter-spacing:.4px;text-transform:uppercase;color:var(–color-text-secondary)} .dgm-ht-sub{font-size:12px;color:var(–color-text-secondary);margin:0 0 16px} .dgm-ht-row{display:flex;align-items:center;justify-content:center;gap:6px;flex-wrap:wrap} .dgm-ht-n{background:var(–color-surface-tertiary);border:1px solid var(–color-border);border-radius:9px;padding:9px 11px;text-align:center; font-size:10.5px;font-weight:700;color:var(–color-text);line-height:1.3} .dgm-ht-n.accent{background:var(–color-background-danger-surface);border-color:var(–color-border-danger-surface);color:var(–color-text-danger)} .dgm-ht-n.blue{background:var(–color-background-info-surface);border-color:var(–color-border-info-surface);color:var(–color-text-info)} .dgm-ht-n.green{background:var(–color-background-success-surface);border-color:var(–color-border-success-surface);color:var(–color-text-success)} .dgm-ht-n.red{background:var(–color-background-danger-surface);border-color:var(–color-border-danger-surface);color:var(–color-text-danger)} .dgm-ht-arr{color:var(–color-text-tertiary);font-size:12px;display:flex;flex-direction:column;align-items:center;gap:1px} .dgm-ht-arr .lbl{font-size:8.5px;font-weight:700;color:var(–color-text-tertiary);text-transform:uppercase;letter-spacing:.3px} @media (max-width:640px){.dgm-ht{padding:20px 16px 18px}.dgm-ht-row{flex-direction:column}} .dgm-ht-q{background:var(–color-surface-tertiary);border:1px dashed var(–color-border);border-radius:10px;padding:9px 14px;text-align:center; font-size:11px;font-weight:700;color:var(–color-text);margin-bottom:10px} .dgm-ht-branches{display:grid;grid-template-columns:repeat(2,1fr);gap:10px} .dgm-ht-b{border-radius:11px;padding:11px 13px;border:1.5px solid} .dgm-ht-b h6{margin:0 0 4px;font-size:10.5px;font-weight:700} .dgm-ht-b p{margin:0;font-size:10px;color:var(–color-text-secondary);line-height:1.35} .dgm-ht-b.accent{background:var(–color-background-danger-surface);border-color:var(–color-border-danger-surface)} .dgm-ht-b.accent h6{color:var(–color-text-danger)} .dgm-ht-b.blue{background:var(–color-background-info-surface);border-color:var(–color-border-info-surface)} .dgm-ht-b.blue h6{color:var(–color-text-info)} .dgm-ht-b.green{background:var(–color-background-success-surface);border-color:var(–color-border-success-surface)} .dgm-ht-b.green h6{color:var(–color-text-success)} .dgm-ht-b.red{background:var(–color-background-danger-surface);border-color:var(–color-border-danger-surface)} .dgm-ht-b.red h6{color:var(–color-text-danger)} .dgm-ht-b.neutral{background:var(–color-surface-tertiary);border-color:var(–color-border)} .dgm-ht-b.neutral h6{color:var(–color-text)} @media (max-width:640px){.dgm-ht{padding:20px 16px 18px}.dgm-ht-branches{grid-template-columns:1fr}} </style> <div class=“dgm-ht”> <span class=“dgm-ht-chip”>HTTP task as a BPMN error source</span> <h4>A 4xx/5xx response becomes a named error the diagram can branch on</h4> <p class=“dgm-ht-sub”>2xx moves on normally; anything else throws a BPMN error the flow can catch.</p> <div class=“dgm-ht-row”> <div class=“dgm-ht-n blue”>● start</div> <span class=“dgm-ht-arr”>→</span> <div class=“dgm-ht-n accent”>HTTP task ⚙️🌐 — GET /v1/score?pan=…</div> </div> <div class=“dgm-ht-branches”> <div class=“dgm-ht-b green”> <h6>2xx</h6> <p>Review score 👤 → end</p> </div> <div class=“dgm-ht-b red”> <h6>4xx / 5xx → BPMN error</h6> <p>Pull report manually 👤 → end</p> </div> </div> </div>

The fields that matter, and what each buys you:

Field Role
requestMethod, requestUrl, requestHeaders, requestBody the call itself; ${var} expressions everywhere
requestTimeout never let a slow API hold the transaction hostage
resultVariablePrefix + saveResponseParameters response status/headers/body land as process variables (bureauResponseStatusCode, bureauResponseBody)
failStatusCodes / handleStatusCodes which statuses count as failure, and turn them into catchable BPMN errors instead of raw exceptions

That last row is the bridge to the rest of this phase. With failStatusCodes set, a 404 from the bureau isn’t a stack trace in a log — it’s an error event the diagram routes, exactly like a delegate throwing BpmnError (lesson 01). Phase 2 still applies unchanged: mark the HTTP task flowable:async="true", and slow or flaky calls retry through the job executor instead of failing the caller’s transaction.

When not to use it: anything needing OAuth token refresh, response parsing beyond “grab the JSON”, idempotency keys, or circuit breakers. That logic belongs in a delegate or a dedicated gateway service. The HTTP task is for calls that are truly just calls.

Use It

The full model is outputs/bureau-check.bpmn20.xml. It deploys to the stock flowable/flowable-rest container with zero custom code. Its core:

<serviceTask id="fetchScore" name="Fetch bureau score" flowable:type="http">
  <extensionElements>
    <flowable:field name="requestUrl">
      <flowable:expression><![CDATA[https://bureau.example.com/v1/score?pan=${pan}]]></flowable:expression>
    </flowable:field>
    <flowable:field name="requestTimeout">
      <flowable:string><![CDATA[5000]]></flowable:string>
    </flowable:field>
    <flowable:field name="resultVariablePrefix">
      <flowable:string><![CDATA[bureau]]></flowable:string>
    </flowable:field>
    <flowable:field name="failStatusCodes">
      <flowable:string><![CDATA[400, 404, 5XX]]></flowable:string>
    </flowable:field>
  </extensionElements>
</serviceTask>

<boundaryEvent id="bureauFailed" attachedToRef="fetchScore">
  <errorEventDefinition errorRef="HTTP_FAULT"/>
</boundaryEvent>

Deploy and drive it with Phase 1’s client (flowable_client.py). Start an instance with a pan variable. Since bureau.example.com doesn’t exist, watch the failure path do its job: the boundary event fires, and the instance parks at Pull bureau report manually instead of dying. That’s integration failure as a designed path, not an incident.

Ship It

This lesson ships outputs/bureau-check.bpmn20.xml, a reusable HTTP-integration pattern: a call, a timeout, response variables, and failure routed to a human fallback. The capstone’s bureau step reuses it.

Check Yourself

Q1. Without failStatusCodes, a 500 from the API causes…

  • A) the error boundary to fire
  • B) a technical exception — rollback, and job retries if the task is async
  • C) the response to be stored with status 500
  • D) the instance to complete

<details><summary>Answer</summary>B — by default non-2xx is a technical failure like any thrown exception. failStatusCodes (+ handling) is what promotes chosen statuses to catchable BPMN errors.</details>

Q2. Where does the response body end up with resultVariablePrefix = bureau?

  • A) discarded
  • B) in bureauResponseBody (plus status/headers variables) on the instance
  • C) in the history tables only
  • D) in a file

<details><summary>Answer</summary>B — the prefix + saveResponseParameters maps the whole response into process variables the next tasks can read.</details>

Q3. The bureau call needs an OAuth refresh flow and a circuit breaker. Best home?

  • A) more HTTP-task fields
  • B) a script task
  • C) a delegate (or a gateway service the HTTP task calls internally)
  • D) the process can’t do this

<details><summary>Answer</summary>C — the HTTP task is deliberately dumb. Protocol logic belongs in code, and the task stays for calls that are just calls.</details>

Challenge. Point requestUrl at a real public API (for example, a status endpoint you trust), deploy to your local engine, and run it twice — once against the real URL, once against a garbage domain. Diff the two instances’ variables and history. You should see the happy path store bureauResponseStatusCode=200 and the failure path route through bureauFailed, without a single line of code.

Related