Plug into Galena.
Three things are open to anyone today, at no cost: the address lookup, the open data files, and the app's API with a free workspace that lasts 7 days. This page says what each one offers, how to call it, its limits and the terms that apply.
Each endpoint, parameter, field and limit here was checked against Galena's code and against a live response. Last read: 2026-11-03 17:36 Toronto time. Schedule 5 lists where each fact comes from.
The address lookup
No keyThe lookup answers one question about an address: what does the public record say about its water service pipe, and how sure is that? It is the service behind the address check on this site. It reads only, and it gives no model's answer about any single home.
- Base address
- https://galena.craftpine.com/lookup/v1/
- Methods
- GET
- Key
- None
- Limits
- 60 searches and 40 reports a minute from one address
Schedule 1. Endpoints of the lookup
-
GET /lookup/
v1/ search Parametersq, the address, required, at most 200 characters.limit, from 1 to 10 results, 5 when not given.The answerresults, a list. Each result hasid,label,city,lat,lon,source. Pass itsidto report. -
GET /lookup/
v1/ report Parametersid, from a search result. It has one of four forms:kit:andwat:for City of Kitchener and City of Waterloo addresses,inv:<place>:for a utility's inventory, andgeo:<lat>,<lon>for a point on the map.The answerOne answer. Its fields:
address,verdict,record,building,area,tests,programme,how_to_check,sources,map,geometry,area_map,water,fix,testing. A field with nothing to say for an address is null, astestsis in the example below. -
GET /lookup/
v1/ health ParametersNone.
The answerok,kitchener_addresses,waterloo_addresses,inventory_records,area_maps,water,facts_sha256,problems.
An error is JSON with one field, error, in plain words. The status is 400 for a missing or bad parameter, and 404 for an id that is not in the list. It is 429 past the limit, and 502 when the map search behind the lookup does not answer. For example, a search with no address answers 400 and {"error":"Type an address."}.
Example: search for Kitchener City Hall
curl 'https://galena.craftpine.com/lookup/v1/search?q=200+King+St+W,+Kitchener&limit=1'
{"results":[{"id":"kit:975827","label":"200 King St W, Kitchener, ON","city":"Kitchener","lat":43.451764,"lon":-80.492419,"source":"City of Kitchener address points"}]}
Example: the report for that result, a few of its fields
curl 'https://galena.craftpine.com/lookup/v1/report?id=kit:975827'
"verdict": {"band": "low_chance", "certainty": "low",
"headline": "This building was built in 1993, after 1980. Lead is unlikely, though no record names the pipe material."}
"record": {"publisher": "City of Kitchener (Kitchener Utilities)", "material": "not recorded",
"install_year": 1992, "licence": "Open Government Licence - City of Kitchener"}
Fig. 1. Read live on 2026-11-03 17:36 Toronto time. Kitchener City Hall is a public building. Use public buildings in your own examples and tests.
What happens to an address you send
- The lookup does not log a query or an address. Its server writes no access log, and the web front in front of it keeps no error log, where request addresses would appear.
- When the lookup's own lists do not hold an address, it sends the text you typed to OpenStreetMap's Nominatim search, to place it on the map. It does so at most once a second. It keeps the last 5,000 such searches in memory for up to a day, and never on disk.
- Browsers let pages on craftpine.com call the lookup, and no other site. Call it from your own server or script.
- Each answer names its sources, with their licences, in
sources.
The open data files
No keyGalena publishes what it counted and what its checks found, as files anyone can download. Each file names its source and the licence of that source. A cache may keep a file for 5 minutes.
City results
The index is /data/cities/index.json. It holds generated, cities, not_published, method. Each of its 4 cities has id, name, province_or_state, country, lines, decision, headline, bbox, licence, source_url, updated, retrieved, publisher, report, hexes, tiles.
Each city's folder holds four files:
report.json, the city's result. Its fields:schema,id,name,province_or_state,country,generated,decision,headline,reasons,metrics,sufficiency,expected_lead_per_cell,estimate,counts,frame,labels_rule,note,method,data_card,bbox,clearance,release_bound,plain,false_non_lead,false_non_lead_field_checked,calibration,equity_failures,caveats,repeat.hexes.geojson, a GeoJSON FeatureCollection of map cells. Each cell hascell,lines,recorded_lead,recorded_grr,recorded_not_lead,recorded_unknown,share_unknown.evaluation.json, the check's full record. Its fields:schema_version,report_id,dataset_card,model_card,split_strategy,metrics,calibration,conformal,bias_diagnostics,equity,red_team,leakage,drift,gate_decision,versions,seeds.SHA256SUMS, the sha256 of the other three.
The folder's own SHA256SUMS lists every file in it.
Schedule 2. City results, with each licence as the files state it
| City | Result | Lines | Licence of the source data | Files |
|---|---|---|---|---|
| Calgary, Alberta | FAIL | 384,888 | Open Government Licence - City of Calgary, version 2.1 | report.json hexes.geojson evaluation.json SHA256SUMS |
| Norfolk, Virginia | FAIL | 71,275 | City of Norfolk Open Data Policy: portal data is dedicated to the public domain, with no use restrictions. | report.json hexes.geojson evaluation.json SHA256SUMS |
| Kitchener, Ontario | INSUFFICIENT | 66,701 | Open Government Licence - The Corporation of the City of Kitchener, version 1.0 | report.json hexes.geojson evaluation.json SHA256SUMS |
| Waterloo, Ontario | INSUFFICIENT | 30,972 | Open Government Licence - City of Waterloo, as the City's open data portal states it. The layer's own licence field is blank. | report.json hexes.geojson evaluation.json SHA256SUMS |
Schedule 2. Read live on 2026-11-03 17:36 Toronto time. When this page was built, every file's sha256 matched both its city's SHA256SUMS and the folder's. Check a download the same way: in its folder, run sha256sum -c SHA256SUMS.
Area files
The index is /data/check/index.json, schema galena.check-index/1. It lists 74 areas. Each area file counts the records in map cells (H3 resolution 8, about 0.7 square kilometres a cell). A cell with fewer than 10 records is left out. The index holds schema, generated, code, unit, min_cell, classes, fields, corners, cities; each area in it has id, name, region, country, file, url, sha256, bytes, counts, cells, totals, suppressed, bbox, source, inputs, notes, left_out. In an area file, each cell is a list in the order its fields gives: cell, lon, lat, lines, lead, grr, not_lead, unknown, corners. Positions are in units of 1/100,000 of a degree (scale). The folder's SHA256SUMS lists every file.
Schedule 3. Licences of the area files, as the files state them
| Areas | Licence of the source data |
|---|---|
| 57 | OPEN-NY Terms of Use and the New York State Department of Health Data Use Policy (https://www.health.ny.gov/about/data_use.htm). Source: New York State Department of Health, New York State Lead Service Line Inventory, published 2025-09-19, last updated 2026-10-06, accessed 2026-11-03. The New York State Department of Health makes no representation, warranty or guarantee relating to the data or analyses derived from these data. |
| 8 | OPEN-NY Terms of Use and the New York State Department of Health Data Use Policy (https://www.health.ny.gov/about/data_use.htm). Source: New York State Department of Health, New York State Lead Service Line Inventory, published 2025-09-19, last updated 2026-10-06, accessed 2026-10-24. The New York State Department of Health makes no representation, warranty or guarantee relating to the data or analyses derived from these data. |
| 1 | City of Norfolk Open Data Policy: portal data is dedicated to the public domain, with no use restrictions. |
| 1 | Contains information licensed under the Open Government Licence – Barrie (https://www.barrie.ca/Online%20Services/PublishingImages/OpenData_Images/COB_DataLicense.pdf). The City of Barrie does not endorse Galena. |
| 1 | Creative Commons Attribution 4.0 (CC BY 4.0, https://creativecommons.org/licenses/by/4.0/), Ville de Montréal. Modified by Galena: one result per service entry, the latest conclusive one, with the City's codes put in plain words. The Ville de Montréal does not endorse Galena. |
| 1 | NYC Open Data Terms of Use |
| 1 | Open Government Licence - City of Calgary, version 2.1 |
| 1 | Open Government Licence - City of Regina (openregina.ca) |
| 1 | Open Government Licence - City of Waterloo, as the City's open data portal states it. The layer's own licence field is blank. |
| 1 | Open Government Licence - The Corporation of the City of Kitchener, version 1.0 |
| 1 | Public domain. The City of Aurora's Lead Service Replacement hub says its data "is in the public domain and may be copied without permission; citation of the source is appreciated." Source: City of Aurora, IL, Data Analytics (AuroraData), Water Service Material Inventory, read 2026-11-03. The City updates the inventory daily, so values can change. |
Schedule 3. Read live on 2026-11-03 17:36 Toronto time. Each area's own entry in the index names its publisher, dataset and source address beside the licence.
The app's API, with a free workspace
A 7-day keyThe app at galena.craftpine.com runs on an HTTP API. A free workspace gives you a key for 7 days and a private space for your own file of service lines. With the key you can take the same steps as the app's own pages. You upload the file, match its columns, train and check a model, map the results, plan field checks and export.
- Base address
- https://galena.craftpine.com/v1/
- Key
- Authorization: Bearer <key>, or X-API-Key: <key>
- Errors
- application/problem+json
- Schema
- galena-openapi.json
Get a key
curl -X POST https://galena.craftpine.com/v1/public/workspaces
This needs no key and no body. The answer holds key, workspace_id, expires_at, scopes, limits and note. The key is shown once and cannot be recovered, so keep it. It carries these scopes, and never admin: platform:read, platform:write, jobs:read, jobs:write, lsl:read, lsl:write, lsl:promote. A request that says it comes from another site's page is refused, so start a workspace from a script or from the app itself. When the workspace ends, an hourly cleanup deletes its data, its files and its key. Only the record that it existed and its audit trail stay.
Every limit is public, at /v1/public/workspaces/limits. Schedule 4 gives them as read on 2026-11-03 17:36 Toronto time. In its words: “Each workspace is a separate tenant with its own encryption key. No other workspace can read its data. The data is deleted when the workspace expires.”
Schedule 4. Limits of a free workspace
- A workspace lasts this many days, then it is deleted with its files
expires_after_days - 7
- Datasets a workspace can hold
max_datasets - 3
- Trainings it can start a day
max_trainings_per_day - 5
- Jobs it can have waiting or running at once
max_open_jobs - 2
- Changes it can make a day
writes_per_day - 300
- Largest body of a change, other than an upload
max_write_body_bytes - 64 KiB65,536 bytes
- Largest file it can upload
max_upload_bytes - 64 MiB67,108,864 bytes
- Files one key can upload an hour
uploads_per_key_per_hour - 30
- Bytes one key can upload a day
upload_bytes_per_key_per_day - 512 MiB536,870,912 bytes
- Bytes all free workspaces together can upload a day
public_upload_bytes_per_day - 1 GiB1,073,741,824 bytes
- Workspaces one address can start a day
workspaces_per_address_per_day - 3
- Workspaces that can be started a day, in all
workspaces_per_day - 50
- Files it can upload
upload_formats - CSV
- Calls a minute, for each key, and for each address without a key
RateLimit-Policy header - 60 in 60 s
The steps, in order
A workspace key may make only the writes below. Any other write is refused with 403. A POST that creates a job or a record needs an Idempotency-Key header: a value you choose, of letters, digits and . _ : -, up to 255 characters. Long work returns 202 and a job; follow it until its status is succeeded, failed or cancelled. Lists come in pages, with cursor and limit (1 to 100, 50 when not given).
-
1. Upload your file of service lines, as CSV
CallPUT /v1/incoming/
{idem} What you send and get backThe file's bytes, up to 64 MiB.
{idem}is an Idempotency-Key you choose. The answer is 201 withincomingandbytes. -
Then make the dataset from it
CallPOST /v1/datasets
What you send and get backThe header
Idempotency-Keywith the same{idem}, and{"name": "My city", "kind": "service_lines", "format": "csv"}. The answer is a job. -
2. Match your columns
CallPOST /v1/datasets/
{id}/ mapping What you send and get back{"dataset_id": "{id}", "fields": {...}}.fieldsmaps each column name in your file to a Galena field: service_line_id, longitude, latitude, year_built, inventory_category, tract_geoid, block_geoid, notes or material. -
Follow any job
CallGET /v1/jobs/
{id} What you send and get backThe job's id. The answer gives its
status,progress,errorandresult_links. -
3. Train and check a model
CallPOST /v1/lsl/
models What you send and get back{"algorithm": "lightgbm", "training_snapshot_id": "{dataset id}", "params": {"seed": 1}}. The app offers these algorithm names: year-built-rule, records-at-face-value, logistic-regression, lightgbm, xgboost and catboost. Training also checks the model on lines it did not see. -
See the models and each check's result
CallGET /v1/lsl/
models GET /v1/lsl/evaluations/ {id} What you send and get backA model's
eval_report_idnames its evaluation. Itsgate_decisionis PASS, FAIL or INSUFFICIENT (not enough data), with the reasons. -
Promote a model whose check passed
CallPOST /v1/lsl/
models/ {id}:promote What you send and get backAn optional
note. A model whose check did not pass is not promoted, and only a promoted model that passed is served. -
4. Score the lines and map them
CallPOST /v1/lsl/
scores GET /v1/lsl/predictions GET /v1/lsl/predictions/ GET /v1/lsl/{service_line_id} rollups GET /v1/lsl/estimates What you send and get back{"model_version_id": "{model id}", "data_snapshot_id": "{dataset id}"}, then read the predictions, one line's prediction, the counts by area (level, such as H3) and the estimates (scope, such as SYSTEM). -
5. Plan field checks
CallPOST /v1/lsl/
inspection-plans GET /v1/lsl/inspection-plans What you send and get back{"mode": "MIXED", "budget_n": 200, "probability_share": 0.5, "seed": 1}.modeis REPRESENTATIVE, REPLACEMENT, MIXED or LCRI_VALIDATION. -
6. Export
CallPOST /v1/lsl/
exports GET /files/{key} What you send and get back{"format": "csv", "model_version_id": "{model id}"}. The app offers csv, geojson, gpkg and lcri_baseline. The finished job links to a signed download, which works until it expires.
A call without a key answers 401, for example {"detail":"Send a bearer token or an X-API-Key.","instance":"/v1/datasets","status":401,"title":"Unauthorized","type":"https://example.invalid/problems/unauthorized"}. Every call takes from a rate limit of 60 calls in 60 seconds, for each key, and for each address that calls without a key. The headers RateLimit-Policy and RateLimit say where you stand.
The schema file
galena-openapi.json describes the API as OpenAPI 3.1. It is generated by the API's own code, for this host. It holds 33 paths and 47 operations, every schema they use is inside the one file, and its server is https://galena.craftpine.com. Version 1.2.0, 343,110 bytes, sha256 98dd5cab656bd1b1c26a3393eff3debc261f1fe36a15d56cfb83ac8213a17895. It also lists operations a workspace key cannot use, such as making API keys or webhooks. The upload, the workspace and the download routes above belong to this host's front and are described on this page, not in the file.
Terms and licences
- This website, craftpine.com, has Craftpine's terms of use and its privacy page. Questions about them go to hello@craftpine.com, the address the terms give.
- Each data file names the licence of the data it comes from. For the city results it is in Schedule 2, for the area files in Schedule 3, and for a lookup answer in its
sources. Read that licence before you reuse the data. - Galena's own terms of use and privacy note cover the lookup, the data files, the app's API and its free workspaces. Questions go to hello@craftpine.com.
Where each fact on this page comes from
Each row names the code it was checked against, with the file and the line. It also names the live reads it was checked against, with the status and the time read on 2026-11-03. The app's code, marked app, is as of commit dbb5ce405187. The code of the lookup and the data files, marked lookup, is as of commit 3c6444ac876e. Each is the last commit that changed the files cited. Galena's code is private for now.
| No. | Fact on this page | Code | Live read |
|---|---|---|---|
| 1 | The address lookup: GET /lookup/v1/search, /lookup/v1/report and /lookup/v1/health, with no key | lookup deploy/ lookup deploy/ | / / / |
| 2 | search: q, the address, required, at most 200 characters | lookup deploy/ lookup deploy/ lookup deploy/ | / / |
| 3 | search: limit, 1 to 10 results, 5 when not given | lookup deploy/ | / |
| 4 | search: the answer's results and each result's fields | lookup deploy/ | / |
| 5 | report: id, as a search result gives it: kit:, wat:, inv:<place>: or geo:<lat>,<lon> | lookup deploy/ lookup deploy/ lookup deploy/ lookup deploy/ lookup deploy/ | / / |
| 6 | report: the answer's fields, as built for each kind of id | lookup deploy/ | / |
| 7 | health: ok, the address and record counts, the area maps, the water figures, the facts file's sha256 and any problems | lookup deploy/ lookup deploy/ | / |
| 8 | Limits: 60 searches and 40 reports a minute from one address | lookup deploy/ lookup deploy/ lookup deploy/ lookup deploy/ | Code only |
| 9 | Errors: JSON with one field, error, and the status 400, 404, 429 or 502 | lookup deploy/ lookup deploy/ lookup deploy/ | / / |
| 10 | No query or address is logged: the lookup writes no access log, and the web front keeps no error log | lookup deploy/ lookup deploy/ lookup deploy/ | Code only |
| 11 | An address the lookup's own lists do not hold goes to OpenStreetMap's Nominatim search, at most once a second; the last 5,000 such searches stay in memory for up to a day, never on disk | lookup deploy/ lookup deploy/ lookup deploy/ lookup deploy/ | Code only |
| 12 | Browsers allow calls from craftpine.com pages only | lookup deploy/ | / / |
| 13 | City results: index.json, and each city's report.json, hexes.geojson, evaluation.json and SHA256SUMS | lookup deploy/ lookup deploy/ lookup deploy/ | / / |
| 14 | Area files: index.json (galena.check-index/1), one file for each area, each cell a list in the order of fields, positions scaled | lookup deploy/ lookup deploy/ | / / / |
| 15 | The data files may be kept by a cache for 5 minutes | lookup deploy/ | / |
| 16 | POST /v1/public/workspaces: no key and no body; the answer holds key, workspace_id, expires_at, scopes, limits and note; the key is shown once; an hourly cleanup deletes an ended workspace's data, files and key | app deploy/ app deploy/ app deploy/ app deploy/ app deploy/ | Not called by the build: each call makes a workspace in the live database. |
| 17 | A request that says it comes from another site's page is refused | app deploy/ app deploy/ | Code only |
| 18 | GET /v1/public/workspaces/limits and the limits in Schedule 4 | app deploy/ app deploy/ app deploy/ | / |
| 19 | A workspace key's scopes, never admin | app deploy/ | / |
| 20 | Send the key as Authorization: Bearer <key> or X-API-Key: <key> | app packages/ app packages/ | / |
| 21 | The writes a workspace key may make, and the refusal of any other | app deploy/ app deploy/ | Code only |
| 22 | Upload: PUT /v1/incoming/{idem} with the file's bytes, then POST /v1/datasets with the same Idempotency-Key | app deploy/ app deploy/ app deploy/ app packages/ | / galena-openapi.json |
| 23 | Match columns: POST /v1/datasets/{id}/mapping, fields from each column in the file to a Galena field | app packages/ app packages/ app web/ | galena-openapi.json |
| 24 | Follow a job: GET /v1/jobs/{id} | app packages/ app packages/ | galena-openapi.json |
| 25 | Train and check: POST /v1/lsl/models, GET /v1/lsl/models, GET /v1/lsl/evaluations/{id}, POST /v1/lsl/models/{id}:promote; the algorithm names the app offers; a model that did not pass is not promoted or served | app packages/ app packages/ app packages/ app packages/ app web/ app web/ app packages/ app packages/ | galena-openapi.json |
| 26 | Score and map: POST /v1/lsl/scores, GET /v1/lsl/predictions, /v1/lsl/predictions/{service_line_id}, /v1/lsl/rollups and /v1/lsl/estimates | app packages/ app packages/ app packages/ app packages/ app packages/ | galena-openapi.json |
| 27 | Plan: POST /v1/lsl/inspection-plans and GET /v1/lsl/inspection-plans | app packages/ app packages/ | galena-openapi.json |
| 28 | Export: POST /v1/lsl/exports; the finished job links to a signed download, GET /files/{key}, until it expires | app packages/ app deploy/ app web/ app deploy/ | galena-openapi.json |
| 29 | A POST that creates a job or a record needs an Idempotency-Key | app packages/ app packages/ | galena-openapi.json |
| 30 | Lists come in pages: cursor, and limit from 1 to 100, 50 when not given | app packages/ | galena-openapi.json |
| 31 | Errors are problem documents (RFC 9457): type, title, status, detail and instance | app packages/ app packages/ | / galena-openapi.json |
| 32 | The app's rate limit, for each key and for each address without a key | app packages/ app packages/ | / |
| 33 | galena-openapi.json: generated by the API on a host that offers Galena alone, every reference inside the file | app packages/ app packages/ app packages/ | / galena-openapi.json |
| 34 | Terms: craftpine.com's terms of use, its privacy page, and the contact address the terms give | Live only | https:/ https:/ |