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-keyheader. 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 result | POST /background_run/{flow_id}, then its status |
| To show the answer while it is written | The same, with streaming turned on |
| Another service to trigger it when something happens | POST /webhook/{flow_id} |
| To test the draft without deploying | POST /test_run/{flow_id} |
| To stop a running run | POST /run/{task_id}/cancel |
Starting the run
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.
| Field | What it does |
|---|---|
input_value | The input text: what the user would type in the chat. |
session_id | Identifies 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_id | Groups the messages in the conversations view. Defaults to session_id. |
tweaks | Changes component fields for this call only. See Changing fields for one call. |
files | IDs of files already uploaded to the conversation. |
input_type | chat (default), text or any. |
output_type | Which 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:
curl "https://YOUR-SERVER/api/v1/background_run/98bd2e8a-0539-46a2-9e97-665a286d4351/status"| Status | What it means | Final? |
|---|---|---|
| queued | Waiting in the queue: no server has started it yet. | No |
| running | It is running. | No |
| paused | It 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 |
| done | It finished. The result is in result. | Yes |
| error | It failed. The reason is in message. | Yes |
| cancelled | It was cancelled. | Yes |
| not found | There 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:
-
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. -
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 instream_url."artifacts": {"message": "","stream_url": "/api/v1/build/895da449-…_e55d7896-…/ChatOutput-uc3ue/streamv2","type": "stream"} -
Open that address with a
GET, with your server in front. It returns an event stream (text/event-stream):event: messagedata: {"message":"Streaming vertex ChatOutput-uc3ue"}event: messagedata: {"chunk":"We offer haircuts for 18 €, colour for 35 € and styling for 15 €."}event: closedata: {"message":"Stream closed"}
| Event | What it carries |
|---|---|
message with message | The notice that the stream starts. |
message with chunk | A 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 media | A conversation file (for example, a generated image). |
| cancelled | The run was cancelled. |
| error | Something failed; the reason is in error. |
| close | The 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
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:
| HTTP | Message | What to do |
|---|---|---|
| 403 | An API key must be passed as query or header | The x-api-key header is missing. |
| 403 | Invalid or missing API key | The key does not exist, was regenerated or is unencoded in the URL. |
| 429 | This flow reached the runs per minute of its execution profile | The flow went over the runs per minute of its execution profile. Wait and retry. |
| 500 | Flow … not found | The 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_idfor 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.