Live: Tor + abuse feeds refreshed every 6 hours

LayerCall

Airtable

No-code

What exists today

Airtable's Run script action can fetch any HTTP API. The thing to know before you start: Airtable has no secret store, so the API key lives in the script body where every base collaborator can read it.

Setup

  1. 1Create an automation triggered by 'When record created' or 'When record matches conditions'.
  2. 2Add a Run script action and pass the ip, email and phone fields as input variables.
  3. 3fetch https://www.layercall.com/v1/score/user from the script.
  4. 4output.set the verdict, then add an Update record action that writes it back.
  5. 5Add a Verdict single-select and a Risk number field to the table first, or the update step has nowhere to write.

Code

// Airtable → Automations → Run script
//
// Add input variables named ip, email, phone in the left-hand panel and map
// them to the trigger record's fields.

const { ip, email, phone } = input.config();

let scored;
try {
  const res = await fetch("https://www.layercall.com/v1/score/user", {
    method: "POST",
    headers: {
      // Airtable has no secret store. See the pitfall below before
      // pasting a live key into a shared base.
      "X-Api-Key": "tl_live_REPLACE_ME",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ ip, email, phone }),
  });
  if (!res.ok) throw new Error(String(res.status));
  scored = await res.json();
} catch (e) {
  scored = { verdict: "Unscored", risk_score: 0, summary: String(e) };
}

output.set("verdict", scored.verdict);
output.set("risk", scored.risk_score);
output.set("summary", scored.summary ?? "");

What to do with each verdict

Three outcomes, and the middle one is the one worth getting right — refusing a real customer usually costs more than reviewing them.

VerdictIn Airtable
allowWrite the verdict back and let the rest of the automation run.
reviewWrite the verdict, then add a Conditional action group that notifies Slack with the summary. Airtable conditions read the script's output variables directly.
blockWrite the verdict and stop. Airtable cannot prevent the record being created — the trigger fires after it exists — so treat this as tagging, not gatekeeping.

What comes back

A real response, generated from the live API rather than written by hand. Branch on verdict; summary is a sentence written to be shown to a person, and components_checked tells you what actually went into the score.

Show the full response(POST /v1/score/user)
{
  "risk_score": 65,
  "verdict": "review",
  "summary": "Needs review (65/100) — Tor exit node, commercial VPN and datacenter ASN.",
  "components": {
    "email": {
      "email": "test@guerrillamail.com",
      "normalized_email": "test@guerrillamail.com",
      "risk_score": 100,
      "verdict": "block",
      "status": "do_not_mail",
      "sub_status": "disposable",
      "deliverability_score": 0,
      "did_you_mean": null,
      "signals": {
        "syntax_valid": true,
        "mx_found": true,
        "is_disposable": true,
        "is_homograph": false,
        "is_role_account": true,
        "is_free_provider": false,
        "is_suspicious_handle": true,
        "is_tagged": false,
        "is_risky_tld": false,
        "is_new_domain": null,
        "has_spf": true,
        "has_dmarc": true,
        "has_website": true,
        "mailbox_exists": null,
        "is_catch_all": null,
        "mailbox_status": "unavailable",
        "has_digital_footprint": null
      },
      "domain": "guerrillamail.com",
      "domain_age_days": null,
      "mx_provider": null,
      "mx_records": [
        "mail.guerrillamail.com."
      ],
      "abuse_reports": 0,
      "digital_footprint": {
        "has_gravatar": false,
        "gravatar_profile_url": null,
        "breach_count": null,
        "seen_in_breach": null
      }
    },
    "phone": {
      "parse_status": "ok",
      "phone": "+14155552671",
      "risk_score": 0,
      "signals": {
        "syntax_valid": true,
        "is_possible": true,
        "is_voip": false,
        "is_premium_rate": false,
        "is_toll_free": false,
        "assigned_area_code": true,
        "is_fictional": false
      },
      "number": {
        "e164": "+14155552671",
        "country": "US",
        "national": "(415) 555-2671",
        "international": "+1 415 555 2671",
        "line_type": "fixed_line_or_mobile"
      },
      "verdict": "allow",
      "abuse_reports": 0
    },
    "ip": {
      "ip": "185.220.101.1",
      "risk_score": 60,
      "signals": {
        "is_vpn": true,
        "is_proxy": true,
        "is_datacenter": true,
        "is_tor": true,
        "recent_abuse": false,
        "is_hijacked_netblock": false
      },
      "geo": {
        "country": "DE",
        "city": "Berlin",
        "asn": "AS60729",
        "isp": "Stiftung Erneuerbare Freiheit"
      },
      "vpn_provider": null,
      "hijacked_source": null,
      "verdict": "review",
      "abuse_reports": 0
    }
  },
  "components_checked": [
    "email",
    "phone",
    "ip"
  ],
  "linkage": {
    "device_email_count": null,
    "email_device_count": 0,
    "email_ip_count": 0,
    "subnet_rate_1h": 2,
    "domain_rate_1h": 0
  },
  "actor": {
    "type": "unknown",
    "proven": false,
    "basis": "none",
    "operator": null,
    "trigger": null,
    "detail": "No signature and no device fingerprint. Drop fp.js on the page, or pass the agent's signed request, to get an answer here."
  },
  "top_signals": [
    "ip: tor exit node",
    "ip: commercial vpn",
    "ip: datacenter asn",
    "email: disposable domain",
    "email: role account",
    "email: machine-generated handle"
  ]
}

Traps specific to Airtable

The key is visible to every base collaborator

There is no environment or secret store for automation scripts. Anyone with access to the base can open the automation and read it. Use a key minted for this purpose alone so you can rotate it without touching anything else, and never reuse your main key here.

Airtable cannot block a record — only tag it

The automation runs after the record exists. If you need to actually refuse something, do it upstream in the form handler; Airtable is the right place to triage, not to gatekeep.

Automation runs are capped by plan

Every triggered record burns one run. On a busy table a per-record automation exhausts the monthly allowance quickly — use 'When record matches conditions' to score only the rows that matter, not every insert.

Questions

Can I score existing rows in bulk?

Not from an automation — those are per-record. Use a Scripting extension in the base, which can loop a query and call /v1/batch with up to 500 values of one type per request.

What should the Verdict field type be?

Single select with allow, review, block and Unscored. Unscored matters: it is what the catch block writes, and without it a failed run leaves the previous verdict in place, which reads as a fresh clean result.