Every /api/v1/* endpoint requires an API key, issued to you privately. Send it as a bearer token.
Authorization: Bearer YOUR_API_KEY
?api_key= query parameter on the CSV endpoint only.Limits are applied per API key.
| Window | Limit |
|---|---|
| Per minute | 120 requests |
| Per hour | 5,000 requests |
Every response carries the current budget so you can self-throttle:
X-RateLimit-Limit: 120 X-RateLimit-Remaining: 117 X-RateLimit-Reset: 1780350207
When a limit is exceeded the API returns 429 Too Many Requests with a Retry-After header (seconds) and a JSON body:
{ "error": "Rate limit exceeded: 120 per 1 minute" }
Need higher throughput for a bulk export? It is a configuration change on your key, not a code change. Just ask.
Send your filters in the request body and get ranked leads straight back. Nothing is saved. Ideal for ad-hoc pulls, testing, and "give me X in Y right now".
POST /api/v1/leads/search
Save a named filter ("NE care homes, dialler-ready") once, then pull from it repeatedly, preview counts, export CSV, and feed outcomes back so the profile sharpens over time.
POST /api/v1/icps then /icps/{id}/leads
Both routes return the same lead object and use the same filter fields. Start with A; move to B when a search becomes a standing target.
{
"premises": 119498,
"tenants": 78402,
"decision_makers": 440068,
"lead_scores": 1607268,
"sources": 28,
"sample_prospect": { "business_name": "Butterwick Limited", "sector": "health_social",
"postcode": "DL146JU", "eaq": 799236, "phone": "+44 1388 603003" }
}
limit (1–1000, default 100) and offset page the results. Nothing is persisted.# body { "geography": ["NE1", "NE2", "NE3"], "sectors": ["care"], "require_phone": true } # response { "total": 153, "limit": 100, "offset": 0, "leads": [ { /* lead object */ }, ... ] }
name, geography and sectors are required). Returns the stored profile including its id. 201 Created.404 if it does not exist.204 No Content.{ "total": 4179, "leads": [ { /* top 10 lead objects */ } ] }
limit (1–1000, default 100) and offset.{ "icp_id": "...", "total": 4179, "limit": 100, "offset": 0, "leads": [ ... ] }
?include=all for every available field, or ?include=company_number,epc_rating for a chosen subset. This endpoint also accepts ?api_key= for direct browser download links.201 Created.{
"icp_id": "<uuid>",
"premises_id": "<uuid from the lead object>",
"outcome": "won", // good | bad | contacted | won | lost
"notes": "signed 24-month, EDF" // optional
}
These fields define a search body and an ICP. They are identical for both routes.
| Field | Type | Meaning |
|---|---|---|
name | string | required for ICPs A label for the saved profile. (Optional on one-call search.) |
geography | string[] | required for ICPs List of postcode districts, e.g. ["NE1","DH1","SR3"]. Use the district (outward part), not the full postcode and not just the area letters. |
sectors | string[] | required for ICPs One or more sector keys (see below), e.g. ["care","health_social"]. |
eaq_min / eaq_max | int | optional Estimated annual consumption band in kWh. Filters to premises sized within the range. |
require_phone | bool | optional true returns only dialler-ready leads (a phone number present). Default false. |
require_tps_clean | bool | optional Restrict to numbers screened against TPS/CTPS. |
require_dm_identified | bool | optional Only leads with a named decision maker. |
prefer_new_incorp | bool | optional Boost recently incorporated businesses (likely to be shopping for a first contract). |
prefer_new_epc | bool | optional Boost premises with a recent EPC (often a change of occupier). |
prefer_multi_site | bool | optional Boost operators that control more than one premises. |
dm_target_types | string[] | optional Filter to decision-maker levels of interest. |
supplier_targets | string[] | optional Bias toward premises whose predicted incumbent supplier is in this list. |
score_threshold | int | optional Minimum score a lead must reach to be returned. Default 15. Raise it for a tighter, higher-confidence funnel. |
| Cross-query filters, combine freely with everything above | ||
tiers | string[] | optional Restrict to grades, e.g. ["diamond","gold"] or ["silver"]. |
eaq_at_least / eaq_at_most | int | optional Hard EAQ (kWh) floor/ceiling on the estimated or confirmed consumption. |
fuel_profiles | string[] | optional ["dual_fuel"] / ["electricity_only"] / ["unknown"]. |
rv_min / rv_max | int | optional Rateable-value (£) range. |
operator_classes | string[] | optional ["independent"] / ["national_or_group"] / ["franchise"] (multi-site operators). |
recently_moved | bool | optional true = moved in within 18 months (highest-intent timing). |
exclude_chains | bool | optional true = independents only (drops national chains, groups, subsidiaries). |
Example, independent silver hospitality in Newcastle over 100,000 kWh, callable:
{ "geography": ["NE1","NE2","NE3","NE4","NE5","NE6"],
"sectors": ["hospitality"], "tiers": ["silver"],
"eaq_at_least": 100000, "require_phone": true, "exclude_chains": true }
Every lead carries a tier, a data-quality grade built from three pillars (an active Companies House identity, a verified sector, and a size estimate) plus how we know the size. The size confidence is what separates a crown-jewel lead from a rough one.
| Tier | What it means |
|---|---|
💎 diamond | Identity + sector + actual metered/certified consumption. We know their real energy use. |
🥇 gold | Identity + sector + size from the building's real size (rateable value or measured floor area) × real DESNZ intensity. |
🥈 silver | Identity + sector + an assumed/inferred size, or any two of the three pillars. A solid lead, softer on one axis. |
🥉 bronze | One pillar or fewer. |
Dialler-ready = a tier and a phone number. A qualified lead without a phone yet is the working pool, same tier, awaiting enrichment. Use require_phone: true to return only dialler-ready leads.
Every lead returned by search, preview and the leads endpoint has this shape. Null means "not known for this record" (we never invent a value).
| Field | Example | Meaning |
|---|---|---|
tier | gold | Data-quality grade: diamond / gold / silver / bronze. See Lead tiers below. |
size_grade | real | How the size estimate is built: actual (metered), real (real building size), estimated (assumed/inferred), none. |
score | 25 | Fit score for the filter set (higher is stronger). |
business_name | ANGEL STAR TRADING LTD | Trading / registered business name. |
sector_category | hospitality | Verified sector. |
address | 92 98 newgate st newcastle | Normalised premises address. |
postcode / postcode_district | NE15RQ / NE1 | Full postcode and its district. |
eaq | 66880 | Estimated annual consumption (kWh). |
eaq_source | voa_estimate | How the EAQ was derived: confirmed / metered (DEC or CRM-confirmed meter) / epc_certified (EPC asset rating — modelled, banded) / voa_estimate / floor_area_estimate / benchmark. Only confirmed/metered are precise figures; everything else pairs with eaq_indicative=true. |
estimated_elec_eaq | 200725 | Estimated annual electricity consumption (kWh) = floor area × real DESNZ (ND-NEED) intensity for the sector. Null where floor area is unknown. |
estimated_gas_aq | 359085 | Estimated annual gas consumption (kWh), same method. Zero where the premises has no mains-gas connection (per its EPC heating fuel), so gas is only counted where it can actually be brokered. |
fuel_profile | dual_fuel | dual_fuel (mains gas + electricity), electricity_only (no gas connection), or unknown. From the EPC heating fuel. Lets you target dual-fuel (larger commission) vs electricity-only. |
estimated_annual_spend | 75317 | Indicative total annual energy spend (£): electricity + gas at standard unit rates where the per-fuel split exists, otherwise EAQ × the electricity rate. Lets you rank prospects by size. Null when EAQ is unknown. |
estimated_elec_spend / estimated_gas_spend | 50181 / 25136 | The electricity and gas components of the annual spend (£). |
estimated_commission | 6243 | Indicative commission opportunity (£) over a standard contract term, at a market-standard per-fuel uplift. A configurable default; a partner calibrates it with their own rates. Null when EAQ is unknown. |
phone | 07832 952699 | Published business phone (the dialler field). |
contact_name | Mr Yu-Hsiang Liao | Named decision maker where identified. |
contact_email | null | Email where known. |
decision_maker_level | premises | Where the decision sits (e.g. premises, central/operator). |
operator_premises_count | null | For multi-site operators, how many premises they control. |
company_number | 13863310 | Companies House number where matched. |
incorporation_date | 2022-01-21 | Date the company was incorporated. |
moved_in | 2026-02-23 | When the occupier took the premises (business-rates liability start). A recent date = they need an energy contract now. Null where not in a published council's data. |
recently_moved | true | true if moved_in is within the last 18 months, the highest-intent timing signal. |
rateable_value | 45250 | VOA/business-rates rateable value (£), authoritative property scale, independent of consumption. Null where no rating exists. |
rv_band | small_mid | micro (<£12k, small-business-rate-relief) / small_mid (£12k–51k) / large (≥£51k). A quick size/scale filter. |
predicted_incumbent | BG SME | Most likely current supplier, modelled from local patterns. |
confirmed_supplier | null | Supplier confirmed via a deal or lookup (never overwritten by a prediction). |
epc_rating / epc_date | D / 2021-06-01 | EPC band and certificate date where present. |
premises_id / tenant_id | <uuid> | Stable identifiers. Pass premises_id back in feedback. |
Pass any of these in sectors (counts are live coverage in the current North East region):
Use postcode districts (the outward code): NE1, DH3, SR5, TS18, DL14. The live region today is the North East (NE, DH, DL, TS, SR districts). The roadmap extends nationally; new regions appear under the same keys with no API change.
| Status | Meaning |
|---|---|
200 / 201 / 204 | Success (204 for delete, no body). |
401 Unauthorized | Missing or invalid API key. |
404 Not Found | The ICP id does not exist. |
422 Unprocessable Entity | The request body failed validation (e.g. a required field missing). The body lists which field. |
429 Too Many Requests | Rate limit hit. Wait the number of seconds in Retry-After. |
Three calls, copy-paste ready. Replace YOUR_API_KEY.
curl https://api.pyperai.co.uk/api/v1/engine-stats \
-H "Authorization: Bearer YOUR_API_KEY"
curl -X POST "https://api.pyperai.co.uk/api/v1/leads/search?limit=50" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"geography":["NE1","NE2","NE3","NE4"],"sectors":["care"],"require_phone":true}'
curl -X POST https://api.pyperai.co.uk/api/v1/icps \ -H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"name":"NE care, dialler-ready","geography":["NE1","NE2","NE3","NE4"],"sectors":["care"],"require_phone":true}' # take the returned id, then: curl "https://api.pyperai.co.uk/api/v1/icps/<id>/leads.csv?api_key=YOUR_API_KEY" -o leads.csv
https://api.pyperai.co.uk/docs for developers who want to try calls in the browser.