guimkt-make-blueprint-expert

Installation

$npx skills add guilhermemarketing/esc-skills --skill guimkt-make-blueprint-expert

Summary

The agent can interpret, create, edit, validate, and optimize Make.com scenario blueprint JSON files, including fixing field mappings, debugging execution errors, generating correct Make expressions, and configuring complex integrations like Facebook Lead Ads with CRM APIs. Invoke when the user mentions Make.com, blueprint JSON, Make scenarios, or needs to create/troubleshoot Make automations.

SKILL.MD


name: guimkt-make-blueprint-expert description: Interpret, create, edit, validate, and optimize Make.com (formerly Integromat) scenario blueprint JSON files. Use when the user wants to create a Make scenario from scratch via blueprint, modify an existing blueprint JSON (fix field mappings, add modules, change body content, swap API endpoints), debug Make execution errors (InvalidConfigurationError, HTTP 400, field mapping issues), understand Make scenario structure, fix jsonStringBodyContent encoding issues, or generate correct Make expressions ({{module.field}}) inside JSON bodies. Also triggers for tasks involving Make HTTP modules, router/filter configuration, Facebook Lead Ads integration with CRM APIs, DataCrazy CRM integration, webhook configuration, variable modules (Set Variable, roleta/round-robin), or any Make.com automation troubleshooting. Use this skill whenever the user mentions "Make.com", "Make scenario", "blueprint JSON", "Make blueprint", "cenário Make", "módulo HTTP Make", "jsonStringBodyContent", "Make expressions", "InvalidConfigurationError Make", or any variation of creating, editing, debugging, or optimizing Make.com automations via blueprint files.

Make.com Blueprint Expert

Create, edit, debug, and optimize Make.com scenario blueprints programmatically via JSON.

Critical Rules

1. JSON Body Content: NEVER Use Quoted Strings Inside Make Expressions

The jsonStringBodyContent field is a double-encoded JSON string — a JSON string whose content is another JSON structure containing Make expressions {{}}. Make validates the body as JSON before evaluating expressions, so escaped quotes inside {{}} break the parser.

// ❌ BREAKS — quotes inside {{}} terminate JSON string prematurely
{"name":"{{ifempty(1.field_name; \"fallback\")}}"}

// ✅ WORKS — direct value, no quoted fallback
{"name":"{{1.field_name}}"}

// ✅ WORKS — ifempty with another field reference (no quotes)
{"type":"{{ifempty(1.data.field_a[]; 1.data.field_b[])}}"}

If a string fallback is needed, set it in a Set Variable module before the HTTP module, then reference the variable.

2. Double-Encoding: Always Use Python json.dumps()

The jsonStringBodyContent blueprint value has two encoding layers:

  • Layer 1: The body content itself is a JSON object (with Make expressions as values)
  • Layer 2: That JSON is stored as a string value inside the blueprint JSON file

Always use Python's json.dumps() twice to generate correctly escaped values:

import json

body_obj = {
    "name": "{{1.data.full_name}}",
    "email": "{{1.data.email}}",
    "config": {"id": "{{25.my_variable}}"}
}
# Double-encode: inner json.dumps creates the JSON string,
# outer json.dumps escapes it for the blueprint
blueprint_value = json.dumps(json.dumps(body_obj, separators=(',', ':')))

3. Array Indexing Is 1-Based

Make uses 1-based indexing for arrays. Empty brackets [] do NOT select a single item — they flatten/iterate arrays into strings.

// ❌ WRONG — does NOT select a single value
{{50.data.results[].id}}

// ✅ CORRECT — selects the first element (1-indexed)
{{50.data.results[1].id}}

// ✅ CORRECT — flattens array to comma-separated string (for display/concat)
{{1.data.tags[]}}

4. NEVER Edit Blueprints with Text-Based File Edit Tools

Blueprint JSON files have very long lines with complex multi-level escaping. Text-based find-and-replace tools (multi_replace_file_content, replace_file_content) will corrupt the file because:

  • Special characters in target content don't match exactly
  • Partial matches can replace schema definitions instead of actual body content
  • Broken continuation lines get injected

Always use Python to read, modify, and write blueprint JSON:

import json

with open('blueprint.json', 'r', encoding='utf-8') as f:
    bp = json.load(f)

# Navigate the structure programmatically
for item in bp['flow']:
    for route in item.get('routes', []):
        for module in route.get('flow', []):
            mapper = module.get('mapper', {})
            if 'jsonStringBodyContent' in mapper:
                body = json.loads(mapper['jsonStringBodyContent'])
                # Modify body fields as needed
                body['name'] = '{{1.data.full_name}}'
                mapper['jsonStringBodyContent'] = json.dumps(body, separators=(',', ':'))

with open('blueprint.json', 'w', encoding='utf-8') as f:
    json.dump(bp, f, indent=4, ensure_ascii=False)

Blueprint Structure

Top-Level Anatomy

{
    "name": "Scenario Name",
    "flow": [
        { "id": 1, "module": "trigger-module:watch", ... },
        { "id": 2, "module": "google-sheets:addRow", ... },
        { "id": 25, "module": "util:SetVariable2", ... },
        { "id": 50, "module": "http:ActionSendData", ... },
        {
            "id": 60, "module": "builtin:BasicRouter",
            "routes": [
                {
                    "flow": [ { "id": 70, ... }, { "id": 71, ... } ],
                    "filter": { "conditions": [...] }
                },
                {
                    "flow": [ { "id": 80, ... }, { "id": 81, ... } ],
                    "filter": { "conditions": [...] }
                }
            ]
        }
    ],
    "metadata": { "instant": true, "version": 1 }
}

Module Types Reference

Module StringType
facebook-lead-ads:watchLeadsFacebook Lead Ads Trigger
google-sheets:addRowGoogle Sheets - Add Row
http:ActionSendDataHTTP - Make a Request (POST/PATCH/PUT)
http:ActionGetDataHTTP - Get Data (GET)
util:SetVariable2Tools - Set Variable
builtin:BasicRouterRouter (with routes/filters)
json:ParseJSONJSON - Parse
builtin:BasicFeederIterator
builtin:BasicAggregatorAggregator
gateway:CustomWebHookCustom Webhook Trigger
util:FunctionSleepTools - Sleep

HTTP Module (http:ActionSendData) Mapper Fields

{
    "mapper": {
        "url": "https://api.example.com/v1/resource",
        "method": "post",
        "headers": [
            {"name": "Authorization", "value": "Bearer {{token}}"},
            {"name": "Content-Type", "value": "application/json"}
        ],
        "contentType": "json",
        "inputMethod": "jsonString",
        "jsonStringBodyContent": "{...double-encoded JSON...}",
        "parseResponse": true,
        "stopOnHttpError": true,
        "allowRedirects": true,
        "shareCookies": false,
        "requestCompressedContent": true
    }
}

Router Filter Conditions

Filters use nested arrays with operator objects:

{
    "filter": {
        "name": "Route Name",
        "conditions": [
            [
                {
                    "a": "{{50.data.count}}",
                    "b": "0",
                    "o": "number:equal"
                }
            ]
        ]
    }
}

Common operators: number:equal, number:notEqual, number:greater, number:less, text:equal, text:contain, text:startsWith, exist, notExist.

Set Variable Module — Roleta (Round-Robin Distribution)

A roleta is a common pattern for distributing incoming data (leads, tasks, tickets) among team members. It uses a Set Variable module with random to assign items probabilistically.

When to Create a Roleta

  • The client needs to distribute leads/tasks equally among 2+ agents
  • CRM APIs require an attendantId or assigneeId on creation
  • The user mentions "roleta", "round-robin", "distribuição", or "distribuir leads"

Blueprint Structure

Place the Set Variable module before any HTTP module that needs the assigned value. The variable name should be descriptive (e.g., roleta_crm, assigned_agent, responsavel).

2-way split (50/50):

{
    "id": 25,
    "module": "util:SetVariable2",
    "version": 1,
    "parameters": {},
    "mapper": {
        "name": "roleta_crm",
        "scope": "roundtrip",
        "value": "{{if(random > 0.5; \"UUID-AGENT-A\"; \"UUID-AGENT-B\")}}"
    }
}

3-way split (33/33/33):

{
    "value": "{{if(random < 0.33; \"UUID-A\"; if(random < 0.66; \"UUID-B\"; \"UUID-C\"))}}"
}

4-way split (25% each):

{
    "value": "{{if(random < 0.25; \"UUID-A\"; if(random < 0.5; \"UUID-B\"; if(random < 0.75; \"UUID-C\"; \"UUID-D\")))}}"
}

Weighted split (70/30):

{
    "value": "{{if(random > 0.3; \"UUID-SENIOR\"; \"UUID-JUNIOR\")}}"
}

Wiring into HTTP Modules

Reference the variable in downstream modules using {{<moduleId>.<variableName>}}:

// In the HTTP body content (as a plain dict before double-encoding):
{
    "assigneeId": "{{25.roleta_crm}}",
    "attendant": {"id": "{{25.roleta_crm}}"}
}

The same variable can be referenced in multiple modules (e.g., both "Create Lead" and "Create Deal" modules).

Python Generation Pattern

def create_roleta_module(module_id, var_name, agents, position_x=900, position_y=0):
    """Generate a Set Variable module for round-robin distribution.
    
    Args:
        module_id: Unique module ID (int)
        var_name: Variable name (e.g., 'roleta_crm')
        agents: List of agent UUIDs to distribute among
        position_x, position_y: Canvas position
    """
    if len(agents) == 2:
        value = f'{{{{if(random > 0.5; "{agents[0]}"; "{agents[1]}")}}}}'
    else:
        # Build nested if() for N agents
        parts = []
        for i, agent in enumerate(agents[:-1]):
            threshold = round((i + 1) / len(agents), 2)
            parts.append(f'if(random < {threshold}; "{agent}"; ')
        value = '{{' + ''.join(parts) + f'"{agents[-1]}"' + ')' * (len(agents) - 1) + '}}'
    
    return {
        "id": module_id,
        "module": "util:SetVariable2",
        "version": 1,
        "parameters": {},
        "mapper": {
            "name": var_name,
            "scope": "roundtrip",
            "value": value
        },
        "metadata": {
            "designer": {"x": position_x, "y": position_y},
            "restore": {"expect": {"scope": {"label": "One cycle"}}}
        }
    }

Facebook Lead Ads Field Mapping

Facebook Lead Ads fields are accessed via <moduleId>.data.<internal_name>. The internal names are defined during form creation and may not be in English.

Discovering Field Names

Check the module's output interface in the blueprint JSON under interface.outputdata.spec:

{
    "name": "data",
    "type": "collection",
    "label": "Field data",
    "spec": [
        {"name": "full_name", "type": "text", "label": "Full name"},
        {"name": "email", "type": "text", "label": "Email"},
        {"name": "phone", "type": "text", "label": "Phone number"},
        {"name": "custom_field", "type": "array", "label": "Custom question label"}
    ]
}

The name field is what you use in expressions: {{1.data.full_name}}, NOT the label.

Custom Fields (Arrays)

Custom fields from Facebook forms come as arrays. Use [] to flatten to a string:

{{1.data.custom_field[]}}

Fields with hyphens or special characters require backticks:

{{1.data.`field-with-hyphens`[]}}

Other Trigger Data

Beyond form fields, the trigger also provides metadata:

  • {{1.leadgenId}} — Lead ID
  • {{1.campaignName}} — Campaign name
  • {{1.adsetName}} — Ad set name
  • {{1.adName}} — Ad name
  • {{1.platform}} — Platform (e.g., fb, ig)
  • {{1.isOrganic}} — Whether the lead is organic

Debugging Common Make Errors

InvalidConfigurationError: "JSON body content is not valid JSON"

Cause: Escaped quotes inside Make expressions in jsonStringBodyContent (e.g., ifempty(val; "text")). Fix: Remove all ifempty / if calls that use string literals with quotes inside the body content. Use direct field references or pre-compute in a Set Variable module.

HTTP 400: Required field is empty

Cause: Wrong field path (e.g., 1.data.full_name when the internal name is 1.data.nome) or wrong module ID prefix. Fix: Check the trigger module's interface.output spec for correct internal field names.

HTTP 400: Invalid field value (e.g., "email must be valid")

Cause: A fallback like ifempty(field; "null") sends literal string "null" as an email address. Fix: Remove the fallback or use an empty string. Facebook Lead Ads forms with required fields always send values.

Incomplete Executions / DLQ (Dead Letter Queue)

Make pauses sequential scenarios when errors accumulate. Items in the DLQ carry the original request body — retrying won't pick up new module mappings. Fix: Delete DLQ items, fix the module, reactivate. Reprocess data from an earlier module (e.g., Google Sheets) if needed.

Module Output Not Available

Cause: Referencing a module that hasn't run yet or is in a different router branch. Fix: Only reference modules that are guaranteed to have executed in the current flow path. Modules in other router branches are not accessible.

Workflow: Creating a New Blueprint

  1. Define the flow: List all modules in order with their connections
  2. Build body objects in Python as plain dicts with Make expressions as string values
  3. Generate the blueprint structure programmatically using Python
  4. Double-encode all jsonStringBodyContent values with json.dumps(json.dumps(obj, separators=(',', ':')))
  5. Validate the final JSON with json.load() before delivering
  6. Test by importing in Make and running with a sample trigger

Workflow: Fixing an Existing Blueprint

  1. Load the blueprint with json.load()
  2. Navigate to the target module: bp['flow'] → routes → flow → mapper
  3. Parse existing body: json.loads(mapper['jsonStringBodyContent'])
  4. Fix the body dict (field names, paths, remove broken expressions)
  5. Write back: mapper['jsonStringBodyContent'] = json.dumps(body, separators=(',', ':'))
  6. Save: json.dump(bp, f, indent=4, ensure_ascii=False)
  7. Validate: Re-load the saved file with json.load() to confirm valid JSON
  8. Print all module bodies for visual verification before delivering

Validation Script Pattern

Always run this after editing any blueprint:

import json

with open('blueprint.json', 'r') as f:
    bp = json.load(f)
print("✅ Blueprint is valid JSON")

for item in bp['flow']:
    for route in item.get('routes', []):
        for module in route.get('flow', []):
            mapper = module.get('mapper', {})
            body = mapper.get('jsonStringBodyContent', '')
            url = mapper.get('url', '')
            method = mapper.get('method', 'post')
            if body:
                body_json = json.loads(body)
                print(f"\nModule {module['id']} ({method.upper()} {url}):")
                for k, v in body_json.items():
                    print(f"  {k}: {v}")

Output HTML (Apresentação ao Cliente)

Além do output em JSON (blueprint Make), gerar versão HTML estilizada quando solicitado para apresentação/documentação ao cliente:

Regras do HTML:

  1. Usar o design system gui.marketing (Inter Tight/Inter, bg #f7f3ed, accent #864df9)
  2. Documentar módulos, routers, filters e connections em tabelas/cards com o layout brand
  3. Header logo com link UTM: https://gui.marketing/?utm_source=esc-skills&utm_medium=deliverable&utm_campaign=guimkt-make-blueprint-expert&utm_content=header-logo
  4. Footer com link UTM: https://gui.marketing/?utm_source=esc-skills&utm_medium=deliverable&utm_campaign=guimkt-make-blueprint-expert&utm_content=footer
  5. Salvar como make-blueprint-{{CLIENTE}}.html

IMPORTANTE: O output principal continua sendo o JSON do blueprint Make. O HTML é um output adicional para documentação.