Skip to content

Personal scoring

Personal scoring rates a home from 0 to 10 for one person's profile: what they can walk to, how long their regular trips take, and the local environment. Every scored number is a travel time. The API never scores a distance. You get the travel times from the TravelTime API, send them with the profile, and the Data API reads the cell features of the cells they cover.

Census demographics are returned as facts and are never scored. A profile that puts a weight or a dealbreaker on a census field, such as population, is refused with a 422.

Score one address

Scoring one address takes three requests. The first two go to the TravelTime API and the third goes to the Data API.

1. A place isochrone for each of the person's places. These are the cells from which the person reaches the place within its time budget, arriving on a weekday morning. Use arrival_searches.many_to_one, the place's mode and travel_time set to the place's budget. Each cell's mean is the trip time in seconds. A place isochrone does not depend on the home, so you can reuse it for every home you score.

bash

curl -X POST "https://api.traveltimeapp.com/v4/h3/fast" \
  -H "Content-Type: application/json" \
  -H "X-Application-Id: $TRAVELTIME_APP_ID" \
  -H "X-Api-Key: $TRAVELTIME_API_KEY" \
  -d '{
    "resolution": 9,
    "properties": ["mean"],
    "arrival_searches": {
      "many_to_one": [
        {
          "id": "office",
          "coords": { "lat": 51.517598, "lng": -0.082147 },
          "transportation": { "type": "public_transport" },
          "arrival_time_period": "weekday_morning",
          "travel_time": 2400
        }
      ]
    }
  }'

2. A walking isochrone from the home. Use one_to_many from the home, walking, with travel_time 900. That is about 60 cells. Skip this step for a home whose cell has a stored walk: the stored walk is used and a posted one is ignored. The request is step 1's, with one_to_many in place of many_to_one, walking, and the home's coordinates.

3. Score the home. Send the place cells as cells on each place, and the walking cells as the listing's walking_cells, each as {cell id: seconds}. The two maps below are cut to a few cells. Send the full maps from steps 1 and 2.

bash

curl -X POST -H "Authorization: Bearer $POI_API_KEY" -H "Content-Type: application/json" \
  "$POI_BASE/v1/scores/listings?country=GB" \
  -d '{
    "profile": {
      "places": [{"label": "office", "mode": "public_transport", "budget_s": 2400,
                  "per_week": 5, "required": true,
                  "cells": {"89194ad32cbffff": 1920, "89194ad32c3ffff": 2010}}],
      "categories": {"supermarket": 0.8, "cafe": 0.5, "nightlife": -0.4},
      "green": 0.6,
      "environment": {"noise": -0.4, "air_quality": -0.5},
      "band_s": 600
    },
    "listings": [{"id": "home", "lat": 51.5365, "lng": -0.0605,
                  "walking_cells": {"89194ad32cbffff": 0, "89194ad32c3ffff": 190}}],
    "include_contributors": true
  }'

access_scores weights the home cell's standard walk, transit and drive scores, each from -1 to 1. They need no walking cells, so they also count when you score an area. A negative weight favours less of it: someone looking for somewhere remote but within reach of the shops sends {"walk": -0.8, "drive": 0.6}, and someone without a car sends {"walk": 0.6, "transit": 1}.

access_mode (walk, drive or best) picks which trip the access weights score. With best, each key takes the better of the walk and the drive, but a drive counts for at most 0.6 of a walk. Driving uses the stored 15-minute drive of the cell whose scores the home took, on listings only. Dealbreakers always use walking times.

A stored walk keeps no place ids or transit modes, so nearest then carries seconds but a null poi_id and h3_r9, and transit.modes is null. A cell's walk, transit and drive access scores are null for a mode with no fetched times, and a null score is not scored.

Requests are capped: at most 1,000 listings, 5 profiles in a household, 5 places per profile, 50,000 cells per place and 150,000 place cells in the request, 2,000 walking cells per listing and 150,000 in the request, 5,000 nearest_times per listing, 200 weighted categories, 50 dealbreakers and 50 preferences. A request over a cap is a 422, except a /cells request over its cell cap, which is a 400 too_many_cells when its cells list is over the cap.

GET /v1/scores/personas returns the preference chips, the preset profiles written as chip states in preferences, and every brand in the country, usable as brand:<slug>. A country never built with cell features cannot be scored: /listings and /cells answer 404 cell_features_not_found, and personas returns brands: []. A country whose index lacks its meta document gets a 503 from /listings and /cells.

Preferences

A profile's preferences maps a chip id to nice, must, avoid or off. The chips own their keys: once preferences is sent, even as {}, every category, environment layer, transit weight and green that any chip lists is cleared, and each chip sets its own keys to its state's weight (nice 0.5, must 1, avoid -0.8; an environment chip's sign follows its direction, so a quiet street wanted is -0.5 on noise). Keys no chip lists keep the weights you send. must also adds a dealbreaker: a place within a 10-minute walk, at least one in the walking band for a counted category, or better than half of the country's homes for an environment chip. A must environment chip in a country with no data for its layer reads as unassessed (met is null), not failed. Environment chips cannot be avoided, and an unknown chip is a 422.

What a result holds

Each listing's result has the score and its band, a match from 0 to 100 (ten times the score) with its label and one-line sentence, and the three components: access (what is within walking time), commute and environment. access is null when the home has no walk and the profile weights no access_scores. contributors lists the five terms that raise the score most and the five that lower it most, in score points. must_haves says, for each dealbreaker, whether the home meets it (met is null when there is no data for it) in one plain sentence.

nearest gives, for each weighted essential category, brand and beach, the walking time to the nearest place: the quickest; when two are equally quick, the one the most sources agree on. Send exact walking times from the TravelTime Time Filter API as nearest_times to replace the cell times; they are used only when the home is scored on a posted walk.

Score an area

POST /v1/scores/cells scores each res-9 cell in an area as if it were the home. Send the profile and exactly one of cells, bbox or division. A bbox or division is read up to 10,000 cells, and truncated is true when it held more. There is no walking isochrone per cell. Each cell carries its access_scores; within_15_min_drive is only on listings. Cells come back best score first, with null scores last, cut to limit.

bash

curl -X POST -H "Authorization: Bearer $POI_API_KEY" -H "Content-Type: application/json" \
  "$POI_BASE/v1/scores/cells?country=GB" \
  -d '{"profile": {"environment": {"noise": -0.6, "crime": -0.4}},
       "bbox": [51.52, -0.09, 51.55, -0.03], "limit": 100}'

Add reach to score only the cells within a walk or drive of what the buyer needs, for example "reach": [{"mode": "walk", "key": "swimming_pool", "max_s": 600}]. The conditions are the same as the features filter: up to 10, all met, 1 to 900 seconds. Unlike a weight, a brand key here must be one of the country's searchable brands. The area is still required. Each cell then carries reach, the seconds for the filtered keys.

Full parameter and response shapes are in the API reference.