K-Stra developers

Quickstart

K-Stra exposes its geomarketing engine as a REST API. Its computations — catchments, demographics and economy, market models by sector and brand, brand coverage, site comparison, real estate — are actions you call with an API key. The same registry drives the web app, the built-in assistant and this API, so they never disagree.

Base URL: https://www.k-stra.com. Specification: /api/v1/openapi.json (OpenAPI 3.1). Interactive reference: /developers/reference. Using an AI assistant or agent? The same actions are tools on the MCP server at /mcp, with the same key.

1. Create a key

Sign in to the web app, open Account → API, give the key a name and choose its scopes:

Scope Allows
read free actions (address search and geocoding, administrative areas, sectors, brands), quotes, reading analyses, jobs, credits and data versions
run charged actions (isochrones, People & Economy, Markets, Real estate, Coverage, Site comparison, network planning, site ranking, enrichment)
manage writing analyses (create, rename, share, duplicate, delete) and managing webhooks

Every key has read. The secret is shown once. It looks like ks_live_ab12cd34_…; keep it out of source code and logs. You can set a monthly credit cap per key and revoke it at any time.

The key acts as you inside your organization: it spends the organization's credits within your team and personal limits, and every run appears in the ledger with the key's name.

2. Who am I

curl https://www.k-stra.com/api/v1/me \
  -H "Authorization: Bearer ks_live_…"

Returns your user, organization, team, the key's scopes and monthly usage, and the credit summary.

3. List the actions

curl https://www.k-stra.com/api/v1/actions -H "Authorization: Bearer ks_live_…"

Each action comes with its inputSchema and outputSchema (JSON Schema), whether it is charged, and the scope it needs.

The public API is the geomarketing engine, not the web app. It exposes the computations and the few lookups you need to build their input:

Group Actions Credits
Catchments analysis.isochrone — drive/walk-time bands, road distance or radius from one or more points, with the residents in each band 3 per call
People & Economy analysis.people_economy — socio-demographic and economic tables of a territory 2 per topic (ten topics, 20 for all of them)
Markets analysis.markets_sector (demand, supply and opportunity by sector; with mixNeighbours or mixSectors it also counts a category mix in the same run), analysis.category_mix (how many stores of several categories, per band or area and per 10,000 residents, next to the local government area, the state and Australia), analysis.markets_brands (competitors by brand), analysis.brand_coverage (where a brand covers its market; with your own points it adds the order in which they can close, and with your planned points the order in which to open them), analysis.site_comparison (compare candidate sites) 40 per sector, whatever the territory · category mix: 2 for the neighbours of a sector, or 1 per category · 5 per brand · 5 per point (coverage) · 20 per candidate site
Real estate analysis.realestate — listings, prices and rents of a territory 20 per analysis, whatever the territory, number of listings and period
Network planning network.coverage — 2 to 500 stores: residents covered once, twice or more (cannibalization, with the pairs that overlap), the areas the network touches but does not reach (white spots), and optional plans of which stores to close and which candidates to open 5 per store and per candidate
Site selection site.rank — 2 to 20 candidate sites for a sector, ranked by net new spend with a score and the reasons in words 20 per candidate
Enrichment enrich.point — one point, one catchment, every section you ask for (people, economy, real estate, brands, sectors) in one call; enrich.points — up to 500 points as a background job with a credit cap sum of the sections
Lookups search.addresses, search.resolve_address, search.reverse_address (geocoding), areas.list (administrative areas), markets.sectors (sector codes), search.brands (brand names, also by sector) free

Everything else the web app does — map helpers, free dashboards, your uploaded datasets — stays in the web app and is not part of this API.

4. Run a free action

Free actions execute directly:

curl -X POST https://www.k-stra.com/api/v1/actions/search.addresses/execute \
  -H "Authorization: Bearer ks_live_…" -H "Content-Type: application/json" \
  -d '{"query":"151 George Street, Sydney NSW 2000","continent":"oceania"}'

Each address comes with its country (AU) and the parts as they are in the data; to show it, write it the way that country does — in Australia house number, street, suburb, state code and postcode, with the first number of the list in number.

5. Run a charged action: quote, then execute

Charged actions take two calls. First the quote, which never charges:

curl -X POST https://www.k-stra.com/api/v1/actions/analysis.people_economy/quote \
  -H "Authorization: Bearer ks_live_…" -H "Content-Type: application/json" \
  -d '{"origins":[{"lat":-33.89106,"lng":151.24843}],"bandSeconds":[300,600],"mode":"car","traffic":"normal","topics":["residents","age_classes"]}'
{ "actionId": "analysis.people_economy", "quotedCredits": 4, "quote": { "totalCredits": 4, "lines": [ … ] }, "polesInvolved": 27543 }

Then the execution, repeating the quoted amount. If the quote changed in the meantime you get 409 quote_changed and nothing is charged.

curl -X POST https://www.k-stra.com/api/v1/actions/analysis.people_economy/execute \
  -H "Authorization: Bearer ks_live_…" -H "Content-Type: application/json" \
  -d '{"origins":[{"lat":-33.89106,"lng":151.24843}],"bandSeconds":[300,600],"mode":"car","traffic":"normal","topics":["residents","age_classes"],"quotedCredits":4}'

The response carries result.charged: true, result.creditsCharged, the tables of the analysis and, when the run was bound to a saved analysis (see Analyses & versions), the version it became.

5a. Rank candidate sites

"I have N candidates for a sector: which one do I open?" Call site.rank with 2 to 20 candidates ({id, lat, lng, name}), the sectorCode and, if you have them, your ownStores. Each candidate gets the sector's catchment, the residents and spend within reach, the competitors inside, the spend it would capture and the part it would take from your own stores. The ranking is by net new spend, with a score (best = 100) and two or three reasons in words per site, e.g. "fewest competitors of the set (12 inside the catchment)". 20 credits per candidate.

5b. Enrich a point in one call

When you have a point and want everything the engine knows about its catchment, call enrich.point instead of the single analyses. Quote first: the answer lists one line per section, so you see what each part costs and drop what you do not need.

curl -X POST https://www.k-stra.com/api/v1/actions/enrich.point/quote \
  -H "Authorization: Bearer ks_live_…" -H "Content-Type: application/json" \
  -d '{"lat":-33.89106,"lng":151.24843,"mode":"car","traffic":"normal","bandSeconds":[600],
       "include":["people","economy","realestate","sectors"],"sectors":["auto"],"detail":"summary"}'

Then execute with the same input plus quotedCredits. The result carries the catchment (bands, residents, poles), people and economy tables, realestate indicators, sectors with the codes the engine picked ("auto" = the three sectors with most shops in the catchment), the skipped sections with their reason, and the data versions used. detail: "full" adds the listings, the shop points and the catchment geometry. Brands need brands (exact names from search.brands); without them the section is skipped, not charged.

Mind the sectors: each one costs 40 credits whatever the size of the catchment, and "auto" picks three, so 120 credits per point. The quote tells you before anything is charged; use include to leave sectors out when you only need the profile.

5c. Enrich a file of points

For many points (a store network, a list of candidates) call enrich.points with up to 500 points ({id, lat, lng}) and the same options as enrich.point. The quote is an estimate: three sample points (first, middle, last) are quoted and multiplied by the count. Execute with quotedCredits and, if you want, a maxCredits cap (default: the estimate). The answer is immediate and carries a jobId:

{ "actionId": "enrich.points", "result": { "jobId": 812, "points": 120, "sampledPoints": 3, "maxCredits": 1440, "estimate": true, "quote": { … } } }

The job enriches one point at a time and charges each for what it really costs, never going past the cap: it stops at the first point that would exceed it, and that point and the ones after it are skipped with batch_cap. Follow it with GET /api/v1/jobs/{id} or let the job.completed webhook tell you; the job's result is the summary (done, skipped, failed, credits charged, why it stopped). Then read GET /api/v1/jobs/{id}/results (one row per point, paged with offset and limit) and GET /api/v1/jobs/{id}/results/{index} for a point's full enrich.point result. Results stay for 30 days.

5d. Check the coverage of a store network

"Does my network cover the territory? Where do stores eat each other, where are the holes?" Call network.coverage with 2 to 500 stores ({id, lat, lng, name}) and the catchment (minutes, mode). Every resident is counted once: network gives the residents covered by one store, by two or more (the cannibalization share) and the residents of the administrative areas the network touches that no store reaches. Per store: residents reached, the exclusive part, the shared part and the stores it overlaps with; overlaps lists the pairs that share most residents; whiteSpots ranks the areas (postcodes by default, whiteSpotLevel 4 to 9) by residents nobody reaches. 5 credits per store.

Which stores to close, which sites to open. Add closeCount (1-50) and the answer carries a closurePlan: the stores in the order they can close, each chosen as the one whose closure leaves the fewest people with no store at all (the others move to a store that still reaches them), with lost and the coverage left after each step. Add candidates (up to 100, same shape as stores) and, if you want, openCount (1-20): the openingPlan ranks them by the people they add that nobody reaches yet, each step measured after the previous choices. measure decides what the plans count: population (default), families (families as the national statistics office counts them; in Australia, census families: couples and one-parent families, without people living alone. households is its former name, still accepted), under_5, aged_65_plus or aged_70_plus — for a childcare network or aged-care coverage the right people, not all residents. The plans are greedy, one step at a time, which is the standard answer for coverage problems and tells you the order. Candidates are charged like stores, 5 credits each; minutes goes up to 60. A network whose catchments add up to more than 50 million address poles is refused before anything is charged: split it by region or use fewer minutes.

6. Read it back for free

Saved results are yours to reread without spending: GET /api/v1/analyses, GET /api/v1/analyses/{code}, GET /api/v1/analyses/{code}/versions/{no}.

Conventions