ModelWorks logoModelWorks

Fetch

Perform HTTP requests, upload blobs, and handle responses in your circuits

Introduction to Fetch

The circuit.core.fetch primitive lets you call any HTTP(S) endpoint, upload or download files, and process responses (JSON, text, binary, or Server-Sent Events). Configure it entirely via your circuit JSON—no direct code access is needed.


Step Definition

JSON
"steps": [
  {
    "name": "fetch_data",
    "function": "circuit.core.fetch",
    "input": { /* see below */ },
    "blob_mappings": [ /* optional: inject blob data into body */ ],
    "stream_mapping": [ /* optional for SSE */ ],
    "provider": { /* optional: declare where your compute infrastructure lives */ }
  }
]

input Properties

FieldTypeRequiredDescription
urlstringYesThe target endpoint URL (can include selectors, e.g. ${{ secrets.api_url }}/path).
methodstringYesHTTP verb: GET, POST, PUT, DELETE, PATCH.
headersobject<string,string>NoKey/value map of headers; values may use selectors.
bodyobject or stringNoJSON object or raw string for the request body; selectors allowed.
timeoutnumberNoRequest timeout in seconds.
body_optionsobjectNoBody encoding and transformation options (see below).
query_paramsobject<string,string>NoURL query parameters; values support ${{ }} expressions. Empty/null values are omitted.

body_options

Controls how the request body is encoded and filtered:

FieldTypeDefaultDescription
encode_asstringjsonEncoding format: json (application/json) or form_urlencoded (application/x-www-form-urlencoded).
skip_null_valuesbooleanfalseRemove null values from objects and null elements from arrays.
skip_empty_arraysbooleanfalseRemove empty arrays from the body.
skip_empty_objectsbooleanfalseRemove empty objects from the body.

Blob Mappings

blob_mappings is a step-level field (a sibling of input, not nested inside it) that injects blob data into the JSON request body. Each group inserts an array of objects at a specific container path.

FieldTypeRequiredDescription
to_attributestringYesContainer path in the body where the array of objects will be inserted (e.g. image, messages[0].content).
mappingsarrayYesField definitions for each object element in the array.

Each entry in mappings:

FieldTypeRequiredDescription
from_selectorstringNo*Memory selector to resolve blob IDs or URLs from memory.
tostringYesField path within each object element. Use [$] for array iteration (e.g. [$].url, image.url).
encode_asstringNoEncoding strategy: url (default), base64, or base64_nodata.
valueanyNo*Static value to inject (alternative to from_selector).

* Either from_selector or value must be provided.

JSON
"blob_mappings": [
  {
    "to_attribute": "messages[0].content",
    "mappings": [
      { "from_selector": "${{ input.image_blob_id }}", "to": "[$].image_url", "encode_as": "url" },
      { "value": "user_upload", "to": "[$].type" }
    ]
  }
]

The engine resolves each blob by selector (or uses the static value), encodes it per encode_as, and injects the resulting array of objects at to_attribute in the request body.


Stream Mapping (SSE)

If your endpoint returns Server-Sent Events, you can capture and accumulate fragments via stream_mapping:

JSON
"stream_mapping": [
  {
    "sse_selector": "choices[0].delta.content",
    "sse_name": "delta_accumulator"
  }
]
FieldTypeRequiredDescription
sse_selectorstringYesJMESPath selector against each event's JSON chunk.
sse_namestringYesKey under output.stream_accumulators where the value is accumulated.
sse_event_typestringNoSSE event type filter—only process chunks where json["type"] matches this string exactly (e.g. response.output_text.delta). If omitted, all chunks are processed.
accumulator_modestringNoHow to accumulate across chunks: last_value, string_concat, array_append, array_by_key, object_merge. If omitted, the default is chosen by value type.
primary_keystringNoWhen accumulator_mode is array_by_key, the key used to identify and merge array elements (e.g. index, id).
merge_schemaobjectNoPer-path merge rules for nested fields. Keys are dot-separated paths (e.g. function.arguments); each value specifies a mode (and optional primary_key for array_by_key).

Provider Metadata (Optional)

The provider field is an optional sibling of input that tells ModelWorks where your compute infrastructure is physically located. This helps users understand which countries their data may pass through when running your circuit.

When someone runs your circuit, their request travels to the server hosting your API endpoint. That server lives somewhere — a datacenter in Iowa, a cloud region in Germany, or maybe a load balancer that spreads traffic across several countries. By listing the regions where your compute infrastructure is located, you give users transparency about where their data is being processed.

Fields

FieldTypeRequiredDescription
namestringYesThe name of your provider (e.g., "Together AI", "OpenAI", "AWS Bedrock").
regionsstring[]NoISO 3166-2 region codes where your infrastructure is located (see below).

Region Codes

Region codes follow the ISO 3166-2 standard — a country code plus a subdivision code. If your provider routes requests to multiple countries, list all of them in the array.

CodeLocation
US-IAIowa, United States
US-CACalifornia, United States
US-VAVirginia, United States
DE-BEBerlin, Germany
CN-BJBeijing, China
CN-SHShanghai, China

Examples

Single region:

JSON
"provider": {
  "name": "Together AI",
  "regions": ["US-IA"]
}

Multiple regions (load balancer):

JSON
"provider": {
  "name": "OpenAI",
  "regions": ["US-CA", "US-IL", "US-VA"]
}

No known region (global load balancer):

JSON
"provider": {
  "name": "Cloudflare"
}

What happens if you don't provide provider?

ModelWorks will try to auto-detect the location by resolving your fetch URL to an IP address and looking up its geographic location. However, this detection can be inaccurate when the endpoint sits behind a CDN or load balancer (like Cloudflare or AWS CloudFront) — the detected location will reflect the CDN's edge server, not your actual infrastructure.

When auto-detected, the circuit details page will show an "LB" badge with a warning tooltip explaining that the region was auto-detected and may not reflect the true origin.


Outputs

After execution, the Fetch step populates its output object, which you can reference with selectors:

JSON
// Non-streaming
${{ fetch_data.output.json_body }}     // parsed JSON
${{ fetch_data.output.text_body }}     // raw text
${{ fetch_data.output.artifact_urls }}  // array of URLs (binary blobs)

// Streaming
${{ fetch_data.output.stream_accumulators.delta_accumulator }}

Use these values in subsequent steps or in your top-level outputs:

JSON
"outputs": [
  {
    "name": "data",
    "value": "${{ fetch_data.output.json_body }}"
  }
]

Examples

Simple GET Request

JSON
{
  "name": "get_user_profile",
  "steps": [
    {
      "name": "profile",
      "function": "circuit.core.fetch",
      "input": {
        "url": "https://api.example.com/users/${{ input.user_id }}",
        "method": "GET",
        "headers": {
          "Authorization": "Bearer ${{ secrets.api_key }}"
        }
      }
    }
  ],
  "outputs": [
    { "name": "profileData", "value": "${{ profile.output.json_body }}" }
  ]
}

POST with JSON Body

JSON
{
  "name": "create_ticket",
  "steps": [
    {
      "name": "ticket",
      "function": "circuit.core.fetch",
      "input": {
        "url": "https://api.example.com/tickets",
        "method": "POST",
        "headers": { "Content-Type": "application/json" },
        "body": {
          "subject": "${{ input.subject }}",
          "description": "${{ input.description }}"
        }
      }
    }
  ],
  "outputs": [
    { "name": "ticketId", "value": "${{ ticket.output.json_body.id }}" }
  ]
}

File Upload & Fetch Binary

JSON
{
  "name": "upload_and_fetch",
  "steps": [
    {
      "name": "uploader",
      "function": "circuit.core.fetch",
      "input": {
        "url": "https://my.blob.service/blobs",
        "method": "POST"
      },
      "blob_mappings": [
        {
          "to_attribute": "files",
          "mappings": [
            { "from_selector": "${{ input.blob_id }}", "to": "[$].url", "encode_as": "url" }
          ]
        }
      ]
    },
    {
      "name": "downloader",
      "function": "circuit.core.fetch",
      "input": {
        "url": "https://my.blob.service/file-download/${{ uploader.output.artifact_urls[0] }}",
        "method": "GET"
      }
    }
  ],
  "outputs": [
    { "name": "downloadedUrl", "value": "${{ downloader.output.artifact_urls[0] }}" }
  ]
}

Next: Adapters – integrate SSE, Webhooks, and custom protocols!

On this page

On this page