Pyper Navigator  ·  API Reference

The growth API for the energy market

A REST API that returns scored, dialler-ready energy prospects on demand. Define who you want, get back named businesses with phone numbers, sector, estimated annual consumption and the decision maker. Built to feed straight into a dialler or CRM.
Base URL  https://api.pyperai.co.uk
On this page
Authentication Rate limits Two ways to use it Endpoints Filter fields Lead tiers The lead object Sectors & geography Errors Quick start

1Authentication

Every /api/v1/* endpoint requires an API key, issued to you privately. Send it as a bearer token.

Authorization: Bearer YOUR_API_KEY
Each partner is issued their own key. The key carries its own rate budget and (on request) its own data scope, so usage is isolated per partner. Keep it server-side; treat it like a password. For browser-initiated CSV downloads where a header cannot be set, the key may also be passed as a ?api_key= query parameter on the CSV endpoint only.

2Rate limits

Limits are applied per API key.

WindowLimit
Per minute120 requests
Per hour5,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.

3Two ways to use it

A  ·  One-call search simplest

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

B  ·  Saved profiles (ICPs) reusable

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.

4Endpoints

Engine overview

GET/api/v1/engine-stats
Headline coverage numbers for the live engine plus one sample prospect. Good for a health check or a dashboard tile.
{
  "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" }
}

One-call search

POST/api/v1/leads/search
Stateless search. Pass filter fields in the JSON body. Query params 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 */ }, ... ] }

Saved profiles: ICPs (Ideal Customer Profiles)

POST/api/v1/icps
Create a saved profile from the filter fields (name, geography and sectors are required). Returns the stored profile including its id. 201 Created.
GET/api/v1/icps
List your saved profiles, most recently updated first.
GET/api/v1/icps/{id}
Fetch one profile. 404 if it does not exist.
PUT/api/v1/icps/{id}
Replace a profile's filters (whole-object update; send the full set of fields).
DELETE/api/v1/icps/{id}
Delete a profile. 204 No Content.

Pulling leads from a profile

GET/api/v1/icps/{id}/preview
Total matching count plus the top 10 leads. Use it to size a list before pulling the full set.
{ "total": 4179, "leads": [ { /* top 10 lead objects */ } ] }
GET/api/v1/icps/{id}/leads
Paginated leads as JSON, ranked by score descending. Query params limit (1–1000, default 100) and offset.
{ "icp_id": "...", "total": 4179, "limit": 100, "offset": 0, "leads": [ ... ] }
GET/api/v1/icps/{id}/leads.csv
Full list as a CSV download (no pagination). Default columns are the 10 core dialler fields. Add ?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.

Feedback loop

POST/api/v1/feedback
Report what happened to a lead so the engine learns. 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
}

5Filter fields

These fields define a search body and an ICP. They are identical for both routes.

FieldTypeMeaning
namestringrequired for ICPs  A label for the saved profile. (Optional on one-call search.)
geographystring[]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.
sectorsstring[]required for ICPs  One or more sector keys (see below), e.g. ["care","health_social"].
eaq_min / eaq_maxintoptional  Estimated annual consumption band in kWh. Filters to premises sized within the range.
require_phonebooloptional  true returns only dialler-ready leads (a phone number present). Default false.
require_tps_cleanbooloptional  Restrict to numbers screened against TPS/CTPS.
require_dm_identifiedbooloptional  Only leads with a named decision maker.
prefer_new_incorpbooloptional  Boost recently incorporated businesses (likely to be shopping for a first contract).
prefer_new_epcbooloptional  Boost premises with a recent EPC (often a change of occupier).
prefer_multi_sitebooloptional  Boost operators that control more than one premises.
dm_target_typesstring[]optional  Filter to decision-maker levels of interest.
supplier_targetsstring[]optional  Bias toward premises whose predicted incumbent supplier is in this list.
score_thresholdintoptional  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
tiersstring[]optional  Restrict to grades, e.g. ["diamond","gold"] or ["silver"].
eaq_at_least / eaq_at_mostintoptional  Hard EAQ (kWh) floor/ceiling on the estimated or confirmed consumption.
fuel_profilesstring[]optional  ["dual_fuel"] / ["electricity_only"] / ["unknown"].
rv_min / rv_maxintoptional  Rateable-value (£) range.
operator_classesstring[]optional  ["independent"] / ["national_or_group"] / ["franchise"] (multi-site operators).
recently_movedbooloptional  true = moved in within 18 months (highest-intent timing).
exclude_chainsbooloptional  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 }

5bLead tiers

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.

TierWhat it means
💎 diamondIdentity + sector + actual metered/certified consumption. We know their real energy use.
🥇 goldIdentity + sector + size from the building's real size (rateable value or measured floor area) × real DESNZ intensity.
🥈 silverIdentity + sector + an assumed/inferred size, or any two of the three pillars. A solid lead, softer on one axis.
🥉 bronzeOne 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.

6The lead object

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).

FieldExampleMeaning
tiergoldData-quality grade: diamond / gold / silver / bronze. See Lead tiers below.
size_graderealHow the size estimate is built: actual (metered), real (real building size), estimated (assumed/inferred), none.
score25Fit score for the filter set (higher is stronger).
business_nameANGEL STAR TRADING LTDTrading / registered business name.
sector_categoryhospitalityVerified sector.
address92 98 newgate st newcastleNormalised premises address.
postcode / postcode_districtNE15RQ / NE1Full postcode and its district.
eaq66880Estimated annual consumption (kWh).
eaq_sourcevoa_estimateHow 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_eaq200725Estimated annual electricity consumption (kWh) = floor area × real DESNZ (ND-NEED) intensity for the sector. Null where floor area is unknown.
estimated_gas_aq359085Estimated 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_profiledual_fueldual_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_spend75317Indicative 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_spend50181 / 25136The electricity and gas components of the annual spend (£).
estimated_commission6243Indicative 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.
phone07832 952699Published business phone (the dialler field).
contact_nameMr Yu-Hsiang LiaoNamed decision maker where identified.
contact_emailnullEmail where known.
decision_maker_levelpremisesWhere the decision sits (e.g. premises, central/operator).
operator_premises_countnullFor multi-site operators, how many premises they control.
company_number13863310Companies House number where matched.
incorporation_date2022-01-21Date the company was incorporated.
moved_in2026-02-23When 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_movedtruetrue if moved_in is within the last 18 months, the highest-intent timing signal.
rateable_value45250VOA/business-rates rateable value (£), authoritative property scale, independent of consumption. Null where no rating exists.
rv_bandsmall_midmicro (<£12k, small-business-rate-relief) / small_mid (£12k–51k) / large (≥£51k). A quick size/scale filter.
predicted_incumbentBG SMEMost likely current supplier, modelled from local patterns.
confirmed_suppliernullSupplier confirmed via a deal or lookup (never overwritten by a prediction).
epc_rating / epc_dateD / 2021-06-01EPC band and certificate date where present.
premises_id / tenant_id<uuid>Stable identifiers. Pass premises_id back in feedback.

7Sectors & geography

Sector keys

Pass any of these in sectors (counts are live coverage in the current North East region):

retailhospitalityofficeeducationmanufacturinghealth_socialcarecharityarts_entertainmentwarehousereal_estatewholesale_retailhealthcarefaithleisureother

Geography

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.

8Errors

StatusMeaning
200 / 201 / 204Success (204 for delete, no body).
401 UnauthorizedMissing or invalid API key.
404 Not FoundThe ICP id does not exist.
422 Unprocessable EntityThe request body failed validation (e.g. a required field missing). The body lists which field.
429 Too Many RequestsRate limit hit. Wait the number of seconds in Retry-After.

9Quick start

Three calls, copy-paste ready. Replace YOUR_API_KEY.

1. Check the engine is live

curl https://api.pyperai.co.uk/api/v1/engine-stats \
  -H "Authorization: Bearer YOUR_API_KEY"

2. Pull dialler-ready care homes in central Newcastle

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}'

3. Save it as a profile and export the full list to CSV

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
Interactive docs. A live, clickable OpenAPI reference is also served at https://api.pyperai.co.uk/docs for developers who want to try calls in the browser.