Docs
API
Everything the app does, from an HTTP request: for Zapier, Make, Activepieces, n8n or your own code. One endpoint, one API key. Rows are built when you ask and not kept after they're delivered.
- URL
https://programmable.info/api/sheet- Method
POST- Header
Authorization: Bearer YOUR_API_KEY- Body (JSON)
{"domains": ["stripe.com"], "format": "json"}
Everything is on the API page in your dashboard: make an API key there, and its request builder writes the full request for whatever you pick.
1. Get an API key
There are two kinds of keys. Only API keys work for HTTP requests.
| Kind | Made in | Works for |
|---|---|---|
| API key | Developers | Any request: this API, live URLs, scripts. Full access to your account, so keep it in your tool's secret settings. |
| Share key | Developers → Links and share keys | Only the link it was made for (a Google Sheets formula, a shared link). It can be one-time, capped or expiring. It won't work in an HTTP step. |
API keys are listed in Developers and share keys in Developers > Links and share keys. Generated keys start with dsk_ and are 36 characters long; a key you choose yourself in Developers (see Live URLs below) works the same way.
2. Send the request
curl -X POST https://programmable.info/api/sheet \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"domains": ["stripe.com", "notion.so"], "columns": "company, tech stack", "format": "json"}'In an automation tool, the same thing goes in its HTTP step:
| Activepieces | HTTP → Send HTTP request. Authentication: Bearer Token. Body Type: JSON. |
| Zapier | Webhooks by Zapier → Custom Request (or POST). Add the header Authorization: Bearer … ; Data: the JSON body. |
| Make | HTTP → Make a request. Header Authorization: Bearer … ; Body type: Raw, JSON. |
| n8n | HTTP Request node. Authentication: Generic → Bearer Auth (or a header). Send Body: JSON. |
Everywhere: method POST, URL https://programmable.info/api/sheet, your API key as the Bearer token, and a JSON body. To use data from an earlier step, put its field inside the body, e.g. {"domains": ["{{website}}"]}.
3. Read the rows
With "format": "json" you get one document back once every row is done:
{
"rows": [
{ "domain": "stripe.com", "company_name": "Stripe", "industry": "Financial services", "cms": "Custom", ... },
{ "domain": "notion.so", "company_name": "Notion", ... }
],
"summary": { "rows": 2, "ok": 2, "failed": 0, "skipped": 0, "credits_charged": 2, "seconds": 3.1, "ai_cost_usd": null },
"errors": []
}Without format the response is a CSV that streams row by row (good for files and spreadsheets). Each row keeps your input columns, then the enrichment, and a row that couldn't be enriched says why in enrich_error.
The request waits for every row. Most tools allow a few minutes; for long lists, send "webhook": "on" and let your CRM webhook receive rows in batches as they finish.
Body fields
Send one of domains, people, linkedin or products. Everything else is optional.
| Field | Example | What it does |
|---|---|---|
| domains | ["stripe.com", "notion.so"] | Companies, by website. A list, or one string separated by commas or new lines. |
| input | "each_run" | each_run: the agent keeps no companies of its own; each run brings them (its link with company= or domains=, a Google Sheets cell, Zapier or n8n, or the companies typed into Run now). For "look up any store I give it", "research the company I send it", a lookup tool. Without companies nothing runs or is charged, and it never runs by itself. Instead of domains, discover, find or accounts. |
| people | ["Jane Doe, stripe.com"] | People as "Name, company domain", or their email address (kept and checked, not guessed over). Makes a people sheet (work email and more). |
| ["linkedin.com/in/jane-doe"] | LinkedIn profiles. A domain after each helps; with Apollo, Hunter, Findymail or PDL connected the profile alone is enough. | |
| products | ["https://www.allbirds.com/products/mens-tree-runners"] | Product page links: one row per product with price, stock, SKU/GTIN, variants and ratings, read from the store's own product data. |
| list | "products" | With domains: list each store's products (Shopify and WooCommerce catalogs, or product sitemaps), one row per product. |
| per_store | 100 | With list=products: most products per store (up to 2,000). |
| discover | "headless CMS vendors" | Find companies matching a description (your Apollo or PDL key if connected, otherwise the web), then enrich them. Instead of domains. |
| like | "stripe.com,paddle.com" | Find companies like these. Works alone or with discover. |
| location | "Austin, TX" | With discover, like, find (web or all) or a list of domains: where the companies are headquartered (city, state or country; several separated by semicolons, or "Austin, TX, Denver, CO"). Checked against what we find on each company; misses are skipped and not charged. |
| employees | "11-50" | With discover or like: company size range, e.g. 11-50, 200+ or under 20. Checked like location. |
| tech | "Webflow, HubSpot" | With discover or like: technologies the companies must use. Checked like location. |
| revenue / founded / funding / jobs | "10M-100M" / "2015+" / "5M+" / "yes" | More discovery filters: annual revenue (USD), founding year, total funding raised, and companies that are hiring (or a minimum number of open jobs). Your provider applies what it can (Apollo: revenue, funding, jobs; People Data Labs: founded); the rest is checked on each company. |
| accounts / who / columns | "target-accounts" / "buyers" / "call-prep" | Library items, named by their handle: a saved company list in place of domains (a list can be a saved search, resolved each run), a saved people filter (titles, seniority, departments; implies search=people), and a saved column set with its AI questions. Explicit parameters win over a Library item's. |
| exclude / to / profile | "customers" / "hubspot-flow" / "cheap" | More Library items by handle: a Do not contact list (websites never returned, from Find, discovery or an upload; a default list applies everywhere), where leads go (webhook, CRM, format) and a spending profile (providers, AI provider and model, caps, reveal). Explicit parameters win. |
| find | "sam" | Run a Find source for the companies instead of discover=: find=sam, formd, sec, contracts, web or any source in Data > Find, with that source's own parameters alongside (e.g. find=sam&naics=5415®isteredFrom=2026-07-01). The Find tab shows the exact link for any search. |
| describe | "payment processors" | With find=: plain words for what you want. Your AI screens the source's results and keeps the ones that fit. |
| sort | "largest" | Which companies to pick when there are more than the limit: largest, smallest, newest, oldest, most_funded or richest (by revenue). Needs a company-search provider; web search can't rank. |
| limit | 50 | With discover or like: how many companies to find (up to 500). |
| compare | 1 | With like: the example companies are rows too, first, with the same columns, so you can compare them with the ones found (a credit each). |
| keep | "performance_score<50, technologies~klaviyo" | Keep only rows where what the run found passes: <, <=, >, >= for numbers, = or != (yes/no), ~ or !~ for contains, on column keys, comma separated (all must pass). For what no database has ahead of time (site speed, the tech a site loads, hiring), measured during the run, PageSpeed too. Rows that don't pass are left out and not charged; discovery and Find look at about three times as many candidates so the limit can still be met. |
| columns | "company, tech stack, email setup" | Which column groups to return, in plain words or exact keys. Leave out for the defaults. It replaces the defaults, so add to them with default: "default, performance" is everything included plus PageSpeed scores. |
| ask | ["Do they offer a free plan?"] | Your own questions, answered per row with your AI key. Each becomes a column. |
| ai_research | 1 | Your questions also search the web (news, directories, docs) and cite the page each answer came from. Per question in JSON: {"prompt": …, "research": true}. |
| ai_max_usd | 2 | Most this request may spend on AI. null = no cap. Leave out for your account default. |
| search | "people" | Find people at the companies in domains. Use with titles, seniority and per_company. |
| titles | "sales, revenue" | Search: job titles to look for. |
| seniority | "vp,director" | Search: seniority levels. |
| per_company | 10 | Search: most people per company. |
| reveal | "email,mobile" | What your data providers may unlock (and spend credits on): email, mobile, personal_email, or none. |
| providers | "apollo,findymail" | Which of your connected data providers to use. Leave out for all; none for the included sources only. |
| apify | "none" | Skip the Apify actors saved in Connections for this run (they run on every company otherwise, on your Apify token). |
| send_to | "hubspot,salesforce" | Also send finished rows to the tools you've connected in Connections: hubspot, salesforce, shopify (people → customers, product sheets → draft products), slack and teams (a message per batch in the channel whose webhook address you pasted: company, person and title, and the AI answers, never emails), instantly, smartlead, lemlist, woodpecker, reply and apollo (people → leads in the campaign or sequence picked in Connections, with the columns as variables, or in Apollo as contact custom fields named like them; rows need an email good enough for the connection's setting). Apollo needs a master API key and a sending mailbox, and its leads wait in the sequence until you activate it there: pick an inactive sequence to review them first. google_sheets (rows go into a new spreadsheet in your Drive). A shared link key sends rows to your tools only when it's locked to its link and its owner ticked "May send to your tools"; one that may not start sending can't add leads to a campaign that's already running either. |
| campaign | "new" | With send_to=apollo, instantly, smartlead or reply: the first run writes a short email sequence from campaign_brief on your AI key and creates the campaign in that tool, paused; every later run of the same link adds its new leads to that same campaign. See "Links that act" below. |
| campaign_name | "Dentists, Austin" | With campaign=new: the new campaign's name (up to 80 characters; a different name is a different campaign); left out, the agent's name. Without campaign=new: the campaign of that name you already have in each cold-email tool in send_to (the exact name, any case, else the only one containing it), so a link can pick it instead of Connections. A tool with no campaign of that name gets no leads, and the run says so before it starts. |
| campaign_brief | "We do bookkeeping for dental clinics. Plain, friendly. Ask for a 15-minute call." | With campaign=new (required): what you sell, to whom, the tone, the call to action. Up to 1,500 characters. The emails use the link's own columns as merge fields: first name, company, the opening line and your AI columns. With site_review in columns, the first email shows each lead's own website review: {{review_score}} (out of 100), {{review_top_issue}} (its worst problem) and {{review_url}} (their review page); a lead whose review isn't finished yet is held and added by a later run, never sent with an empty field. |
| campaign_steps / campaign_days | "3" / "10" | With campaign=new: how many emails (1-5, default 3) and over how many days in all (default 10, at least a day between emails). |
| approval | "manual" | manual (the default): campaigns are created paused and leads wait for you to start them in the tool. In Smartlead and Reply.io you always start them yourself, whatever this says. agent (or your Agent settings' default): an AI reviewer on your key checks each lead and the emails first; what passes goes in, the rest is held for you. Only a link that says approval=agent itself starts a campaign it wrote, once its emails pass; the default reviews and holds but never starts anything. With min_fit, a lead whose fit can't be read is held in agent and auto runs (kept in manual). auto: only when the link itself says so, never as a default. Once this run's new leads are in, it starts a campaign this link created, or a campaign picked in Connections that has never been started; real email goes out from your mailboxes. A link starts a campaign at most once, never restarts a paused or finished one, and never starts a replacement for a deleted one. agent and auto need the team owner or an admin (or the account's own API key); a shared link key needs its owner to allow them. |
| hold | "1" | hold=1: every lead going to a cold-email tool waits in Approvals for the person, after the checks (fit, the email's domain, competitors); the ones they approve go into the campaign, still paused. For "hold them for my approval". Not with approval=auto. |
| min_fit | 7 | 1-10, with a fit question (fit_ask=N, else the question named fit, e.g. ai=[{"name":"Fit","prompt":"Score this company's fit 1-10…"}]): leads whose answer starts with a lower number aren't sent anywhere (they stay in the sheet; the summary counts them as below min fit). With search=people, fit is scored per company first and people are searched only at companies that pass, so a low-fit company costs no people search or emails. Defaults to your Agent settings in agent and auto modes. |
| fit_ask | 2 | Which of the link's AI questions (1-5, in order) is the fit score for min_fit. Leave out to use the one named fit. |
| review_rules | "No questions in the first line" | With approval=agent: extra rules for the AI reviewer, on top of the writing rules in your Agent settings (up to 500 characters). |
| screen | 0 | With discover: 1 (your Agent settings' default) screens the discovered companies against the description on your AI key before anything else; 0 doesn't. |
| crm_task / task_due | "1" / "3" | With send_to=hubspot or salesforce: after each record is written, a to-do on it (the contact or company in HubSpot, the Lead or Account in Salesforce), assigned to the record's owner and due in task_due days (0-60, default 3). The task names who to reach and why, from the row's AI answers. |
| event | "new" | new: the first run creates an event in Programmable Talk from event_title (required), event_starts_at (e.g. 2026-11-12T12:00:00-05:00), event_timezone (IANA, e.g. America/New_York), event_summary and event_platform_url (where people join), with growth playbooks on when the link sends growth playbooks, and later runs reuse it. Or your Talk event's slug or id. Every lead sent gets a Talk invite and {{event_url}}: their personal link that registers them in one click; Talk tracks opens and registrations and sends the playbook on registering. With playbook=, the playbook page's call to action is the event. Needs Programmable Talk connected in Connections and a cold-email tool in send_to. |
| event_title, event_starts_at, event_timezone, event_summary, event_platform_url, event_question | "Growth workshop", "2026-11-12T12:00:00-05:00" | The event event=new creates in Programmable Talk: its name (2-160 characters), start with its offset, IANA time zone (default UTC), what it's about (shown on its page), the https link people join at, and the one question registering asks (3-200 characters, e.g. their store's link and a competitor's). |
| playbook_cta, playbook_cta_label | "https://programmable.talk/e/growth-workshop", "Register for the workshop" | With playbook=only or preview: this link's call to action on the playbook page, in place of the one in Connections → Growth playbooks: an https link on your own site, your event's page (programmable.talk, lu.ma, Eventbrite) or a booking page, and the button's words (2-60 characters). |
| playbook | "only" | preview: like only, but the link opens a preview of the playbook (score, diagnosis, the ideas' titles, one sample idea) under your brand, with playbook_cta where the full playbook would be unlocked, e.g. an event's registration; each lead also gets {{playbook_opportunity}} and {{playbook_idea}} (and {{playbook_score}}) from the preview, which campaign=new uses in the first email. only: send only leads whose company has a growth playbook on programmable.marketing, each with its link as {{playbook_url}} (with campaign=new the emails offer it). The link opens the playbook under the sender's brand with "Talk to <sender>" (set up once in Connections → Growth playbooks; subscribers only). Leads without one are held, not sent and not counted as covered, and requested: once the playbook is made, a later run adds them. Links to playbooks that exist are unlimited; new requests (companies without one yet) are 25 a month on Starter and AppSumo, 100 on Growth and 300 on Scale, and over that their leads wait unrequested until next month. Needs a cold-email tool in send_to. |
| skip_crm | "hubspot" | hubspot or salesforce: leave out companies that CRM already has (matched by website, looked up each run, before anything is charged), so a territory or lookalike list brings only new accounts. find=crm reads companies from it instead: crm=hubspot&crm_list=closed_lost (companies on deals lost in the last since_days, default 365), recent (added in the last since_days, default 30) or all. Only names and websites are read; a shared link key can do either only when it's locked to its link and may send to your tools. HubSpot's lost deals need the deals scope on its app. |
| job_changes | "hubspot" | hubspot: check the CRM's contacts that have a LinkedIn profile (up to limit, default 200) for job changes. Each is looked up afresh, where they work now first (their profile only); only people now at a different company than the CRM has come back, with previous_company and job_change columns, and only they get the email lookup. Contacts still at the same company aren't charged (refunded) and are counted in the summary, as are those that couldn't be checked and why. Add reveal=email for their new work email, and send_to (slack, your CRM with crm_task=1) to act on it. Names, LinkedIn URLs and company websites are read each run and not kept. |
| sync / sync_apply / auto_under_pct | "1" / "review" / "10" | With list=products&domains=<a supplier's store>&send_to=shopify: compare the supplier's catalog with your store (matched by SKU, then barcode, then title) and bring the store up to date. Products the store doesn't have are added as drafts; a different price or sale price updates the variant; sold out at the supplier adds the sold-out-at-source tag (and removes it when it's back); after a run that listed the whole catalog, products this sync added that the supplier dropped are archived, never deleted. sync_apply=review (the default) holds price changes and removals for you on the Agents page; auto applies everything; auto_under_pct=N applies price changes under N%. Add schedule=daily to keep it in step. |
| tpl | "lost-deal-revival" | The agent template a link came from (templates add it). It's only counted, so you can see which templates are used; it never changes what the link does. |
| new_only | 1 | Only companies this link hasn't covered before: the websites it already delivered are skipped before anything is looked up (no credits). A company counts as covered once every destination in send_to took it (or skipped it on purpose, like no email); if a destination failed, wasn't connected or was left out of the run, its companies are tried again next time. For discover and find, more candidates are asked for so the limit can still be met. The summary says how many were already seen. |
| schedule | "weekly" | daily or weekly: saves the link to run by itself (Agents) instead of running it now, with new_only on unless the link says new_only=0. Needs your sign-in or your own API key; returns the schedule's id and next run. Opening the link again keeps the frequency set on Agents; a deleted schedule isn't brought back by its link (re-create it on Agents). Links of people (people=, linkedin=) or with an email address in them can't be scheduled. |
| webhook | "off" | on sends rows to your CRM webhook as they finish (the default), off doesn't, or a one-off https:// URL. |
| format | "json" | json returns one JSON document. Leave out for CSV. |
| name | "Q3 prospects" | A name for the run (used for the file name and receipt). |
Column groups: email (email finder), company (company), leadership (leadership), site (website), tech (tech stack), email_auth (email setup), dns (dns), security (security), socials (social profiles), contacts (contacts), domain_age (domain age), providers (your data providers), product (product), product_price (price & stock), product_ids (product ids), product_variants (variants), product_market (marketplaces), product_reviews (ratings), performance (performance & third parties), hiring (hiring), gtm (go-to-market), sitemap (site map), news (news & buzz), footprint (digital footprint), public_footprint (public footprint), trust (trust & compliance), filings (filings & public record), govcon (government contracting), commerce (store signals), google (google data), changes (changes over time), upkeep (website upkeep), site_review (website review), uk_company (uk company record), funding (funding (sec form d)), ads (ad networks), web_risk (unsafe-site check), authority (link authority), booking (online booking), intake (online intake), gaps (reason to reach out).
Send a CSV file
Post the file as multipart form data. Columns are recognized from their contents, whatever they're called; correct a guess with map, and say has_header=no when the first row is data.
curl -X POST https://programmable.info/api/sheet \
-H "Authorization: Bearer YOUR_API_KEY" \
-F file=@contacts.csv \
-F "map=email:Contact Info|company:Employer" \
-F format=jsonAgent links (GET)
For spreadsheets and browsers, the same options work as a URL: https://programmable.info/x/live?key=YOUR_KEY&domains=stripe.com,notion.so. In Google Sheets: =IMPORTDATA("https://programmable.info/x/live?key=…&domains=stripe.com"). A key in a URL can be seen by anyone who sees the URL, so make these in Developers > Links: their keys can be limited and locked to the link.
No key needed in a browser: open https://programmable.info/x/live?domains=stripe.com,notion.so&columns=company,tech and, if you're not signed in, you're asked to sign in (or create an account) and brought straight back, with the cost shown before it runs. Signed in, links you type or bookmark run at once; a link someone else sends you shows the cost and a Run button first, so nobody can spend your credits with a link. Tools like Google Sheets and Zapier have no browser session, so they still need key=. That key can be one we generate (dsk_…) or one you choose in Developers, like key=velvet-kayak-mango-orbit, so you can type links from memory; chosen keys must be hard to guess, and wrong keys are rate-limited.
Plain-English requests (request=)
Instead of naming every parameter, say what you want: https://programmable.info/x/live?request=find the CTOs at fintech startups in New York, with deep research on the AI tools they use (or the shorter q=). It's translated into the named parameters above on your own AI key, so you need Anthropic, OpenAI, Gemini or OpenRouter connected.
- Confirmed once. The first time, you see what the link will do (what it finds, the questions it asks your AI, anything it had to guess and the exact link with named parameters) and press Run. After that the translation is remembered for your account, so the same link runs the same way every time, also from Google Sheets or Zapier. Rewording it translates and asks again.
- Named parameters win. Mix both, e.g.
request=deep research on their AI tools&domains=stripe.com,notion.so: anything the link spells out is used as is, and the request only fills in the rest. - From automations an unconfirmed request returns
428with aconfirmlink to open once in a browser. In Settings you can choose to confirm only requests that spend your money; those (AI questions, paid provider data, sending rows to another tool) are always confirmed once. - Only the translation is kept, never the rows.
Links that act: campaigns, schedules, new companies
A link is a set of instructions (what to find, what to ask, where to send it) plus the companies for one run. The companies can change every run: domains= from a Sheets cell (ENCODEURL(A2)), an n8n or Zapier payload, a POST body, a watch's changed companies, or what discover=, find= or accounts= turn up. The instructions are what a link is remembered by.
- The link's fingerprint is every parameter except the companies (
domains,people,linkedin,products), who's calling and how the answer looks (key,name,format,header,meta,webhook), per-run caps (limit,ai_limit,ai_max_usd), the run options (new_only,schedule,approval,min_fit,review_rules) and the campaign's copy (campaign_brief,campaign_steps,campaign_days). Order doesn't matter, and neither does how a value is written: comma lists in any order or case, 1/true/yes, ask= or the same questions in ai= JSON, a GET link or its JSON POST body. Same fingerprint, same campaign and same covered companies. - campaign=new writes the emails once, on your own AI key (it counts toward the run's AI spend, your daily AI limit and a shared key's AI cap), checks them strictly (plain text, only merge fields the link fills, no link in the first email unless the brief asks for one, within campaign_days), creates the campaign, and pins it to your account, the tool and the link's fingerprint. Every later run adds its new leads to that campaign and never creates another. If you delete the campaign in the tool, the next run says so and creates a new one, since the link still asks for one. Edit the copy in the tool; changing campaign_brief doesn't rewrite a campaign that exists.
- Apollo: a master API key and the sending mailbox picked in Connections. The contact custom fields the emails use (named like your AI columns) are created first; the sequence is created inactive, on the sending schedule picked in Connections (else your default). Its emails are "approved" only when the run may start it; otherwise they wait as "to be reviewed", so nothing sends even if the sequence is switched on by mistake: in manual mode, approve the emails in Apollo when you activate it. A link never starts an Apollo sequence it didn't create (Apollo doesn't show whether one has run before). Instantly: pick the sending accounts in Connections; the campaign is created as a draft, weekdays 9:00-17:00 (US Eastern unless your timezone is known), without open or link tracking, stopping on reply. Smartlead: the campaign is created as a draft, weekdays 9:00-17:00 US Eastern, without open or link tracking, stopping on reply. Reply.io: the contact custom fields the emails use are created first; the sequence is created as new, on your default schedule, without open or link tracking, finishing a contact who replies. Its key needs the sequences:write and contacts:write scopes. Add the sending mailboxes in Smartlead or Reply.io, and start the campaign there: a link never starts one in these two, even with approval=auto or approval=agent. Unsubscribe and opt-out lines are left to each tool's own settings: the emails we write don't include one, so turn the tool's on.
- approval=agent has an AI reviewer on your own key look at each lead before it goes in: its fit score against min_fit, whether the email is on another company's domain, your Do not contact list (all without AI), then whether the company fits the link's market, whether the opening line is really about that company, and your writing rules (no hype, no claims the facts don't back, no flattery, no link in the first email unless allowed, length). It answers send or hold with reason codes, checked strictly; anything unclear is held. It also reviews the emails campaign=new wrote, and only then starts that campaign. Held companies wait on Agents: approve re-runs the link for that company into the same campaign, discard drops them. Agent settings add "review my first N sends myself", a share of approved leads sent to you first, and a daily send limit (for agent and auto). The reviewer sees company facts and what was written, not the person's email; the receipt keeps decisions as counts and reason codes, and a held company keeps only its website, the link's settings and the reasons.
- approval=manual (the default) leaves every new campaign paused: you review the leads and the copy in the tool and start it yourself. approval=auto, only when the link says it and only in Apollo and Instantly, starts a campaign this link created once this run's new leads are in, or, without campaign=new, the campaign picked in Connections if it has never been started. A link starts a campaign at most once; a paused, stopped or finished campaign is left alone and the summary says so ("Paused in Instantly: not restarted"); a replacement for a deleted campaign is created paused. After creating or starting a campaign the run reads its state back, and a campaign created running when it shouldn't be is paused at once and reported. Auto sends real email from your mailboxes with nobody looking first: use manual for anything new, and auto only for a link you've already watched work. Every start is in the run's summary and receipt. Rules for cold email differ by country (the US CAN-SPAM Act, the EU and UK GDPR and ePrivacy rules, Canada's CASL and others); check what applies to you. This isn't legal advice.
- new_only=1 keeps, per link fingerprint, the websites of the companies the link has already delivered (registrable domains only, never anything about the people) and skips them next time before anything is looked up, so they cost nothing and nobody is sent twice. People are de-duplicated by the tool you send them to: Apollo finds existing contacts, Instantly skips leads already in a campaign. If a destination refused a run's rows, its companies aren't marked as covered, so the next run tries them again.
- playbook=only sends only leads whose company has a growth playbook on programmable.marketing, each with its own link as the
{{playbook_url}}merge field (an Apollo contact field named Playbook URL); with campaign=new the first email offers it. The link opens the playbook under your brand (from your verified work email) with "Talk to you" pointing at your site or booking page, set once in Connections → Growth playbooks (subscribers). Leads without a playbook are held: not sent, not counted as covered, and requested, so once it's made a later run adds them (the summary counts them as waiting). Links to playbooks that exist are unlimited; new requests are 25 a month on Starter and AppSumo, 100 on Growth and 300 on Scale (asking again for the same company doesn't use credits), and leads over that wait until next month. Only company websites are sent to programmable.marketing, never people. playbook=preview is the same for invitations, an event's for example: the link opens a preview (score, diagnosis, the ideas' titles, one sample idea) with your call to action where the full playbook would be unlocked, and each lead also gets{{playbook_opportunity}}and{{playbook_idea}}from it, which campaign=new uses in the first email.playbook_ctaandplaybook_cta_labelset that call to action for one link, e.g. your programmable.talk registration page and "Register for the workshop". - event=new creates your event in Programmable Talk on the first run (
event_title,event_starts_at,event_timezone,event_summary,event_platform_url; growth playbooks on when the link sends growth playbooks), pins it to the link, and reuses it after; event=<slug> uses one you have. Connect Talk once in Connections with a Talk API key. Every lead sent gets a Talk invite and{{event_url}}: their personal link, which registers them in one click. The emails go from your own cold-email tool; Talk keeps the invite (sent, opened, registered) in your Talk account and emails the full playbook when they register. With playbook=, the playbook page's button is the event; with campaign=new, the emails are written for the event, its name and time, and invite with{{event_url}}. - schedule=daily or weekly saves the link (never its key) to run by itself, with new_only on, and answers with the schedule's id and first run. Opening the same link again finds the same schedule. Agents lists it with pause, resume, delete and each run's rows, credits, AI cost and leads added. A run that finds the account out of credits pauses the schedule. A share key can't save a schedule. A run moves the next one a full period out; after a failed run the wait doubles, and three failed runs in a row pause it with the reason. A run holds its schedule while it runs, so "Run now" can't start it twice.
- Watches that act. On Agents, a watch can run a link when something changes: paste an agent link without companies (its columns, questions, people search and send_to, campaign=new and approval included) and pick which watched changes trigger it (hiring, tech, pricing…, or any). After each check it runs for the companies that changed (for a search watch, the new companies it found), with new_only on, on your credits and AI key, under the same pinning and approval rules as any run of that link. The card shows what it runs, the last time it acted and the leads it added.
- Sending the change itself. A watch can also send each change, without running a link, to HubSpot (a note on the company) or Salesforce (a completed task on the account); the company is created there first if it's missing. The note says which watch, what changed, before and now, and when it was checked. Your webhook (event
monitor.signals) and, for search watches, email work as before. Cold-email tools only get people through a link: a change is about a company, with no one to email.
Agent templates (Agents → Templates) are ready-made links for common jobs; fill in what you sell, your market and who to reach:
- Invite your market to your event: Companies in your market, the right people at each, invited to your Talk event with a preview of their own growth playbook; registering unlocks the full one. Uses company discovery, people search, Programmable Talk event, growth playbooks, new campaign, approval, new companies only, schedule.
/x/live?discover=[description]&location=[location]&employees=[employees]&search=people&titles=[titles]&per_company=2&columns=company&reveal=email&event=[event]&playbook=preview&send_to=[tool]&campaign=new&campaign_name=[campaign_name]&campaign_brief=[brief]&campaign_steps=2&approval=manual&schedule=weekly - Invite companies like your best customers: Lookalikes of the customers you'd most like more of, invited to your event with a preview of their growth playbook: the audience most likely to buy after it. Uses company discovery, people search, Programmable Talk event, growth playbooks, new campaign, approval, new companies only, schedule.
/x/live?like=[like]&employees=[employees]&limit=50&search=people&titles=[titles]&per_company=2&columns=company&reveal=email&event=[event]&playbook=preview&send_to=[tool]&campaign=new&campaign_name=[campaign_name]&campaign_brief=[brief]&campaign_steps=2&approval=manual&schedule=weekly - Invite founders who just raised: Companies that just filed an SEC Form D in your space, their founders, invited to your event with a preview of their growth playbook: they've raised, now they need to grow. Uses public records, people search, Programmable Talk event, growth playbooks, new campaign, approval, new companies only, schedule.
/x/live?find=formd&industries=[industries]&describe=[market]&search=people&titles=founder, co-founder, ceo&seniority=founder,c_suite&per_company=1&columns=company&reveal=email&event=[event]&playbook=preview&send_to=[tool]&campaign=new&campaign_name=[campaign_name]&campaign_brief=[brief]&campaign_steps=2&approval=manual&schedule=weekly - Invite companies on a tech stack: Companies that run a tool you know well (HubSpot, Webflow, Salesforce…), invited to your event with a preview of their growth playbook: an audience with the same problems. Uses company discovery, people search, Programmable Talk event, growth playbooks, new campaign, approval, new companies only, schedule.
/x/live?discover=[description]&tech=[tech]&employees=[employees]&search=people&titles=[titles]&per_company=2&columns=company&reveal=email&event=[event]&playbook=preview&send_to=[tool]&campaign=new&campaign_name=[campaign_name]&campaign_brief=[brief]&campaign_steps=2&approval=manual&schedule=weekly - Invite companies hiring for growth: Companies in your market that are hiring right now, their marketing and growth leads invited to your event with a preview of their growth playbook: growing teams want a plan. Uses company discovery, people search, Programmable Talk event, growth playbooks, new campaign, approval, new companies only, schedule.
/x/live?discover=[description]&location=[location]&employees=[employees]&hiring=1&search=people&titles=head of marketing, head of growth, vp marketing, cmo&seniority=vp,head,director,c_suite&per_company=1&columns=company&reveal=email&event=[event]&playbook=preview&send_to=[tool]&campaign=new&campaign_name=[campaign_name]&campaign_brief=[brief]&campaign_steps=2&approval=manual&schedule=weekly - Invite store owners to an ecommerce agent session: Creates a session in Programmable Talk where you build each attendee's agent live (their competitor's prices, stock and new products every week, from the two links they drop in), then invites owners of independent online stores with a preview of their store's growth playbook; registering unlocks the full one. Uses company discovery, people search, Programmable Talk event, growth playbooks, new campaign, approval, new companies only, schedule.
/x/live?discover=[description]&location=[location]&employees=[employees]&search=people&titles=founder, owner, ceo, head of ecommerce&seniority=founder,c_suite,head&per_company=1&columns=company&reveal=email&event=new&event_title=[event_title]&event_starts_at=[event_starts_at]&event_timezone=[event_timezone]&event_summary=[event_summary]&event_question=[event_question]&playbook=preview&send_to=[tool]&campaign=new&campaign_name=[campaign_name]&campaign_brief=[brief]&campaign_steps=2&approval=manual&schedule=weekly - Invite your lost deals to your event: Companies from deals you lost in the last year, invited back through your event with a preview of their growth playbook: a reason to talk again that isn't a sales call. Uses reads your CRM, people search, Programmable Talk event, growth playbooks, new campaign, approval, new companies only, schedule.
/x/live?find=crm&crm=[crm]&crm_list=closed_lost&since_days=365&search=people&titles=[titles]&per_company=1&columns=company&reveal=email&event=[event]&playbook=preview&send_to=[tool]&campaign=new&campaign_name=[campaign_name]&campaign_brief=[brief]&campaign_steps=2&approval=manual&schedule=weekly - Account research (SDR prep): For the accounts you're about to work: how well each fits, what changed recently, and a first line to open with. Uses web research.
/x/live?domains=[domains]&ai=[Fit, Why now, Opener]&ai_research=1 - Lookalikes: Companies like your best customers, in the size range you sell to, scored for fit. Uses company discovery.
/x/live?like=[like]&employees=[employees]&ai=[Fit]&limit=50 - Market → people → campaign: Find the companies in a market, the right people at each, research them, and add them to a campaign it writes once. Uses company discovery, people search, lowest fit score, new campaign, approval, new companies only.
/x/live?discover=[description]&location=[location]&employees=[employees]&search=people&titles=[titles]&seniority=[seniority]&reveal=email&ai=[Fit, Why now, Opener]&min_fit=7&send_to=[tool]&campaign=new&campaign_name=[campaign_name]&campaign_brief=[brief]&approval=manual&new_only=1 - Growth playbook campaign: Find companies in your market and the right people at each, and offer every lead the growth playbook made for their company, under your brand, in a campaign it writes once. Uses company discovery, people search, growth playbooks, new campaign, approval, new companies only, schedule.
/x/live?discover=[description]&location=[location]&employees=[employees]&search=people&titles=[titles]&seniority=[seniority]&reveal=email&ai=[Opener]&playbook=only&send_to=[tool]&campaign=new&campaign_name=[campaign_name]&campaign_brief=[brief]&approval=manual&schedule=weekly - Create an event and invite your market: Creates your event in Programmable Talk, then invites the right people at companies in your market: each gets a personal one-click registration link and a preview of the growth playbook made for their company, and the full playbook when they register. Uses company discovery, people search, Programmable Talk event, growth playbooks, new campaign, approval, new companies only, schedule.
/x/live?discover=[description]&location=[location]&employees=[employees]&search=people&titles=[titles]&columns=company&reveal=email&event=new&event_title=[event_title]&event_starts_at=[event_starts_at]&event_timezone=[event_timezone]&event_summary=[event_summary]&playbook=preview&send_to=[tool]&campaign=new&campaign_name=[campaign_name]&campaign_brief=[brief]&campaign_steps=2&approval=manual&schedule=weekly - Just funded (weekly): Companies that just raised (SEC Form D) in your market, their founders, and why now: every week, only the new ones. Uses people search, schedule, new companies only.
/x/live?find=formd&industries=[industries]&describe=[market]&search=people&titles=founder, co-founder, ceo&seniority=founder,c_suite&ai=[Why now]&send_to=[send]&schedule=weekly&new_only=1 - New federal contractors (weekly): Companies newly registered on SAM.gov in your industry codes, with what they sell: every week, only the new ones. Uses schedule, new companies only.
/x/live?find=sam&naics=[naics]®isteredFrom=[registeredFrom]&ai=[What they sell]&schedule=weekly - Account watch → act: Watch your accounts for hiring, tech and product changes; when one changes, find the people, write why now and an opener, and add them to a paused campaign. Uses watch trigger, people search, new campaign, approval, new companies only.
/x/live?search=people&titles=[titles]&reveal=email&ai=[Why now, Opener]&send_to=[tool]&campaign=new&campaign_brief=[brief]&approval=manual - Competitor prices (daily): Your competitors' product pages every day: price, stock and discounts, sent to your webhook. Uses schedule, webhook.
/x/live?products=[products]&webhook=[webhook]&schedule=daily&new_only=0 - Lost-deal revival (weekly): Companies on deals you lost in the last year, checked for what's changed since; the ones worth another try become a task for the account's owner. Uses reads your CRM, web research, CRM tasks, schedule, new companies only.
/x/live?find=crm&crm=[crm]&crm_list=closed_lost&since_days=365&ai=[Worth another try, Why now]&ai_research=1&send_to=[crm]&crm_task=1&schedule=weekly - Pre-meeting brief: Before a call: what the company does, what changed lately and one good question to ask, posted to your Slack or Teams channel. Uses web research, Slack or Teams.
/x/live?domains=[domains]&ai=[Meeting brief]&ai_research=1&send_to=[channel] - Territory builder: A rep's book of accounts: companies in a region and size that fit, scored, leaving out every company already in your CRM. Uses company discovery, skips what's in your CRM, lowest fit score, CRM tasks.
/x/live?discover=[description]&location=[location]&employees=[employees]&limit=100&skip_crm=[crm]&ai=[Fit, Why now]&min_fit=7&send_to=[crm]&crm_task=1 - Job changes (weekly): Your CRM's contacts who moved to a new company: their new work email, a line about the move, a post in Slack and a task in your CRM. Uses job changes, Slack or Teams, CRM tasks, schedule.
/x/live?job_changes=[crm]&limit=200&reveal=email&ai=[Opener]&send_to=slack,[crm]&crm_task=1&schedule=weekly - Website review leads (weekly): Companies in a niche with problems on their website, the person to tell, and an opener about the worst problem, for agencies and web freelancers. Uses company discovery, website review, people search, schedule, new companies only.
/x/live?discover=[description]&location=[location]&limit=25&columns=company, site_review&search=people&titles=[titles]&per_company=1&reveal=email&ai=[Opener]&send_to=[tool]&schedule=weekly - New-business watch (weekly): Businesses that just filed a trademark for something in your market, with what they'll need next, posted to your channel each week. Uses public records, Slack or Teams, schedule, new companies only.
/x/live?find=trademarks&goods=[goods]&state=[state]&limit=50&ai=[What they'll need]&send_to=[channel]&schedule=weekly - Supplier catalog sync (daily): Keep your store in step with a supplier's: new products in as drafts, price and sale changes, sold-out tags, and archiving what they dropped, with big changes held for you. Uses catalog sync, schedule.
/x/live?list=products&domains=[supplier]&send_to=store&sync=1&sync_apply=review&auto_under_pct=[pct]&schedule=daily - Price watch: A message in your channel when a competitor's price falls by 10% or more on a product you watch. Uses watch trigger, price rule, Slack or Teams.
/x/live? - New arrivals and back in stock: A digest in your channel when a supplier or competitor adds products, or something sells out or comes back. Uses watch trigger, Slack or Teams.
/x/live? - Vendor check (weekly): New suppliers added to your CRM this week, checked for federal registration and exclusions, security and website health, with red flags as a task. Uses reads your CRM, CRM tasks, Slack or Teams, schedule, new companies only.
/x/live?find=crm&crm=[crm]&crm_list=recent&since_days=7&columns=company, govcon, trust, upkeep&ai=[Red flags]&send_to=[crm],slack&crm_task=1&schedule=weekly - Hiring surge: Employers filing many H-1B applications for the roles you serve, with what they're hiring for: for recruiters, HR tools and dev shops. Uses public records, Slack or Teams.
/x/live?find=h1b&titles=[titles]&states=[states]&minCertified=10&limit=50&columns=company, hiring&ai=[Hiring for]&send_to=[channel] - Switched tools: When a prospect's tech stack changes, check whether they moved off a competitor (or onto one) and tell the account's owner. Uses watch trigger, Slack or Teams, CRM tasks.
/x/live?columns=company, tech&ai=[Tool change]&send_to=slack,[crm]&crm_task=1 - Churn-risk watch: Your customers watched for layoffs, leadership changes, tools removed and a quiet product: each change is scored for risk and sent to the account's owner. Uses watch trigger, Slack or Teams, CRM tasks.
/x/live?columns=company, hiring, tech&ai=[Churn risk]&send_to=slack,[crm]&crm_task=1 - Partner finder: Companies in a market that list integrations with a tool like yours (or a competitor's), with why they'd partner. Uses company discovery.
/x/live?discover=[description]&limit=50&columns=company, sitemap&ai=[Partner fit] - Investor deal flow (weekly): Companies that just filed a Form D (raised money), scored against your investment thesis, in your channel every week. Uses public records, Slack or Teams, schedule, new companies only.
/x/live?find=formd&describe=[market]&limit=50&ai=[Thesis fit, What they sell]&min_fit=7&fit_ask=1&send_to=[channel]&schedule=weekly - Clinics with a weak booking funnel (weekly): Medical clinics and dermatology, endocrinology or physical therapy practices with no online booking found on their site, a slow mobile site or new-patient forms that are PDFs to print, and the practice manager's email, for agencies that build booking and patient intake. Uses company discovery, people search, schedule, new companies only.
/x/live?discover=[description]&location=[location]&limit=25&columns=company, booking, intake, performance, gaps&keep=online_booking=no or performance_score<50 or intake_outdated=yes&search=people&titles=[titles]&per_company=1&reveal=email&ai=[Opener]&schedule=weekly - Dental practices without online booking (weekly): Dental practices with no online booking found on their site, and the office manager's email. Uses company discovery, people search, schedule, new companies only.
/x/live?discover=[description]&location=[location]&limit=25&columns=company, booking, intake, gaps&keep=online_booking=no&search=people&titles=[titles]&per_company=1&reveal=email&ai=[Opener]&schedule=weekly - Salons and spas without online booking (weekly): Hair salons, day spas and med spas with no online booking found on their site, and the owner's email. Uses company discovery, people search, schedule, new companies only.
/x/live?discover=[description]&location=[location]&limit=25&columns=company, booking, gaps&keep=online_booking=no&search=people&titles=[titles]&per_company=1&reveal=email&ai=[Opener]&schedule=weekly - Home services companies with a slow mobile site (weekly): Plumbers, HVAC, roofing and cleaning companies whose site scores under 50 for mobile speed, with whether they book online, and the owner's email. Uses company discovery, people search, schedule, new companies only.
/x/live?discover=[description]&location=[location]&limit=25&columns=company, booking, performance, gaps&keep=performance_score<50&search=people&titles=[titles]&per_company=1&reveal=email&ai=[Opener]&schedule=weekly
Your own cron works too: every run of the same link only takes what's new.
curl "https://programmable.info/x/live?find=formd&industries=Computers&search=people&titles=founder&reveal=email&send_to=instantly&campaign=new&campaign_brief=…&new_only=1&format=json" \
-H "Authorization: Bearer YOUR_API_KEY"The JSON summary carries destinations (sent, skipped and the error per tool, also listed in errors), campaigns (id, name, a link to open it, emails written, leads added and skipped, whether it's sending, and when this run started it) and new_only (how many were already seen). In CSV a destination's error is a last line starting # error:. In Google Sheets, =IMPORTDATA("https://programmable.info/x/live?key=…&discover=…&new_only=1") recalculates now and then: with new_only each refresh returns only companies not covered before, so keep the results you want by copying them out, or let the link send them to your CRM or campaign instead.
Google Sheets add-on
Formulas and a sidebar inside your own sheet. In Google Sheets open Extensions → Apps Script, then paste in Code.js (as Code.gs) and add an HTML file named Sidebar with Sidebar.html. Save, reload the sheet, and a Programmable menu appears. Open the sidebar and paste an API key.
=PROGRAMMABLE(A2, "employees")one value about a company=PROGRAMMABLE_ROW(A2, "company, tech stack")several columns across the row, with=PROGRAMMABLE_HEADERS("company, tech stack")for the header=PROGRAMMABLE_EMAIL("Jane Doe", "acme.com")a work email- Programmable → Enrich selection fills a whole column of websites at once; Refresh skips the cache.
Results are cached for 6 hours, so Sheets recalculating doesn't spend credits again. The key stays in your Google account.
Claude, Cursor and other AI tools (MCP)
Programmable is also an MCP server, so an AI assistant can find companies, research them and start sheets for you. The endpoint is https://programmable.info/api/mcp (Streamable HTTP), with your API key as Authorization: Bearer …. Credits, limits and your Do not contact list apply exactly as they do on the API; a shared link's limited key won't work here.
Claude Code
claude mcp add --transport http programmable https://programmable.info/api/mcp --header "Authorization: Bearer YOUR_KEY"
Cursor (.cursor/mcp.json) and other clients that take a URL and headers
{
"mcpServers": {
"programmable": {
"url": "https://programmable.info/api/mcp",
"headers": { "Authorization": "Bearer YOUR_KEY" }
}
}
}Claude Desktop (claude_desktop_config.json), through the mcp-remote bridge
{
"mcpServers": {
"programmable": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://programmable.info/api/mcp", "--header", "Authorization:${AUTH}"],
"env": { "AUTH": "Bearer YOUR_KEY" }
}
}
}company_dossiergives everything about one company from its website on one page: what it does, size, headquarters, tech, email format, hiring, funding and registrations, then every other fact by group, each with its source. 1 credit, like one row of a company sheet. The same asPOST /api/companywith{"domain": "stripe.com"}.watch_companyadds a company to your companies watch, checked every week for hiring, tech, pricing, funding and leadership changes, at 1 credit per company per check.find_companiessearches the public sources (SEC, federal contracts, SAM.gov, registries, the web…). Included.enrich_companiesresearches up to 25 websites and returns their rows, at the usual credits per row; a longer list comes back as a sheet link instead.start_sheetturns a plain-English request or a domain list into sheet links (open in your browser, or CSV and JSON for scripts) instead of putting every row in the chat. Credits are spent when it runs.email_formatsgives a company's email formats with their shares and a confidence score, never anyone's address. Included.list_columnsshows every column you can ask for, by group.list_agentslists your saved agents (your account's only), with where each one sends its rows.run_agentruns one of them by id or name, exactly as its link does, and returns its rows: your credits, its limits, and a limit you can only lower. Its rows also go wherever that agent already sends them, and nowhere else.
Webhooks
Set a CRM webhook in Developers → Webhooks and rows are POSTed to it as JSON batches while a sheet runs. Each delivery carries an X-Programmable-Signature header you can check with your signing secret; checking it is optional.
What a row costs
One credit a row, plus one for each of these column groups when a sheet asks for them: email (the email finder, on people sheets), leadership, performance and product_market. Find people adds one more per person found. AI columns and your own provider keys add nothing. Rows that can't be enriched are refunded at the same rate. JSON responses report credits_charged in the summary; with Accept: application/x-ndjson the response streams events, and the first (start) carries creditsPerRow.
Registry: subawards
The subawards a prime reported, grouped by subawardee: GET https://programmable.info/api/subawards?prime_uei=<UEI>&since=YYYY-MM-DD with your API key as Authorization: Bearer …. It uses the same key, the same daily registry limit and the same availability rules as the SAM.gov registry lookups. since is optional and defaults to a year ago; history starts on 2021-10-01 (fiscal year 2022).
{
"data": {
"prime_uei": "ABCDEF123456",
"since": "2025-10-26",
"subawardees": [
{ "uei": "XYZ987654321", "name": "Widget Makers LLC", "count": 4, "total_amount": 412000, "latest_action_date": "2026-08-14", "name_only": false },
{ "uei": null, "name": "Acme Widgets Inc", "count": 1, "total_amount": 35000, "latest_action_date": "2026-01-10", "name_only": true }
],
"totals": { "subawardees": 2, "count": 5, "amount": 447000 }
},
"source_version": "2026-10-01",
"data_date": "2026-10-26"
}- Subawardees are grouped by UEI. Rows without a UEI are grouped by name and marked
name_only: a match to a registration from them is only possible, not exact. A subawardee whose name is a person's keeps its UEI with no name. - Contract and grant subawards are both included. The data comes from the first-tier subaward reports primes file (FFATA, now reported in SAM.gov), as published by USAspending, and is refreshed daily.
data_dateis the day of the newest file loaded. - The reports are incomplete. They cover only first-tier subawards of $30,000 or more, primes under-report, and reports arrive late, so recent months fill in over time. Amounts are as reported.
400for a malformed UEI or date.503withRetry-Afterwhile the data is still loading, so an empty answer always means no reported subawards, never "not loaded".
Errors
Errors come back as {"error": "…"} with a plain-language message.
| 400 | Something in the request is wrong; the message says what. |
| 401 | No key, or the key isn't recognized (not copied in full, or revoked). |
| 402 | Not enough credits for the rows. |
| 403 | The key's limits refuse this request, or it's a share key used outside its link. |
| 413 | Too many rows for one request: send parts. |
| 428 | A plain-English request (request=) that hasn't been confirmed yet: open the confirm link once in a browser. |
| 429 | Too many requests this hour: wait and retry. |
Rows are charged as they're built (see What a row costs); rows that fail are refunded.