Skip to content

Calling a flow through the API

A flow with a deployed version can be run from any application with an HTTP request. The recommended way is to start it in the background and check its status afterwards: the request answers right away and your application does not sit waiting with the connection open. This page explains how, what each step returns and how to tell where a run is. The examples are real responses, trimmed to read better.

The flow’s API Access window generates the call’s code with your flow, your key and your server already filled in. Which tabs it shows depends on the flow’s components: see Publishing a flow.

Before you start

  • A deployed version. Calls run the deployed version, not the draft you see in the editor. If you change the flow, deploy a new version for calls to use it. See Versions and publishing. The only exception is the test run.
  • An API key. It goes in the x-api-key header. See API keys.
  • The flow ID. It is the one in the editor’s address and in the API Access code. Flows are always called by their ID, not by their name.

Every path starts with https://YOUR-SERVER/api/v1/. The API Access code already shows your server’s address.

Which call to use

You want…Call
To run the flow and collect the resultPOST /background_run/{flow_id}, then its status
To show the answer while it is writtenThe same, with streaming turned on
Another service to trigger it when something happensPOST /webhook/{flow_id}
To test the draft without deployingPOST /test_run/{flow_id}
To stop a running runPOST /run/{task_id}/cancel

Starting the run

Ventana de terminal
curl -X POST "https://YOUR-SERVER/api/v1/background_run/FLOW_ID" \
-H "x-api-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"input_value": "Hi, do you have a slot for a haircut on Friday afternoon?", "session_id": "customer-42"}'

The response comes back right away, with the run’s ID and the address to check its status:

{
"taskid": "98bd2e8a-0539-46a2-9e97-665a286d4351",
"message": "Flow execution started.",
"statusurl": "/api/v1/background_run/98bd2e8a-0539-46a2-9e97-665a286d4351/status"
}

The taskid is also the run’s ID in the executions log.

The request body

Every field is optional.

FieldWhat it does
input_valueThe input text: what the user would type in the chat.
session_idIdentifies the conversation. Send the same value with every message from the same user so the flow remembers what came before. If you leave it out, NappAI creates one and returns it in the result.
conversation_idGroups the messages in the conversations view. Defaults to session_id.
tweaksChanges component fields for this call only. See Changing fields for one call.
filesIDs of files already uploaded to the conversation.
input_typechat (default), text or any.
output_typeWhich outputs come back: chat (default, the Chat Output components), text, any (every output) or debug (every component).

Checking the status

Send a GET to the statusurl, for example every second or every few seconds, until it reaches a final status:

Ventana de terminal
curl "https://YOUR-SERVER/api/v1/background_run/98bd2e8a-0539-46a2-9e97-665a286d4351/status"
StatusWhat it meansFinal?
queuedWaiting in the queue: no server has started it yet.No
runningIt is running.No
pausedIt is waiting for a call to end or for someone to answer an approval. It then carries on by itself, with the same ID.No
doneIt finished. The result is in result.Yes
errorIt failed. The reason is in message.Yes
cancelledIt was cancelled.Yes
not foundThere is no run with that ID.Yes

The status is in the status field. This is how a normal run goes:

{ "status": "queued", "message": "Task is waiting in the queue for a worker" }
{ "status": "running" }
{ "status": "done", "result": { "session_id": "customer-42", "outputs": [ … ] } }

A paused one carries what the flow has returned so far and what it is waiting for:

{
"status": "paused",
"paused": { "kind": "approval", "pause_id": "0ce2cdac-2e14-4b97-ba9c-7424c4790e55", "component_id": "AdvancedAgent-pq02" },
"result": { "session_id": "…", "outputs": [ … ] }
}

kind is approval (or tool_approval) when it waits for an approval, and outbound_call when it waits for an outbound call to end. When it resumes it goes through queued and running again and ends in done.

A failed one says which component failed and why:

{ "status": "error", "message": "Error building Component OpenAI: Error code: 401 - Incorrect API key provided", "traceback": "…" }

The result

When the status is done, result carries what the flow returned:

{
"status": "done",
"result": {
"session_id": "customer-42",
"outputs": [
{
"inputs": { "input_value": "Hi, do you have a slot for a haircut on Friday afternoon?", "files": [] },
"outputs": [
{
"results": {
"message": {
"text": "Sure, we have a slot for a haircut on Friday afternoon; call 960 000 000 and we will confirm the exact time.",
"sender": "Machine",
"sender_name": "AI",
"session_id": "customer-42",
"files": []
}
},
"artifacts": {
"message": "Sure, we have a slot for a haircut on Friday afternoon; call 960 000 000 and we will confirm the exact time.",
"sender": "Machine",
"sender_name": "AI",
"files": [],
"type": "object"
},
"component_display_name": "Chat Output",
"component_id": "ChatOutput-uc3ue"
}
]
}
]
}
}

The flow’s answer is at result.outputs[0].outputs[0].results.message.text. outputs[0].outputs has one item per output component returned, and component_id says which one each is.

Streaming the response

To show the text while the model writes it:

  1. Turn on streaming in the component that writes the answer: it is the Stream field, in the advanced options of the models and of the Agent. You can also turn it on for one call only with a tweak: "tweaks": {"AdvancedAgent-2jhmg": {"use_stream": true}} on an Agent, or {"stream": true} on a model.

  2. Start the run and check its status as usual. It becomes done as soon as the answer starts being written, without the text: instead it carries "type": "stream" and an address in stream_url.

    "artifacts": {
    "message": "",
    "stream_url": "/api/v1/build/895da449-…_e55d7896-…/ChatOutput-uc3ue/streamv2",
    "type": "stream"
    }
  3. Open that address with a GET, with your server in front. It returns an event stream (text/event-stream):

    event: message
    data: {"message":"Streaming vertex ChatOutput-uc3ue"}
    event: message
    data: {"chunk":"We offer haircuts for 18 €, colour for 35 € and styling for 15 €."}
    event: close
    data: {"message":"Stream closed"}
EventWhat it carries
message with messageThe notice that the stream starts.
message with chunkA piece of the text. Join them in order. Depending on the component, many small pieces arrive, or a single one with the whole text.
message with mediaA conversation file (for example, a generated image).
cancelledThe run was cancelled.
errorSomething failed; the reason is in error.
closeThe stream is over. It may arrive more than once: keep the first.

After 30 seconds without data, a : keep-alive line arrives so the connection is not dropped; ignore it.

Cancelling a run

Ventana de terminal
curl -X POST "https://YOUR-SERVER/api/v1/run/TASK_ID/cancel" -H "x-api-key: YOUR_KEY"
{ "status": "ok", "task_id": "7e18b6fa-3042-45ba-8b9f-43eaae93dae7", "message": "Cancellation signal sent" }

The run stops within a few seconds and its status becomes cancelled:

{ "status": "cancelled", "message": "Task cancelled by user" }

Testing the draft (test_run)

POST /test_run/{flow_id} runs the draft, what you see in the editor now, without deploying a version. It takes the same body and returns a taskid and the same statusurl, which you check in the same way.

{ "taskid": "1a4f4e91-ca98-4c68-bd89-b7e3a2cd100c", "message": "Test run started.", "statusurl": "/api/v1/background_run/1a4f4e91-ca98-4c68-bd89-b7e3a2cd100c/status" }

To test it with a given environment, use POST /test_run/{environment_id}/{flow_id}. This is what the Test mode of API Access generates.

Webhooks

POST /webhook/{flow_id} is the address for another service to trigger the flow. The flow needs a Webhook Input component, which receives what arrives in the request:

{ "headers": { … }, "query_params": { … }, "body": { "customer": "Ana", "input_value": "I want an appointment on Saturday" } }

NappAI removes the API key and the cookies from the headers and parameters before passing them to the flow.

What the calling service gets back depends on whether the flow has a Webhook Response component:

  • Without Webhook Response, the answer is immediate (HTTP 202) and the flow carries on in the background:

    {
    "taskid": "457708c0-e188-4ad5-b428-f77fd6c5ce8c",
    "status": "Task started in the background",
    "message": "Flow execution started.",
    "statusurl": "/api/v1/webhook_task/457708c0-e188-4ad5-b428-f77fd6c5ce8c"
    }

    Its status is checked at statusurl, with the same statuses.

  • With Webhook Response, the request waits for the flow to finish and returns exactly what reaches that component. Use it only when the calling service needs a specific answer in the same request.

Webhooks of services such as Gmail, Outlook, Teams or Shopify are not called like this: they are published from the component itself. See Publishing a flow.

Changing fields for one call (tweaks)

tweaks changes the value of component fields for that call only, without touching the deployed version. The key is the component’s ID, shown on the component itself and in the API Access code:

{
"input_value": "Hi",
"tweaks": {
"AdvancedAgent-2jhmg": { "use_stream": true },
"TextInput-entrada": { "input_value": "Valencia" }
}
}

Only fields that exist in that component change. Instead of the ID you can also use the component’s display name.

Errors when starting it

If the run does not even start, the response gives the reason in message:

HTTPMessageWhat to do
403An API key must be passed as query or headerThe x-api-key header is missing.
403Invalid or missing API keyThe key does not exist, was regenerated or is unencoded in the URL.
429This flow reached the runs per minute of its execution profileThe flow went over the runs per minute of its execution profile. Wait and retry.
500Flow … not foundThe ID does not exist, the flow has no deployed version or your key has no access to it.
{ "message": "Invalid or missing API key", "type": "http_error", "status_code": 403 }

If the run starts and a component fails later, the request is accepted all the same: the failure shows up afterwards in the error status, with the component and the reason, and in the executions log.

Good practices

  • Reuse the same session_id for every message from the same user, so the flow remembers the conversation.
  • Treat queued, running and paused as “still going”, and stop checking at done, error or cancelled.
  • Check the status every second or every few seconds; checking more often does not make it finish sooner.
  • Keep the key on your application’s server, never in code that runs in the browser.