Dynamiq
Deploy & Integrate

Call Your App over HTTP

The full HTTP contract for invoking a deployed Dynamiq App — auth, request body, synchronous responses, SSE streaming, async callbacks, and error codes.

Every deployed App serves its own HTTPS endpoint. You send a POST request with your workflow's input, and get the result back synchronously (one response when the run finishes), as a Server-Sent Events (SSE) stream, or asynchronously to a callback URL you provide. This page is the complete contract; the Integration tab of your App page generates the same requests prefilled with your hostname and input schema.

Base URL

Each App has a unique hostname, shown (with a copy button) in the Hostname field of the App page header. All requests go to:

https://<your-app-hostname>

The platform routes the request to your App based on the hostname itself — there is no app ID in the path. Don't share hostnames between Apps; each App gets its own, and it stays stable across redeployments.

Authentication

If the App was deployed with Endpoint Authorization enabled (the default — Authorization: Enabled in the header), every request must carry an Access Key as a Bearer token:

Authorization: Bearer $DYNAMIQ_ACCESS_KEY

Access Keys are org- or project-scoped: a project-scoped key only works for Apps in that project. Public Apps (Authorization: Disabled) accept requests without the header.

Only Access Keys work here. A Personal Access Token is scoped to the management API, not to deployed Apps, and is rejected with 403 even when the token itself is valid.

Request body

The body is JSON, sent with Content-Type: application/json (the App also accepts multipart/form-data for file inputs; any other content type returns 415). The only required field is input, whose keys are exactly the fields defined on your workflow's Input node — the Integration and Test tabs both render these fields for you.

inputobjectrequired
Workflow input. Keys must match your workflow's Input node fields.
streamboolean
true streams results back as Server-Sent Events; false (default) returns a single JSON response.
execution_modestring
'sync' (default) or 'async'. 'async' returns immediately and delivers the result to your callback URLs instead.
callbacksarray
Up to 5 {url, auth, metadata} objects that receive the result. Only allowed with execution_mode "async"; each url must be a public HTTPS URL.
last_node_outputboolean
Return only the final node's output instead of all node outputs.
error_on_node_failureboolean
Treat any node failure as a request error.

The examples below use a workflow whose Input node defines a single question field — replace the input object with your own schema.

Synchronous requests

POST with "stream": false (or omit it) and the connection stays open until the workflow finishes; the response is the workflow output as JSON with status 200.

curl -X POST "https://<your-app-hostname>" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $DYNAMIQ_ACCESS_KEY" \
  -d '{
    "input": {
      "question": "What can you do?"
    },
    "stream": false
  }'
import os
import requests
import json

endpoint = "https://<your-app-hostname>"
token = os.getenv("DYNAMIQ_ACCESS_KEY")  # Generate Access Key in the UI settings

headers = {
    'Content-Type': 'application/json',
    'Authorization': f'Bearer {token}',
}

# Payload: Modify input schema as per the input node schema defined in the UI
payload = {
    "input": {
        "question": "What can you do?"
    },
    "stream": False,
}

# Make a POST request to the deployed endpoint
response = requests.post(endpoint, json=payload,
                         headers=headers, stream=False)

if response.status_code == 200:
    try:
        data = response.json()
        print("Response:", json.dumps(data, indent=4))
    except json.JSONDecodeError as e:
        print(f"Failed to decode JSON response: {e}")
else:
    print(f"""Failed to connect to {endpoint}.
          Status code: {response.status_code}. Response: {response.text}""")
// HTTP Client without Streaming
const endpoint = "https://<your-app-hostname>";
const token = process.env.DYNAMIQ_ACCESS_KEY;  // Access key should be securely stored

const headers = {
  'Content-Type': 'application/json',
  'Authorization': `Bearer ${token}`
};

// Payload: Modify input schema as per the input node schema defined in the UI
const payload = {
  "input": {
    "question": "What can you do?"
  },
  "stream": false
};

async function fetchWithoutStreaming() {
  const response = await fetch(endpoint, {
    method: 'POST',
    headers: headers,
    body: JSON.stringify(payload)
  });

  if (!response.ok) {
    throw new Error(`Failed to connect to ${endpoint}. Status code: ${response.status}. Response: ${await response.text()}`);
  }

  const data = await response.json();
  console.log("Response:", JSON.stringify(data, null, 4));
}

fetchWithoutStreaming();

Synchronous calls hold the HTTP connection for the full run. For workflows that take more than a few seconds, prefer streaming or async callbacks — see Streaming and async for guidance.

Streaming over SSE

POST with "stream": true and the App responds with Content-Type: text/event-stream, sending output as it is generated instead of one final response. Each SSE data: line carries a JSON message. Messages whose event field matches the streaming event name configured on your workflow's streaming-enabled node ("data" by default) carry incremental chunks of model output ("deltas") at data.choices[0].delta.content.

curl -N -X POST "https://<your-app-hostname>" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $DYNAMIQ_ACCESS_KEY" \
  -d '{
    "input": {
      "question": "What can you do?"
    },
    "stream": true
  }'
import os
import requests
import json

endpoint = "https://<your-app-hostname>"
token = os.getenv("DYNAMIQ_ACCESS_KEY")  # Generate Access Key in the UI settings

streaming_event = "data"  # Event name configured in the UI

headers = {
    'Content-Type': 'application/json',
    'Authorization': f'Bearer {token}',
}

# Payload: Modify input schema as per the input node schema defined in the UI
payload = {
    "input": {
        "question": "What can you do?"
    },
    "stream": True,
}

# Make a POST request to the deployed endpoint
response = requests.post(endpoint, json=payload,
                         headers=headers, stream=True)

if response.status_code == 200:

    # consume server-sent events (SSE)
    for line in response.iter_lines(decode_unicode=True):
        if line.startswith("data:"):
            data = line[len("data:"):].strip()
            try:
                json_data = json.loads(data)
                if json_data.get("event") == streaming_event:
                    content = json_data.get("data", {}).get("choices", [{}])[0].get("delta", {}).get("content")
                    if content:
                        print(content, end='')
            except json.JSONDecodeError as e:
                print(f"Invalid JSON format: {data} - Error: {e}")

else:
    print(f"""Failed to connect to {endpoint}.
          Status code: {response.status_code}. Response: {response.text}""")
// HTTP Client with Server-Sent Events (Streaming)
const endpoint = "https://<your-app-hostname>";
const token = process.env.DYNAMIQ_ACCESS_KEY;  // Access key should be securely stored

const streamingEvent = "data";  // Event name configured in the UI

const headers = {
  'Content-Type': 'application/json',
  'Authorization': `Bearer ${token}`
};

// Payload: Modify input schema as per the input node schema defined in the UI
const payload = {
  "input": {
    "question": "What can you do?"
  },
  "stream": true
};

async function fetchWithStreaming() {
  const response = await fetch(endpoint, {
    method: 'POST',
    headers: headers,
    body: JSON.stringify(payload)
  });

  if (!response.ok) {
    throw new Error(`Failed to connect to ${endpoint}. Status code: ${response.status}. Response: ${await response.text()}`);
  }

  const reader = response.body!.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });

    // Process complete SSE lines
    const lines = buffer.split('\n');
    buffer = lines.pop() || '';  // Keep the last incomplete line in buffer

    for (const line of lines) {
      if (line.startsWith('data:')) {
        const data = line.substring(5).trim();
        const jsonData = JSON.parse(data);
        if (jsonData.event === streamingEvent) {
          const content = jsonData.data?.choices?.[0]?.delta?.content;
          if (content) {
            console.log(content);
          }
        }
      }
    }
  }
}

fetchWithStreaming();

Async with callbacks

For long runs where you don't want any open connection, set "execution_mode": "async" and pass one or more callbacks (up to 5; each url must be a publicly reachable HTTPS URL). auth is optional and supports only "type": "bearer"; when set, the token is sent as Authorization: Bearer <token> on the callback request.

The request body looks like this:

{
  "input": {
    "question": "What can you do?"
  },
  "execution_mode": "async",
  "callbacks": [
    {
      "url": "https://your-callback-url.example.com/webhook",
      "auth": {
        "type": "bearer",
        "token": "your-callback-auth-token"
      },
      "metadata": {
        "key": "value"
      }
    }
  ]
}

The App responds immediately with status 202 and a request ID:

{
  "id": "f7c7bb61-4f9f-4fd0-940b-98ebd5bd2777",
  "status": "accepted"
}

When the run finishes, your callback URL receives a POST request with the same id:

{
  "id": "f7c7bb61-4f9f-4fd0-940b-98ebd5bd2777",
  "status": "succeeded",
  "timestamp": "2026-03-26T08:34:27.337615881Z",
  "output": {
    "output": "How are you?"
  },
  "metadata": {
    "one": "two"
  }
}

status is "succeeded" or "failed" — check it before reading output, which is the workflow output on success and may be omitted on failure. The metadata you set on the callback is echoed back, and id matches the 202 response, so you can correlate the result with your original request. See Webhooks and events for callback delivery details.

Error codes

StatusMeaning
400Invalid request — malformed JSON, missing input, invalid field values, or callbacks sent without "execution_mode": "async". The body includes validation details per field.
401Missing or invalid Access Key on a private App.
403The token is valid but is not an Access Key (e.g. a Personal Access Token), or the Access Key is scoped to a different project or organization than the App. Also returned with code subscription_limit_reached when your organization's monthly App invocation quota is exhausted.
404No App matches the hostname, or the App was deleted or archived.
415Unsupported Content-Type — send application/json (or multipart/form-data for file inputs).

Non-2xx responses use a consistent JSON error envelope with a message and, for validation failures, per-field details.

Beyond a single request

The same hostname also serves the Runs API (/v1/runs, /v1/files, …) for run management: create background runs, list and filter runs, re-attach to a live event stream, cancel runs, and answer human-in-the-loop requests mid-run. The Integration tab additionally generates a WebSocket variant (wss://<your-app-hostname>) for bi-directional streaming.

On this page