GalenaCraftpine Inc.

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.

Fig. 1

The address lookup

No key

The 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

  1. GET /lookup/v1/search

    Parameters

    q, the address, required, at most 200 characters. limit, from 1 to 10 results, 5 when not given.

    The answer

    results, a list. Each result has id, label, city, lat, lon, source. Pass its id to report.

  2. GET /lookup/v1/report

    Parameters

    id, from a search result. It has one of four forms: kit: and wat: for City of Kitchener and City of Waterloo addresses, inv:<place>: for a utility's inventory, and geo:<lat>,<lon> for a point on the map.

    The answer

    One 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, as tests is in the example below.

  3. GET /lookup/v1/health

    Parameters

    None.

    The answer

    ok, 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

Fig. 2

The open data files

No key

Galena 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:

The folder's own SHA256SUMS lists every file in it.

Schedule 2. City results, with each licence as the files state it

CityResultLinesLicence of the source dataFiles
Calgary, AlbertaFAIL384,888Open Government Licence - City of Calgary, version 2.1report.json hexes.geojson evaluation.json SHA256SUMS
Norfolk, VirginiaFAIL71,275City 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, OntarioINSUFFICIENT66,701Open Government Licence - The Corporation of the City of Kitchener, version 1.0report.json hexes.geojson evaluation.json SHA256SUMS
Waterloo, OntarioINSUFFICIENT30,972Open 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

AreasLicence of the source data
57OPEN-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.
8OPEN-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.
1City of Norfolk Open Data Policy: portal data is dedicated to the public domain, with no use restrictions.
1Contains 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.
1Creative 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.
1NYC Open Data Terms of Use
1Open Government Licence - City of Calgary, version 2.1
1Open Government Licence - City of Regina (openregina.ca)
1Open Government Licence - City of Waterloo, as the City's open data portal states it. The layer's own licence field is blank.
1Open Government Licence - The Corporation of the City of Kitchener, version 1.0
1Public 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.

Fig. 3

The app's API, with a free workspace

A 7-day key

The 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 filesexpires_after_days
7
Datasets a workspace can holdmax_datasets
3
Trainings it can start a daymax_trainings_per_day
5
Jobs it can have waiting or running at oncemax_open_jobs
2
Changes it can make a daywrites_per_day
300
Largest body of a change, other than an uploadmax_write_body_bytes
64 KiB65,536 bytes
Largest file it can uploadmax_upload_bytes
64 MiB67,108,864 bytes
Files one key can upload an houruploads_per_key_per_hour
30
Bytes one key can upload a dayupload_bytes_per_key_per_day
512 MiB536,870,912 bytes
Bytes all free workspaces together can upload a daypublic_upload_bytes_per_day
1 GiB1,073,741,824 bytes
Workspaces one address can start a dayworkspaces_per_address_per_day
3
Workspaces that can be started a day, in allworkspaces_per_day
50
Files it can uploadupload_formats
CSV
Calls a minute, for each key, and for each address without a keyRateLimit-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. 1. Upload your file of service lines, as CSV

    Call

    PUT /v1/incoming/{idem}

    What you send and get back

    The file's bytes, up to 64 MiB. {idem} is an Idempotency-Key you choose. The answer is 201 with incoming and bytes.

  2. Then make the dataset from it

    Call

    POST /v1/datasets

    What you send and get back

    The header Idempotency-Key with the same {idem}, and {"name": "My city", "kind": "service_lines", "format": "csv"}. The answer is a job.

  3. 2. Match your columns

    Call

    POST /v1/datasets/{id}/mapping

    What you send and get back

    {"dataset_id": "{id}", "fields": {...}}. fields maps 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.

  4. Follow any job

    Call

    GET /v1/jobs/{id}

    What you send and get back

    The job's id. The answer gives its status, progress, error and result_links.

  5. 3. Train and check a model

    Call

    POST /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.

  6. See the models and each check's result

    Call

    GET /v1/lsl/modelsGET /v1/lsl/evaluations/{id}

    What you send and get back

    A model's eval_report_id names its evaluation. Its gate_decision is PASS, FAIL or INSUFFICIENT (not enough data), with the reasons.

  7. Promote a model whose check passed

    Call

    POST /v1/lsl/models/{id}:promote

    What you send and get back

    An optional note. A model whose check did not pass is not promoted, and only a promoted model that passed is served.

  8. 4. Score the lines and map them

    Call

    POST /v1/lsl/scoresGET /v1/lsl/predictionsGET /v1/lsl/predictions/{service_line_id}GET /v1/lsl/rollupsGET /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).

  9. 5. Plan field checks

    Call

    POST /v1/lsl/inspection-plansGET /v1/lsl/inspection-plans

    What you send and get back

    {"mode": "MIXED", "budget_n": 200, "probability_share": 0.5, "seed": 1}. mode is REPRESENTATIVE, REPLACEMENT, MIXED or LCRI_VALIDATION.

  10. 6. Export

    Call

    POST /v1/lsl/exportsGET /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.

Fig. 4

Terms and licences

Schedule 5

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 pageCodeLive read
1The address lookup: GET /lookup/v1/search, /lookup/v1/report and /lookup/v1/health, with no keylookup deploy/lookup/app.py:1425
lookup deploy/caddy/Caddyfile:28
/lookup/v1/health 200, 17:36
/lookup/v1/search?q=200 King St W, Kitchener&limit=1 200, 17:36
/lookup/v1/report?id=kit:975827 200, 17:36
2search: q, the address, required, at most 200 characterslookup deploy/lookup/app.py:634
lookup deploy/lookup/app.py:640
lookup deploy/lookup/app.py:641
/lookup/v1/search?q=200 King St W, Kitchener&limit=1 200, 17:36
/lookup/v1/search?q= 400, 17:36
3search: limit, 1 to 10 results, 5 when not givenlookup deploy/lookup/app.py:636/lookup/v1/search?q=200 King St W, Kitchener&limit=1 200, 17:36
4search: the answer's results and each result's fieldslookup deploy/lookup/app.py:676/lookup/v1/search?q=200 King St W, Kitchener&limit=1 200, 17:36
5report: id, as a search result gives it: kit:, wat:, inv:<place>: or geo:<lat>,<lon>lookup deploy/lookup/app.py:1306
lookup deploy/lookup/app.py:1308
lookup deploy/lookup/app.py:1309
lookup deploy/lookup/app.py:1307
lookup deploy/lookup/app.py:1398
/lookup/v1/report?id=kit:975827 200, 17:36
/lookup/v1/report?id=example 400, 17:36
6report: the answer's fields, as built for each kind of idlookup deploy/lookup/app.py:1302/lookup/v1/report?id=kit:975827 200, 17:36
7health: ok, the address and record counts, the area maps, the water figures, the facts file's sha256 and any problemslookup deploy/lookup/app.py:1405
lookup deploy/lookup/app.py:1411
/lookup/v1/health 200, 17:36
8Limits: 60 searches and 40 reports a minute from one addresslookup deploy/lookup/app.py:483
lookup deploy/lookup/app.py:484
lookup deploy/lookup/app.py:633
lookup deploy/lookup/app.py:1304
Code only
9Errors: JSON with one field, error, and the status 400, 404, 429 or 502lookup deploy/lookup/app.py:627
lookup deploy/lookup/app.py:1356
lookup deploy/lookup/app.py:649
/lookup/v1/search?q= 400, 17:36
/lookup/v1/report?id=example 400, 17:36
10No query or address is logged: the lookup writes no access log, and the web front keeps no error loglookup deploy/lookup/app.py:4
lookup deploy/lookup/Dockerfile:9
lookup deploy/caddy/Caddyfile:5
Code only
11An 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 disklookup deploy/lookup/app.py:34
lookup deploy/lookup/app.py:488
lookup deploy/lookup/app.py:502
lookup deploy/lookup/app.py:517
Code only
12Browsers allow calls from craftpine.com pages onlylookup deploy/caddy/Caddyfile:10/lookup/v1/health with Origin: https://www.craftpine.com 200, 17:36
/lookup/v1/health with Origin: https://example.org 200, 17:36
13City results: index.json, and each city's report.json, hexes.geojson, evaluation.json and SHA256SUMSlookup deploy/cities/publish.py:289
lookup deploy/cities/publish.py:166
lookup deploy/caddy/Caddyfile:39
/data/cities/index.json 200, 17:36
/data/cities/SHA256SUMS 200, 17:36
14Area files: index.json (galena.check-index/1), one file for each area, each cell a list in the order of fields, positions scaledlookup deploy/lookup/prep_area_map.py:702
lookup deploy/lookup/prep_area_map.py:717
/data/check/index.json 200, 17:36
/data/check/kitchener-on.json 200, 17:36
/data/check/SHA256SUMS 200, 17:36
15The data files may be kept by a cache for 5 minuteslookup deploy/caddy/Caddyfile:44/data/cities/index.json 200, 17:36
16POST /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 keyapp deploy/compose/compose_app.py:1106
app deploy/compose/compose_app.py:566
app deploy/compose/compose_app.py:570
app deploy/compose/compose_app.py:179
app deploy/compose/compose_app.py:22
Not called by the build: each call makes a workspace in the live database.
17A request that says it comes from another site's page is refusedapp deploy/compose/compose_app.py:582
app deploy/compose/compose_app.py:160
Code only
18GET /v1/public/workspaces/limits and the limits in Schedule 4app deploy/compose/compose_app.py:1105
app deploy/compose/compose_app.py:201
app deploy/compose/compose_app.py:100
/v1/public/workspaces/limits 200, 17:36
19A workspace key's scopes, never adminapp deploy/compose/compose_app.py:118/v1/public/workspaces/limits 200, 17:36
20Send the key as Authorization: Bearer <key> or X-API-Key: <key>app packages/platform/src/craftpine/api/platform_app.py:782
app packages/platform/src/craftpine/api/platform_app.py:786
/v1/datasets 401, 17:36
21The writes a workspace key may make, and the refusal of any otherapp deploy/compose/compose_app.py:140
app deploy/compose/compose_app.py:793
Code only
22Upload: PUT /v1/incoming/{idem} with the file's bytes, then POST /v1/datasets with the same Idempotency-Keyapp deploy/compose/compose_app.py:1104
app deploy/compose/compose_app.py:552
app deploy/compose/compose_app.py:92
app packages/platform/src/craftpine/api/catalog.py:121
/v1/datasets 401, 17:36
galena-openapi.json
23Match columns: POST /v1/datasets/{id}/mapping, fields from each column in the file to a Galena fieldapp packages/platform/src/craftpine/api/catalog.py:147
app packages/platform/src/craftpine/api/schemas_api.py:398
app web/src/domain/lines.ts:38
galena-openapi.json
24Follow a job: GET /v1/jobs/{id}app packages/platform/src/craftpine/api/catalog.py:160
app packages/platform/src/craftpine/api/schemas_api.py:279
galena-openapi.json
25Train 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 servedapp packages/platform/src/craftpine/api/catalog.py:467
app packages/platform/src/craftpine/api/catalog.py:481
app packages/platform/src/craftpine/api/catalog.py:714
app packages/platform/src/craftpine/api/catalog.py:493
app web/src/views/WorkView.tsx:48
app web/src/views/WorkView.tsx:898
app packages/platform/src/craftpine/lsl/diff.py:27
app packages/platform/src/craftpine/api/README.md:27
galena-openapi.json
26Score and map: POST /v1/lsl/scores, GET /v1/lsl/predictions, /v1/lsl/predictions/{service_line_id}, /v1/lsl/rollups and /v1/lsl/estimatesapp packages/platform/src/craftpine/api/catalog.py:507
app packages/platform/src/craftpine/api/catalog.py:534
app packages/platform/src/craftpine/api/catalog.py:547
app packages/platform/src/craftpine/api/catalog.py:583
app packages/platform/src/craftpine/api/catalog.py:596
galena-openapi.json
27Plan: POST /v1/lsl/inspection-plans and GET /v1/lsl/inspection-plansapp packages/platform/src/craftpine/api/catalog.py:609
app packages/platform/src/craftpine/api/catalog.py:623
galena-openapi.json
28Export: POST /v1/lsl/exports; the finished job links to a signed download, GET /files/{key}, until it expiresapp packages/platform/src/craftpine/api/catalog.py:674
app deploy/compose/compose_app.py:1108
app web/src/api/galena.ts:247
app deploy/compose/compose_app.py:1041
galena-openapi.json
29A POST that creates a job or a record needs an Idempotency-Keyapp packages/platform/src/craftpine/api/platform_app.py:387
app packages/platform/src/craftpine/api/platform_app.py:69
galena-openapi.json
30Lists come in pages: cursor, and limit from 1 to 100, 50 when not givenapp packages/platform/src/craftpine/api/spec.py:193galena-openapi.json
31Errors are problem documents (RFC 9457): type, title, status, detail and instanceapp packages/platform/src/craftpine/api/problems.py:12
app packages/platform/src/craftpine/api/problems.py:33
/v1/datasets 401, 17:36
galena-openapi.json
32The app's rate limit, for each key and for each address without a keyapp packages/platform/src/craftpine/api/config.py:61
app packages/platform/src/craftpine/api/platform_app.py:436
/v1/health 200, 17:36
33galena-openapi.json: generated by the API on a host that offers Galena alone, every reference inside the fileapp packages/platform/src/craftpine/api/__main__.py:19
app packages/platform/src/craftpine/api/bundle.py:115
app packages/platform/src/craftpine/api/offer.py:17
/v1/version 200, 17:36
galena-openapi.json
34Terms: craftpine.com's terms of use, its privacy page, and the contact address the terms giveLive onlyhttps://www.craftpine.com/terms 200, 17:36
https://www.craftpine.com/privacy 200, 17:36