Skip to content
Gridlode
Docs · Integrations

Integrations

Gridlode is a plain HTTPS API, so any tool that can make a web request can use it. These guides cover the most common no-code tools, spreadsheets, CRMs and AI assistants. Writing code instead? Use the Python and Node.js SDKs.

What every integration needs

Base URLhttps://api.gridlode.com
Auth headerAuthorization: Bearer sk_live_… (create a key per tool in the dashboard)
EndpointThe engine's route, e.g. GET /v1/domain/tech?domain=example.com. Every route is in the API reference.
ParametersQuery string for GET, or a JSON body for POST
ResultThe engine's result is in data; credits.charged is what the call cost

Store the key in the tool's credential or secret store where it has one. Failed calls are never charged, your key is limited to a number of requests per second, and adding max_age=86400 accepts an answer up to a day old at a discount, which saves credits when a workflow re-runs on the same input.

Zapier

Webhooks by Zapier (no code)

  1. In your Zap, add an action step and choose Webhooks by Zapier, event GET.
  2. URL: https://api.gridlode.com/v1/domain/tech (or any engine route).
  3. Query String Params: domain mapped to a field from your trigger, for example the company website.
  4. Headers: Authorization with the value Bearer sk_live_….
  5. Test the step. Zapier flattens the response, so later steps can map fields such as Data Total or Data Technologies Name.

For engines with list or boolean parameters, use the POST event instead, set Payload Type to json and put the parameters in Data.

Code by Zapier

A Code step gives you full control over which fields come out:

Code by Zapier (JavaScript)
// Code by Zapier > Run JavaScript
// Input Data: domain = (map a field from your trigger), apiKey = sk_live_...
const res = await fetch(
  "https://api.gridlode.com/v1/domain/tech?domain=" + encodeURIComponent(inputData.domain),
  { headers: { Authorization: "Bearer " + inputData.apiKey } },
);
const body = await res.json();
if (body.status !== "success") throw new Error(body.error.code + ": " + body.error.message);

output = {
  technologies: body.data.technologies.map((t) => t.name).join(", "),
  total: body.data.total,
  credits_charged: body.credits.charged,
};

Bulk jobs with a Catch Hook

  1. Create a Zap with the trigger Webhooks by Zapier → Catch Hook and copy its URL.
  2. Create the job (from code, the dashboard or another Zap) with POST /v1/tasks and that URL as webhook_url. See Bulk jobs.
  3. When the job ends, the Zap receives {"event":"task.completed","task":{...}}. Add a GET step to https://api.gridlode.com/v1/tasks/Task Id/results?format=json with your Authorization header.

Because the Zap fetches the results with your own key, a forged webhook can at most trigger a read of your own job. To check the signature anyway, use Catch Raw Hook and the verification code from the n8n section in a Code step.

Make

  1. Add the HTTP → Make a request module to your scenario.
  2. URL: https://api.gridlode.com/v1/domain/whois, Method: GET.
  3. Authentication: choose API key authentication and create a keychain credential with the key Bearer sk_live_…, placement header, name Authorization. If your module version has no keychain option, add an Authorization header instead.
  4. Query String: add domain and map it from the previous module.
  5. Turn on Parse response so later modules can map data.registrar, data.expires and the rest.
  6. Keep Evaluate all states as errors on, and add an error handler route (for example Ignore or Resume) so a 424 from one bad domain doesn't stop the run. Errors are free.

For a bulk job, start a scenario with Webhooks → Custom webhook, use its address as webhook_url, then fetch /v1/tasks/{{task.id}}/results?format=json with an HTTP module and iterate over items with an Iterator.

n8n

Call an engine with the HTTP Request node

  1. Create a credential of type Header Auth: name Authorization, value Bearer sk_live_….
  2. Add an HTTP Request node: method GET, URL https://api.gridlode.com/v1/contacts/website.
  3. Authentication: Generic Credential Type → Header Auth → the credential above.
  4. Turn on Send Query Parameters and add domain = {{ $json.domain }}.
  5. Under Options → Batching, send for example 5 items every 1,000 ms to stay under your rate limit when the node runs over many items.

For list parameters such as DNS record types, use POST with Send Body as JSON, for example {"domain": "{{ $json.domain }}", "types": ["MX", "TXT"]}.

Receive the Gridlode webhook

  1. Add a Webhook trigger: HTTP method POST, path gridlode, respond Immediately. Under Options, turn on Raw Body so the exact signed bytes are kept.
  2. Activate the workflow and copy the Production URL.
  3. In another workflow (or the same one, from a manual trigger), create the job with an HTTP Request node: POST https://api.gridlode.com/v1/tasks with a JSON body such as {"engine": "domain.tech", "inputs": ["a.com", "b.com"], "field": "domain", "webhook_url": "<Production URL>"}. Save the webhook_secret from the response.
  4. After the Webhook node, add a Code node that verifies the signature (below), then an HTTP Request node to GET https://api.gridlode.com/v1/tasks/{{ $json.task.id }}/results?format=json, then Split Out on items.
n8n Code node: verify X-Gridlode-Signature
// Code node (Run Once for All Items), right after the Webhook node.
// Self-hosted n8n: allow the crypto module with NODE_FUNCTION_ALLOW_BUILTIN=crypto
const crypto = require('crypto');
const secret = 'whsec_...'; // the webhook_secret returned when you created the job

const raw = (await this.helpers.getBinaryDataBuffer(0, 'data')).toString('utf8');
const header = $input.first().json.headers['x-gridlode-signature'] || '';
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto.createHmac('sha256', secret).update(parts.t + '.' + raw).digest('hex');
const valid = parts.v1 && parts.v1.length === expected.length &&
  crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1)) &&
  Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
if (!valid) throw new Error('Invalid Gridlode signature');

return [{ json: JSON.parse(raw) }]; // { event: "task.completed", task: { id, status, ... } }

Google Sheets

A custom function lets you write =GRIDLODE("domain.dns", A2) in any cell. The API key is kept in the script's properties, not in the sheet.

  1. Open your spreadsheet and choose Extensions → Apps Script.
  2. Replace the contents of Code.gs with the script below and click Save.
  3. Open Project Settings (the gear icon), scroll to Script Properties, click Add script property and add GRIDLODE_API_KEY with your key as the value. Save.
  4. Back in the sheet, type a formula such as =GRIDLODE("url.metadata", A2, "title").
Code.gs
/**
 * Gridlode for Google Sheets: =GRIDLODE(engine, input, [path], [options])
 *
 * Setup: Extensions > Apps Script, paste this file and save. Then open Project Settings (gear icon) >
 * Script Properties > Add script property: GRIDLODE_API_KEY = sk_live_...
 * The key lives in the script's properties, never in a cell.
 */
var GRIDLODE_API = "https://api.gridlode.com";
var GRIDLODE_CACHE_SECONDS = 21600; // reuse answers for 6 hours so recalculation doesn't charge again

/**
 * Calls a Gridlode engine and returns a value from its result.
 *
 * @param {string} engine Engine slug, e.g. "domain.dns", "url.metadata" or "domain.tech".
 * @param {string} input The main input: a domain, URL, email address, phone number or query.
 * @param {string} path Optional field to return, e.g. "title" or "records.MX.value.exchange".
 * @param {string} options Optional extra parameters, e.g. "types=A,MX" or "max_age=0".
 * @return The value at path (lists joined with commas), or the whole result as JSON.
 * @customfunction
 */
function GRIDLODE(engine, input, path, options) {
  if (!engine || input === "" || input === null || input === undefined) return "";
  var cache = CacheService.getScriptCache();
  var cacheKey = gridlodeHash_([engine, input, options || ""].join("|"));
  var hit = cache.get(cacheKey);
  var data;
  if (hit) {
    data = JSON.parse(hit);
  } else {
    data = gridlodeRequest_(String(engine).trim(), input, options);
    var text = JSON.stringify(data);
    if (text.length < 90000) cache.put(cacheKey, text, GRIDLODE_CACHE_SECONDS);
  }
  return gridlodeFormat_(path ? gridlodePick_(data, String(path).split(".")) : data);
}

function gridlodeRequest_(engine, input, options) {
  var key = PropertiesService.getScriptProperties().getProperty("GRIDLODE_API_KEY");
  if (!key) throw new Error("Add GRIDLODE_API_KEY under Apps Script > Project Settings > Script Properties.");
  var info = gridlodeEngine_(engine);
  var params = { max_age: "86400" }; // accept Gridlode's own cache (billed at a discount); override with "max_age=0"
  params[info.field] = String(input).trim();
  String(options || "").split("&").forEach(function (pair) {
    var i = pair.indexOf("=");
    if (i > 0) params[decodeURIComponent(pair.slice(0, i).trim())] = decodeURIComponent(pair.slice(i + 1).trim());
  });
  var query = Object.keys(params).map(function (k) {
    return encodeURIComponent(k) + "=" + encodeURIComponent(params[k]);
  }).join("&");
  var url = GRIDLODE_API + info.route + "?" + query;

  for (var attempt = 0; attempt < 4; attempt++) {
    var res = UrlFetchApp.fetch(url, { headers: { Authorization: "Bearer " + key }, muteHttpExceptions: true });
    var status = res.getResponseCode();
    if (status === 429 || status >= 500) {
      var headers = res.getHeaders();
      var wait = Number(headers["Retry-After"] || headers["retry-after"] || attempt + 1);
      Utilities.sleep(Math.min(wait, 5) * 1000);
      continue;
    }
    var body = JSON.parse(res.getContentText());
    if (status >= 400 || body.status === "error") {
      throw new Error(body.error ? body.error.code + ": " + body.error.message : "HTTP " + status);
    }
    return body.data;
  }
  throw new Error("Gridlode is busy. Try again in a moment.");
}

/** Maps an engine slug to its route and main parameter, from the public engine list. */
function gridlodeEngine_(slug) {
  var cache = CacheService.getScriptCache();
  var cached = cache.get("gridlode:engines");
  var map;
  if (cached) {
    map = JSON.parse(cached);
  } else {
    var list = JSON.parse(UrlFetchApp.fetch(GRIDLODE_API + "/v1/engines").getContentText()).engines;
    map = {};
    list.forEach(function (e) {
      var schema = e.params_schema || {};
      var field = (schema.required && schema.required[0]) || Object.keys(schema.properties || {})[0];
      map[e.slug] = { route: e.route, field: field };
    });
    cache.put("gridlode:engines", JSON.stringify(map), GRIDLODE_CACHE_SECONDS);
  }
  if (!map[slug]) throw new Error("Unknown engine " + slug + ". See https://gridlode.com/docs/engines");
  return map[slug];
}

/** Follows a dot path. On a list, it picks the rest of the path from every element. */
function gridlodePick_(value, parts) {
  for (var i = 0; i < parts.length; i++) {
    if (value === null || value === undefined) return "";
    if (Array.isArray(value) && !/^[0-9]+$/.test(parts[i])) {
      var rest = parts.slice(i);
      return value.map(function (v) { return gridlodePick_(v, rest); });
    }
    value = value[parts[i]];
  }
  return value;
}

function gridlodeFormat_(value) {
  if (value === null || value === undefined) return "";
  if (Array.isArray(value)) {
    var flat = [].concat.apply([], value);
    if (flat.every(function (v) { return v === null || typeof v !== "object"; })) {
      return flat.filter(function (v) { return v !== null && v !== ""; }).join(", ");
    }
    return JSON.stringify(value).slice(0, 50000);
  }
  if (typeof value === "object") return JSON.stringify(value).slice(0, 50000);
  if (typeof value === "string") return value.slice(0, 50000);
  return value;
}

function gridlodeHash_(text) {
  var bytes = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, text, Utilities.Charset.UTF_8);
  return "gridlode:" + Utilities.base64EncodeWebSafe(bytes);
}
FormulaReturns
=GRIDLODE("domain.dns", A2, "records.MX.value.exchange")The domain's mail servers, comma separated
=GRIDLODE("domain.dns", A2, "records.A.value", "types=A")IPv4 addresses (resolving only A records)
=GRIDLODE("url.metadata", A2, "title")The page title
=GRIDLODE("domain.tech", A2, "technologies.name")Detected technologies
=GRIDLODE("contacts.email_check", A2, "verdict")deliverable_unknown, risky or undeliverable
=GRIDLODE("domain.rdap", A2)The whole result as JSON
  • The second argument fills the engine's main parameter (domain, url, email, q…). Pass other parameters in the fourth argument as name=value&name=value.
  • Results are cached in the script for six hours and the function asks for Gridlode's own cache (max_age=86400), so reopening or recalculating the sheet doesn't charge full price again. Pass "max_age=0" to force a fresh lookup.
  • Everyone who can edit the spreadsheet can open the script project and see its properties. Use a dedicated key for the sheet and revoke it if the sheet is shared widely.
  • Each custom function call has a 30-second limit. For thousands of rows, a bulk job with a CSV download is faster and easier on your rate limit.

Clay

  1. In your table, click Add column → Add enrichment and choose HTTP API.
  2. Method: GET. Endpoint: https://api.gridlode.com/v1/domain/tech?domain= followed by the Domain column (type / to insert it).
  3. Headers: Authorization = Bearer sk_live_….
  4. Run it on a few rows, then use Add columns from response (or a formula column) to pull out fields such as data.total or data.technologies.
  5. Set the column to run on new rows only, and add a run condition (for example "Domain is not empty") so you don't pay for blank rows. If the column has a rate-limit setting, keep it under your plan's requests per second.

Other useful enrichments: /v1/contacts/website for emails, phones and social profiles from a company site, /v1/contacts/email-check to validate addresses, and /v1/domain/whois for registration dates.

HubSpot

The usual pattern is: a workflow sends the record to a small endpoint, the endpoint calls Gridlode, and the result is written back to a HubSpot property.

  1. Create a custom property for the result, for example a company property Tech stack (multi-line text).
  2. Build a workflow, for example company-based, enrolling companies whose Company domain name is known and Tech stack is unknown.
  3. Add a Send a webhook action that POSTs the record ID and domain to your endpoint: an n8n or Make scenario, or your own service.
  4. In that endpoint, call GET https://api.gridlode.com/v1/domain/tech?domain=… and write the result back with HubSpot's CRM API: PATCH /crm/v3/objects/companies/{companyId} with {"properties": {"tech_stack": "…"}}, authenticated with a private app token.

With a HubSpot plan that includes custom code actions, you can skip the endpoint: store the key as a secret, call Gridlode from the action and copy its output into the property with an Edit record action.

HubSpot custom code action
// Workflow > Custom code action (Node.js). Secrets: GRIDLODE_API_KEY. Property to include: domain
exports.main = async (event, callback) => {
  const domain = event.inputFields["domain"];
  const res = await fetch("https://api.gridlode.com/v1/domain/tech?domain=" + encodeURIComponent(domain), {
    headers: { Authorization: "Bearer " + process.env.GRIDLODE_API_KEY },
  });
  const body = await res.json();
  if (body.status !== "success") throw new Error(body.error.code + ": " + body.error.message);
  callback({
    outputFields: { tech_stack: body.data.technologies.map((t) => t.name).join(", ") },
  });
};

Claude, Cursor and other AI agents

Gridlode runs a remote MCP server at https://api.gridlode.com/mcp. Every engine becomes a tool your assistant can call, billed like the REST API. Add it to Claude Code with one command:

Claude Code
claude mcp add --transport http gridlode https://api.gridlode.com/mcp \
  --header "Authorization: Bearer YOUR_API_KEY"

For Claude Desktop, Cursor and other clients, add the server to the client's MCP configuration:

mcp.json
{
  "mcpServers": {
    "gridlode": {
      "type": "http",
      "url": "https://api.gridlode.com/mcp",
      "headers": { "Authorization": "Bearer YOUR_API_KEY" }
    }
  }
}

Then ask, for example, "Which mail provider does stripe.com use?" or "Find the contact emails on these five websites." Tool names, testing with curl and more are in the MCP server guide.