Back to blog

Guides · Jun 3, 2026

Normalize messy API responses to clean JSON before Zapier, Make, and n8n

Third-party APIs never agree on shape: snake_case here, nested prices there, missing fields everywhere. Put a Crontap schedule in front and let an AI integration reshape each run's response into one clean JSON object before it reaches your Zap, Scenario, or Workflow.
crontap.com / blog
Stop rewriting Code steps in Zapier, Make, and n8n. Put a Crontap schedule in front to normalize messy API responses to one clean JSON shape.

You know the step. It is the Code by Zapier action, or the Function module in Make, or the Code node in n8n, that sits right after "Catch Hook" and does nothing glamorous: it takes whatever the upstream API just handed you and beats it into the shape the rest of your automation expects. Rename product_id to id. Coerce "1395.00" (a string, of course) into a number. Decide whether availability: "in_stock" and is_available: "yes" and stockCount: 0 all mean the same thing. You wrote it once, it worked, you moved on.

Then the vendor shipped a "minor API update" and your Zap started writing null into a column that used to have prices in it. So you open the Code step again. You add another branch. You ship it. Three weeks later a different vendor does the same thing, and now you are maintaining the same hand-rolled normalizer in four places, in three different tools, each with its own dialect of JavaScript and its own way of reading the input bundle.

This post is about deleting that step. Not improving it: deleting it. You put a Crontap schedule in front of the messy API, let an AI integration reshape each run's response into one fixed JSON schema, and forward that clean object to your Zap, Scenario, or Workflow. Your downstream automation stops parsing surprises and starts receiving the same predictable object every time.

Why the obvious fixes are awkward

There are three ways people usually deal with shape drift, and all three have a tax.

Per-tool Code or Function steps. This is the default, and it scatters. The Zapier Code step does not know about the Make Function module, which does not know about the n8n Code node. The same normalization logic lives in three editors with three slightly different runtimes (Zapier runs Node or Python in a sandbox, Make has its own functions, n8n gives you a Code node). When the API changes, you fix it three times, and you only find out it broke when a downstream record looks wrong. The logic is brittle because it is imperative: you are branching on every shape you have personally seen, and the next shape you have not seen yet wins.

Formatter and mapping nodes. Every platform has a "map this field to that field" tool, and they are genuinely good at the boring 80%: rename a key, format a date, pick a value out of a path. What they cannot do is reason. They cannot look at {"pricing": {"amount": "1395.00"}} from one vendor and {"amount_minor": "139500"} from another and understand that both mean 1395 dollars and should land in the same priceCents field. The moment the variance is semantic rather than positional, a static mapper taps out and hands you back to the Code step.

A transform microservice. The "real engineer" answer: stand up a tiny service that owns the schema, validates with JSON Schema, and exposes one clean endpoint. This is the correct shape for a high-volume strict-contract pipeline, and we will come back to it. But for "I have six webhooks that occasionally drift," it is a deploy, a host, a secret, an on-call surface, and a thing to remember exists in eight months. That is a lot of ceremony to rename some keys.

The gap in the middle is: something that reasons about messy, variant payloads like a Code step, lives in one place like a microservice, but costs you a prompt instead of a deploy. That is the shape this builds.

The in-product shape

Crontap's AI Integrations card sits on a schedule's form, right next to the webhook Integrations card. After the schedule's HTTP run finishes, Crontap takes that one run's response body, transforms it with a model using your plain-English prompt, and forwards the result to a URL you pick. Set the output format to JSON and you get back a parsed object, not a string.

Messy API response  →  Crontap schedule run  →  AI transform (JSON mode)  →  Zapier / Make / n8n webhook

The important word is "transform." This is not a summary and not a digest. In JSON mode you are asking the model to read this run's response and emit a structured object that matches a schema you describe in the prompt. It only ever sees that single run's response body (truncated to about 100KB, and treated strictly as untrusted data) plus a little run metadata: the status code, whether the run was ok or failed, how long it took, and the response size. No browsing, no tools, no memory of previous runs. It reads one payload and returns one object. That is exactly the scope of work a normalizer needs, and nothing more. For the use-case framing, this is a scheduled AI job doing automated data sync without a backend route in the middle.

Setting it up

Three fields do the work.

1. Write a prompt that names the exact target schema. Do not ask for "clean JSON." Ask for these keys, with these types, with these rules. The more concrete you are, the more boring and repeatable the output gets.

Convert the response into a single JSON object with exactly these keys:
- id (string): the product's unique identifier, whatever field it lives in
- name (string): the human-readable product name
- priceCents (integer): the price in minor units. If the price is a decimal
  amount like "1395.00", multiply by 100 and round to an integer.
- currency (string): the 3-letter ISO code, uppercase. Default to "USD" if absent.
- inStock (boolean): true if the item is purchasable (a positive stock count,
  or a status like "in_stock" or "yes"), false otherwise.
- updatedAt (string): an ISO 8601 timestamp. If you only have a Unix epoch,
  convert it.
 
Use null for any value you genuinely cannot determine. Do not add other keys.

2. Set Output format to JSON. Text mode returns a string; JSON mode returns a parsed object, which is what your downstream wants to consume directly. Describe the shape in the prompt (as above), because JSON mode guarantees you get valid parsed JSON back, not that it matches your schema field-for-field.

3. Forward to URL. Paste your destination webhook: a Zapier Catch Hook, a Make custom webhook, an n8n Webhook node, or your own endpoint. Optionally flip on "Include schedule URL" so the forwarded payload carries the schedule's URL too, and leave "Also run on failure" off unless you want error responses normalized as well.

AI integrations are a Pro feature: Pro starts at $2.99/mo, and the card has a "Perform test" button on every tier, so you can watch a real payload get reshaped before you ever save the schedule.

Fix this in 60 seconds with Crontap. Free tier available. No credit card. Schedule your first job →

Worked example: three messy inputs, one clean object

Here is the same product, "Aeron Chair", as three different vendors might return it. This is the variance that breaks static mappers.

Vendor A: snake_case, nested pricing, price as a decimal string, a non-ISO timestamp.

{
  "product_id": "SKU-118",
  "title": "Aeron Chair",
  "pricing": { "amount": "1395.00", "currency_code": "usd" },
  "availability": "in_stock",
  "last_modified": "2026-06-03 08:14:22"
}

Vendor B: camelCase, flat, price as a whole number, stock as a count, no timestamp.

{
  "sku": "SKU-118",
  "productName": "Aeron Chair",
  "price": 1395,
  "currency": "USD",
  "stockCount": 12
}

Vendor C: terse keys, price already in minor units but as a string, a stringy boolean, a Unix epoch, no currency at all.

{
  "code": "SKU-118",
  "label": "Aeron Chair",
  "amount_minor": "139500",
  "is_available": "yes",
  "ts": 1749024862
}

With the prompt above and Output set to JSON, all three collapse into the same object. The model handles the parts a mapper cannot: it reads "1395.00" as a decimal and "139500" as minor units and lands both on the same integer, it defaults Vendor C's missing currency to USD, and it converts the epoch and the loose datetime to ISO 8601.

{
  "id": "SKU-118",
  "name": "Aeron Chair",
  "priceCents": 139500,
  "currency": "USD",
  "inStock": true,
  "updatedAt": "2026-06-03T08:14:22Z"
}

What actually arrives at your Make, Zapier, or n8n webhook is that clean object wrapped in Crontap's forward envelope. The reshaped object is aiOutput (already a parsed object in JSON mode, so no JSON.parse on your side), sitting alongside the run metadata, a link back to the schedule, a timestamp, and the schedule URL if you enabled it.

{
  "aiOutput": {
    "id": "SKU-118",
    "name": "Aeron Chair",
    "priceCents": 139500,
    "currency": "USD",
    "inStock": true,
    "updatedAt": "2026-06-03T08:14:22Z"
  },
  "failed": false,
  "statusCode": 200,
  "statusOk": true,
  "duration": 412,
  "durationUnit": "ms",
  "size": "1.2 kB",
  "verb": "POST",
  "goToUrl": "https://crontap.apihustle.com/crontap/schedule/AbC123",
  "timestamp": 1749024902000,
  "url": "https://api.vendor.com/v2/products/SKU-118"
}

In Zapier you point everything downstream at aiOutput.priceCents. In Make you reference aiOutput in the next module. In n8n you read $json.aiOutput. The vendor's dialect never reaches your automation, because the normalization happened before the webhook fired.

Code step vs AI normalize

Code / Function step (per tool)AI normalize (one place)
Where it livesInside each Zap, Scenario, and Workflow, duplicated per automationOne Crontap schedule in front, shared by every downstream
Handles variant shapesYou branch on every shape you have seen; the next one breaks itThe model reshapes variant payloads to one target schema
Reasons about missing fieldsHand-written null checks and defaults you maintainThe prompt states the defaults; the model fills them or returns null
Maintenance when the API driftsEdit the code step in every tool that touches that APIEdit one prompt
CostTool task or operation counts, plus your time per fixOne scheduled run plus one model call per run

The honest column is "cost." A Code step is "free" in the sense that you already pay for the tool, but it is not free in attention, and attention is the resource that actually runs out when you have a dozen of these.

Reliability notes (read these before you trust it)

A normalizer you cannot trust is worse than no normalizer, so be clear-eyed about the edges.

JSON mode returns parsed JSON, not a guaranteed schema. You get a valid, parsed object back. You do not get a field-by-field guarantee that it matches the keys and types you described. The prompt is a strong instruction, not a contract. Treat the model as a very good normalizer that occasionally gets creative, not as a validator.

Oversized output is rejected, not truncated. Truncating JSON would corrupt it, so if the transformed object comes back too large, the integration rejects it rather than forwarding half an object. Keep your target schema tight: a flat object with the fields you actually need forwards cleanly and is cheap. If you find yourself asking the model to emit a 500-item array, that is a sign the upstream call should paginate or filter first.

Validate at the destination too. Belt and suspenders. The forwarded envelope carries statusCode, statusOk, and failed, so your Zap or Workflow can branch on a bad run before it touches aiOutput. If your contract is strict, add a quick shape check (a filter step, or real JSON Schema validation) right after the Catch Hook.

There is no cross-run memory in v1. The output is not stored and the model does not see previous runs. It normalizes each response in isolation. Do not lean on it to dedupe across runs or to remember "what the price was last time." That is the destination's job, or a job for a stateful route.

When to graduate

This pattern is for the messy middle: a handful of webhooks that drift, where a clean-enough object forwarded reliably beats a perfect object that took a sprint to build. Reach for something heavier when:

  • You run high volume and every cent of model cost and millisecond of latency matters.
  • You have a strict contract where a malformed object downstream is an incident, not an annoyance.
  • You need a hard schema guarantee, the kind only real validation gives you.

In those cases, build your own route: call the API, validate the extracted object against a real JSON Schema with a strict validator, and forward only what passes. The competitor pricing monitor with Firecrawl and GPT walks through exactly that DIY shape, structured outputs plus a server-side validator in a route you own, for when the contract has to hold.

FAQ

Does JSON mode guarantee my schema?

No. JSON mode guarantees you get back valid, parsed JSON (it arrives as aiOutput, already an object, not a string), and you describe the target shape in plain English in the prompt. It is not a JSON Schema validator, so if the contract is strict, add a shape check at the destination or graduate to your own validated route.

How big can the payload be?

The AI sees this run's response body truncated to roughly 100KB. Anything past that is cut before the model reads it, so if your API returns large lists, paginate, filter, or select fields at the source first. Smaller inputs also keep the transform fast and cheap.

Can I chain into Zapier, Make, or n8n directly?

Yes, that is the whole point. Set the forward URL to a Zapier Catch Hook, a Make custom webhook, or an n8n Webhook node. The envelope arrives as an ordinary POST with a JSON body, and your clean object is at aiOutput.

What if the API returns an error?

By default the AI integration only runs after a successful run. Turn on "Also run on failure" if you want error responses normalized too, for example to forward a clean, predictable error object instead of a raw 500 page. Either way the envelope carries failed, statusCode, and statusOk, so the destination can branch on the outcome.

What are the cadence limits?

AI integrations are a Pro feature. On Pro you get one AI integration per schedule at a minimum cadence of one day. On Ultra it is unlimited integrations per schedule at a minimum cadence of one hour. The card's "Perform test" button works on every tier, so you can try the transform before you commit to saving.

References

Related on Crontap

Fix this in 60 seconds with Crontap. Free tier available. No credit card. Schedule your first job →

From the blog

Read the blog

Guides, patterns and product updates.

Tutorials on scheduling API calls, webhooks and automations, plus deep dives into cron syntax, timezones and reliability.

Product Updates

Introducing AI Integrations

Transform a schedule's HTTP response with a plain-English prompt, return text or JSON, and forward it to Slack, Make, n8n, or your own endpoint. Test on any tier; saving is a Pro feature.

Alternatives

Vercel Cron every minute: beating the Hobby hourly limit

Vercel Cron caps Hobby at hourly cadence and 5 jobs, and ties every change to a redeploy. Here is the external cron pattern teams use to ship per-minute schedules, per-IANA timezones, and one dashboard across projects without paying $20/mo per user for Pro.

Alternatives

Cloud Run cron without Cloud Scheduler

Cloud Scheduler costs $0.10 per job per month after the first 3 and asks for OIDC plus IAM bindings on every target. Here is the IAM-free pattern Cloud Run teams use to fire their .run.app URLs on a clock with one bearer token and one dashboard across every GCP project.