Ingest and serve API

Send rows into a dataset, start a pipeline run with parameters and read its results, and read the latest rows of a dataset, all over plain HTTPS with a secret URL.

Three integrations give a workspace an HTTP surface without any sign-in dance. Each one is a single secret URL created in Jsonify.

Integration Direction URL shape Purpose
Receive JSON webhook You send rows in POST https://factory.jsonify.com/api/ingest/<id> Each request writes a new version of a dataset worksheet
Pipeline Trigger You start a run POST https://factory.jsonify.com/api/ingest/<id> Starts one pipeline, optionally with parameters, and gives you URLs to follow it
Publish Dataset API You read rows out GET https://factory.jsonify.com/api/serve/<id> Returns the latest version of a dataset worksheet as JSON

<id> is the integration’s own identifier, shown on its detail page. A fourth integration, Fetch external JSON, goes the other way round: Jsonify fetches rows from a URL of yours. It is covered at the end.

The URL is the secret. There is no separate key, header or signature: anyone who has the URL can use it until the integration is deleted. Keep it out of shared documents and front-end pages, and if it leaks, delete the integration and create a new one; the old URL stops resolving the moment you delete it.

Receive rows over a webhook

Receive JSON webhook gives a dataset worksheet an address that other systems can post rows to: a form tool, an internal service, a partner’s export, a scheduled job.

Set it up

Option Values Default What it does
Label Text “Receive JSON webhook” How the integration appears in pickers
Target dataset A dataset, or + Create new dataset Required Rows posted to the URL land here
Target worksheet A worksheet of that dataset Required Which worksheet receives them

Press Connect; there is nothing to test. The integration is Live at once and its Overview tab shows the Ingest endpoint block with the URL, the dataset’s current version and when it last changed. The detail page’s Code tab holds a cURL sample and a second sample for your own program, each with a Copy button.

A Receive JSON webhook integration's Overview tab showing the Ingest endpoint block with the URL and a Copy link
The ingest endpoint. Copy the URL from here; it is the only credential.

Send rows

Post one JSON object, or an array of objects. Each object is a row; its keys are column names.

curl -X POST https://factory.jsonify.com/api/ingest/int7f2c9a1b3 \
  -H 'Content-Type: application/json' \
  -d '[
    {"product_url": "https://www.example-store.com/p/12345", "retailer": "Example Store", "price": 149.0, "currency": "GBP"},
    {"product_url": "https://www.example-store.com/p/12399", "retailer": "Example Store", "price": 189.0, "currency": "GBP"}
  ]'

The answer confirms what was written:

{"ok": true, "integration": "int7f2c9a1b3", "dataset": "dsa41b7c9e2f0", "version": 18, "rows_ingested": 2}

Every successful request creates a new dataset version. If the dataset has key columns, incoming rows replace rows with the same key and new keys are added; if it has none, the rows are appended below the existing ones. Keys become column names, so keep them consistent between requests. Anything in the array that is not an object is dropped.

After the version is written, the dataset does everything it does after a pipeline publish: alert rules are evaluated, sinks deliver, dashboards recompute. The dataset page shows the version with the source Ingested.

Errors

Answer Meaning
400 “response must be a JSON object or array of objects” The body was not JSON, or not an object or array
400 “No rows to write” An empty array, or an array with no objects in it
400 “Integration is not live” The integration is not Live. Open it and check its status
400 “This integration is configured for pull mode” You posted to a Fetch external JSON integration, which is read by Jsonify, not written to
404 “Integration not found” Wrong URL, or the integration was deleted
404 “Target dataset not found” The dataset was deleted. Open the integration and pick another

A partner emails a nightly CSV, and a small job on your side converts it to JSON and posts it to the ingest URL. The dataset Partner stock gains a version each night, an alert rule watches for products that disappeared, and a sheet mirrors it for the buying team. No pipeline involved.

Start a run from a webhook

Pipeline Trigger gives one pipeline an address that starts a run. Use it from a scheduler you already have, a deploy step, a Zapier zap, or anything that can send a request.

Set it up

Option Values Default What it does
Label Text “Run pipeline on POST” How the integration appears in pickers
Pipeline to run A pipeline with an active version Required Which pipeline each request starts

Press Connect. The Overview tab shows the Trigger endpoint block with the URL, followed by the pipeline’s parameters: each one’s name, type, default and description. Those are the fields you may send in the body. The pipeline’s own Run dialog also shows a banner, “Pipeline Trigger attached”, linking back to the integration, so anyone running it by hand knows it can also be started from outside.

Start a run

An empty body starts the pipeline with its defaults, exactly as pressing Run would:

curl -X POST https://factory.jsonify.com/api/ingest/intd3e8f1a2b4

To pass parameters, send them as top-level JSON fields. Two optional run controls sit alongside them: row_limit (the row cap for this run; 0 means a full run) and pipeline_version (run a specific version rather than the active one).

curl -X POST https://factory.jsonify.com/api/ingest/intd3e8f1a2b4 \
  -H 'Content-Type: application/json' \
  -d '{"region": "uk", "include_out_of_stock": false, "row_limit": 0}'

The nested form {"params": {"region": "uk"}, "row_limit": 0} is also accepted. Do not send the same parameter both ways; the request is refused. Parameters are validated exactly as in the Run dialog; a wrong type or an unknown name is a 400 with the reason.

The answer arrives as soon as the run is queued:

{
  "ok": true,
  "run_id": "mrun00000361",
  "pipeline": "marketpipe03",
  "status": "queued",
  "status_url": "https://factory.jsonify.com/api/ingest/intd3e8f1a2b4/runs/mrun00000361"
}

Only one run per pipeline can be active. While one is queued or running, another request gets 409 with "error": "Pipeline already has an active run" plus the active run_id and its status; nothing is queued behind it. Wait for the status URL to report a finished run, then post again.

Runs started this way show the trigger webhook on the runs list. If one fails persistently, it is eligible for automatic repair in the same way as a scheduled run.

Check the status

status_url is specific to the run and to the trigger that started it. Fetch it until status is completed, failed or cancelled. Every couple of seconds is plenty.

curl https://factory.jsonify.com/api/ingest/intd3e8f1a2b4/runs/mrun00000361
{
  "ok": true,
  "run_id": "mrun00000361",
  "pipeline": "marketpipe03",
  "status": "completed",
  "created_at": 1789452131,
  "finished_at": 1789452498,
  "results": [
    {
      "dataset": "marketdata02",
      "dataset_name": "Price observations",
      "version": 61,
      "table": "main",
      "row_count": 1184,
      "result_url": "https://factory.jsonify.com/api/ingest/intd3e8f1a2b4/runs/mrun00000361/results/marketdata02?table=main"
    }
  ],
  "results_truncated": false
}

results is empty until the run completes. A failed run adds an error field. A run that published to more than 100 datasets lists the first 100 and sets results_truncated to true; open the run in Jsonify for the rest.

Read the results

Each result_url returns the exact dataset version the run produced, a page at a time. limit is at most 100 rows; follow next_url while has_more is true.

curl 'https://factory.jsonify.com/api/ingest/intd3e8f1a2b4/runs/mrun00000361/results/marketdata02?table=main&offset=0&limit=100'
{
  "ok": true,
  "run_id": "mrun00000361",
  "pipeline": "marketpipe03",
  "dataset": "marketdata02",
  "dataset_name": "Price observations",
  "version": 61,
  "table": "main",
  "row_count": 1184,
  "offset": 0,
  "limit": 100,
  "rows": [ { "...": "..." } ],
  "has_more": true,
  "next_url": "https://factory.jsonify.com/api/ingest/intd3e8f1a2b4/runs/mrun00000361/results/marketdata02?table=main&offset=100&limit=100"
}

Asking for results before the run has completed answers 409 “Trigger run is not completed”. Because the version is pinned, you can page through it at leisure; a later run does not change what this URL returns.

Answer Meaning
400 “Invalid run config:” A parameter failed validation, or was supplied both directly and inside params
409 “Pipeline already has an active run” Wait for the current run, then post again
409 “Pipeline has no active revision” The pipeline has never been activated; finish its first build in Jsonify
404 “Trigger run not found” The run was not started by this trigger, or the identifier is wrong
409 “Trigger run is not completed” Results were requested before the run finished

A deployment step posts to the trigger after each catalogue import, waits on the status URL, then pages the result into the search index. The run’s row cap, parameters and version are all in the request, so the step needs nothing from the Jsonify UI.

Serve a dataset over HTTP

Publish Dataset API gives a dataset worksheet an address that returns its latest rows. Your app, your BI tool’s web connector, or a scheduled loader fetches it whenever it likes.

Set it up

Option Values Default What it does
Label Text “Publish dataset API” How the integration appears in pickers
Source dataset A dataset Required The dataset whose rows are returned
Source worksheet A worksheet of that dataset Required Which worksheet is returned

Press Connect. The Overview tab shows the Serve endpoint block with the URL and the dataset’s current version.

Read rows

curl https://factory.jsonify.com/api/serve/int9c4e2b7d1a
{
  "integration": "int9c4e2b7d1a",
  "dataset": "marketdata01",
  "dataset_name": "Live product offers",
  "table": "main",
  "version": 42,
  "updated_at": 1789452498,
  "row_count": 3120,
  "rows": [
    {"product_url": "https://www.example-store.com/p/12345", "retailer": "Example Store", "title": "Espresso machine 15 bar", "price": 149.0, "currency": "GBP", "in_stock": true},
    {"...": "..."}
  ]
}

The endpoint always returns the active version of the worksheet, in full, as JSON. There are no query parameters: no paging, no filters, no other formats. A dataset that has not published yet answers with "version": null and an empty rows array. Compare version between fetches to know whether anything changed.

For a filtered or paged read, or CSV, use the command line or an MCP assistant instead; both sign in with a credential rather than a URL.

Serve URLs are read by whoever holds them. Do not embed one in a public web page or a shared notebook. For sharing a dataset with people, use a share link, which can be turned off and shows a proper page.

Fetch rows from your own endpoint

Fetch external JSON is the mirror of the ingest webhook: instead of you posting rows, Jsonify fetches them from a URL you own and writes them into a dataset. Use it for a read-only endpoint that already exists.

Option Values Default What it does
Label Text “Fetch external JSON” How the integration appears in pickers
Endpoint URL An http:// or https:// URL Required Where rows are fetched from. Must return JSON
Target dataset A dataset, or + Create new dataset Required Where the rows go
Target worksheet A worksheet of that dataset Required Which worksheet receives them
Auth header (optional) A full header line, for example Authorization: Bearer … Empty Sent on every fetch. Leave blank for an open endpoint

Connect fetches the endpoint once, without the auth header, and checks the answer is JSON. If your endpoint refuses unauthenticated requests, the check fails; make it answer with an empty JSON array for unauthenticated calls, or allow Jsonify’s check through some other way, then save.

The endpoint may return an array of objects, or an object whose data, results, items or rows key holds the array.

Like a pulled sheet, this source has no timer of its own: Jsonify fetches it before every run of a pipeline that uses the dataset as an input, and when you sync the dataset by hand. Each fetch that returns rows writes a new version, merged by key when the dataset has key columns. See Pull rows from a sheet for the same behaviour explained for sheets.

Keeping URLs private

  • Store the URL where you keep secrets. A CI secret, a secret manager, an environment variable. Not in the request log of a shared tool.
  • One integration per caller. If two systems post to the same dataset, give each its own Receive JSON webhook. Then a leak, or a decommissioned system, costs you one delete rather than a migration.
  • Rotate by replacing. There is no rotate button for a URL. Create a new integration, move the caller to the new URL, then delete the old one. The old URL stops working the moment it is deleted; nothing else changes.
  • Watch for use. Ingest and trigger activity shows up as dataset versions with the source Ingested and as runs with the trigger webhook. An unexpected one is the signal to replace the URL.

What’s next

Connect your data assistant

Build datasets and work with your data in ChatGPT, Claude, Copilot or another assistant.

Connect in ChatGPT

  1. Open Settings → Security and login and enable Developer mode.
  2. Open Plugins and select + to create a connection. Name it Jsonify, add a short description, and paste the URL below.
  3. Use OAuth for authentication, select Create, and sign in to your Jsonify account when prompted.
  4. Start a new chat and select Jsonify from + → More, then describe your dataset.
Server URLhttps://factory.jsonify.com/mcp

If Developer mode is unavailable, your plan or workspace settings may restrict custom connections.

Official ChatGPT setup guide ↗

Then say: “build me a dataset of competitor product prices and availability, refreshed daily”. Full instructions per client on /connect.