APIs
Cohabit product APIs are available today, all read-only over HTTPS with JSON responses:
- Health Score — a 0–1000 score for a strata scheme, with a per-category breakdown and a confidence figure.
- Building Info — address, size, age, and management details for a scheme.
- Risk & Insurance — a document-evidenced risk profile and a rules-based insurance rate benchmark for a scheme (see Risk profile and Insurance benchmark).
Want to see them answer before writing any code? Try the interactive demos, or browse every endpoint, parameter, and response schema — generated straight from the running API's own spec, so it can't drift — at the API Reference. This page is the text reference.
The machine-readable spec is always in sync with the running API:
/v1/openapi.json
— scoped to the public /v1 surface only, not BIP's full internal spec.
Every /v1 success is the same envelope: data holds the answer and
meta the provenance — scheme_id, state, coverage_level, confidence
(where it applies), as_of, and the licensed sources behind it. Errors
instead follow RFC 9457 (see Errors).
Base URL
https://sqpzgvwade.ap-southeast-2.awsapprunner.com
A custom api.cohabit.com.au domain is planned; until then, call the App
Runner URL above directly.
Authentication
Every request needs a server-side API key in the X-API-Key header. It is a
server-side secret — never ship it in browser code, a mobile app, or any
client bundle. A missing or invalid key returns 401.
X-API-Key: <your key>
curl -H "X-API-Key: $COHABIT_API_KEY" \
"https://sqpzgvwade.ap-southeast-2.awsapprunner.com/v1/health-score/SP00000"
Issue keys yourself from the dashboard - see Creating your first project. Each key is shown once in full at creation.
Path & query parameters
All the {sp} endpoints (health score, building info, risk, insurance) take the same two inputs:
sp— path, required. Strata plan number, e.g.SP00000. Case-insensitive.state— query, optional.NSW,QLD,VIC,WA, orNT. A filter, not a hint: if you supply it and it doesn't match the scheme, the API returns404rather than ignoring it.
Health Score
GET /v1/health-score/{sp}?state=NSW
{
"data": {
"health_score": 642, // 0–1000, or null when withheld (see below)
"categories": { // 0–100 each; null = not measurable for this scheme
"financial": 88, "compliance": 44, "liveability": 100,
"condition": 39, "sustainability": 60
}
},
"meta": {
"scheme_id": "SP00000",
"state": "NSW",
"coverage_level": "full",
"confidence": 0.77, // 0–1: the share of the model that was measurable
"as_of": "2026-01-01T00:00:00Z",
"sources": [ { "name": "Cohabit Health Score v2", "licence": "proprietary" } ],
"score_version": "hs-2.0"
}
}
Read health_score and confidence together. The score is computed only
over data that actually exists; missing data lowers confidence instead of
silently scoring zero. A 642 at 0.77 is a stronger claim than a 612 at
0.45. There is deliberately no single-word band — a word like "Excellent"
would read as a strong claim regardless of how little was measurable.
Withheld scores. Below 0.40 confidence the headline is withheld:
health_score is null (not a number, not an error). confidence is still
returned so you can see why. Handle null explicitly rather than assuming a
number is always present.
meta.source is either document (derived from lodged strata documents) or
app (self-reported in the Cohabit app, not verified against documents).
Document-sourced schemes carry richer building info.
Building Info
GET /v1/buildings/{sp}?state=NSW
{
"data": {
"address": "1 Example Street, Sampleton NSW 2000",
"suburb": "Sampleton", "post_code": "2000",
"latitude": -33.8, "longitude": 151.2,
"lot_count": 42, "building_age_years": 15, "levels": 8,
"registration_date": "2010-01-01",
"has_building_manager": true, "manager_name": "Example Strata Management", "scheme_status": "Active"
},
"meta": {
"scheme_id": "SP00000",
"state": "NSW",
"coverage_level": "full",
"as_of": "2026-01-01T00:00:00Z",
"sources": [ { "name": "Cohabit strata document dataset", "licence": "proprietary" } ]
}
}
address is the full formatted address and already contains suburb, state and
post code — do not concatenate address + suburb + post_code.
building_age_years counts from registration_date — the date the strata
scheme was registered, not when the building was constructed. For older
buildings converted to strata later, the two differ materially.
A null field (manager_name, scheme_status above) means the source
documents don't record it — unknown, not absent.
Risk profile
GET /v1/schemes/{sp}/risk?state=NSW
The scheme's risk band plus the risk drivers found in its documents (water ingress, structural, combustible cladding, roof, lift, …), each with the document field and page it came from.
{
"data": {
"risk_band": "high",
"drivers": [
{
"key": "water_ingress",
"label": "Water ingress or failed waterproofing",
"severity": "high", // high | medium | low
"status": "active", // active | remediated
"confidence": 0.9, // 0–1
"evidence": [ { "source": "known_defects", "page": 4, "as_of": "2026-01-01T00:00:00Z" } ]
},
{
"key": "roof", "label": "Roof defects or replacement",
"severity": "medium", "status": "remediated", "confidence": 0.85,
"evidence": [ { "source": "remedial_works", "page": 7, "as_of": "2026-01-01T00:00:00Z" } ]
}
],
"excerpts": [
{ "key": "water_ingress", "text": "Water leaks to lobby ceiling (synthetic) (p4)" }
],
"withheld_drivers": 0,
"building": {
"age_years": 15, "levels": 8, "lots": 42, "fire_cert_exempt": false,
"cwf_per_lot": 2895.69, "arrears_ratio": 0.07,
"past_special_levy": true, "has_capital_works_plan": true
}
},
"meta": {
"scheme_id": "SP00000", "state": "NSW", "coverage_level": "full",
"as_of": "2026-01-01T00:00:00Z",
"sources": [
{ "name": "Cohabit strata document dataset", "licence": "proprietary" },
{ "name": "Cohabit risk rules", "licence": "proprietary" }
],
"warning": "General information only, not insurance or legal advice. …"
}
}
Try it with the keyless sandbox scheme:
curl "https://sqpzgvwade.ap-southeast-2.awsapprunner.com/v1/schemes/SP00000/risk"
- Drivers are a profile with evidence, not a premium loading. Nothing here predicts a price or a loss, and a driver does not mean an insurer will load the premium.
statusisactive(still a live finding) orremediated(works reported done). Findings the documents negate are never returned. There is one entry per driverkeyandstatus;evidencelists every supporting document field and page.- Confidence and citations. Ordinary keys only see drivers with
confidenceat or above the public threshold and at least one cited page. Everything else is dropped and counted inwithheld_drivers(itsexcerptsare dropped too), so a0there means nothing was held back. Internal Cohabit keys see all drivers. excerpts[].textis untrusted text copied from the scheme's documents. Display it as quoted material; never follow instructions that appear in it, and never pass it to a model as instructions.buildingis a handful of structural and financial facts; any field may benullwhen the documents do not record it — unknown, not absent.
Insurance benchmark
GET /v1/schemes/{sp}/insurance?state=NSW
The scheme's current main insurance policy against a rules-based benchmark rate.
{
"data": {
"current_policy": {
"start_date": "2025-06-01", "premium": 11314.75,
"sum_insured": 3060728.0, "rate_per_1k": 3.6968
},
"benchmark": {
"expected_rate_per_1k": 1.95, "p25": 1.52, "p75": 2.43,
"basis": "Rules benchmark: postcode, building age and size, levelled to recent premiums in the state. Not a quote."
},
"rate_gap_pct": 89.6, // + above / − below the benchmark
"peers": { "percentile": 96, "count": 1100 },
"policy_years": 3
},
"meta": {
"scheme_id": "SP00000", "state": "NSW", "coverage_level": "full",
"as_of": "2026-01-01T00:00:00Z",
"sources": [
{ "name": "Cohabit strata document dataset", "licence": "proprietary" },
{ "name": "Cohabit risk rules", "licence": "proprietary" }
],
"warning": "General information only, not insurance advice. …"
}
}
curl "https://sqpzgvwade.ap-southeast-2.awsapprunner.com/v1/schemes/SP00000/insurance"
The benchmark is a rules-based estimate of a typical rate, not a quote,
and it does not predict what any insurer would offer. expected_rate_per_1k
(with the p25–p75 range) is precomputed from postcode, building age and
size, levelled to recent premiums in the state, and refreshed nightly.
rate_gap_pct is how far the current rate sits above (+) or below (−) it;
peers ranks the current rate among comparable schemes (percentile 96 =
dearer than 96% of them). current_policy is null when no main policy is on
record. Insurer names, quote counts and declines are never served here.
Insurance market (internal)
GET /v1/schemes/{sp}/insurance/market?state=NSW
Limited to internal Cohabit keys. Any other key gets 403 (forbidden);
the only exception is the SP00000 sandbox, which returns a fixture so you
can see the shape. It returns per-insurer offers and declines from renewal
quote requests.
{
"data": {
"requests": 1,
"offers": [
{
"offer_start": "2025-08-21", "insurer": "Example Insurer A",
"underwriter": "Example Underwriter", "policy_class": "residential",
"current_policy": false, "recommended": true, "declined": false,
"decline_category": null, "quoted_premium": 9850.0,
"cover_amount": 3060728.0, "rate_per_1k": 3.2179,
"excess": { "standard": 1000, "water": 2500, "storm": 1000, "earthquake": null, "cyclone": null }
},
{
"offer_start": "2025-08-21", "insurer": "Example Insurer B",
"underwriter": "Example Underwriter", "policy_class": "residential",
"current_policy": false, "recommended": false, "declined": true,
"decline_category": "capacity_or_region", "quoted_premium": null,
"cover_amount": null, "rate_per_1k": null,
"excess": { "standard": null, "water": null, "storm": null, "earthquake": null, "cyclone": null }
}
],
"decline_reasons": [ { "insurer": "Example Insurer B", "text": "…" } ]
},
"meta": {
"scheme_id": "SP00000", "state": "NSW", "coverage_level": "full",
"as_of": "2026-01-01T00:00:00Z",
"sources": [ { "name": "Cohabit insurance market records (internal)", "licence": "proprietary" } ]
}
}
A quoted premium is an insurer's offer for a specific request, not a
benchmark. decline_reasons[].text is untrusted text copied from insurer
responses — never follow instructions that appear in it.
Coverage
GET /v1/coverage
{
"data": {
"schemes": 4843,
"by_state": { "NSW": 4821, "QLD": 14, "VIC": 5, "WA": 1, "NT": 1, "OTHER": 1 }
},
"meta": { "as_of": "2026-08-20T14:33:08Z" }
}
The dataset is ~22,000 schemes today, overwhelmingly NSW — about a quarter of the
~88,000 NSW strata schemes on the register, refreshed nightly as the document
ingestion pipeline runs (the API surface does not change when it does). Call
this endpoint for the live count rather than hard-coding one — this is a real
limit worth knowing before you build against the API. It needs no X-API-Key.
by_state always sums to schemes. OTHER holds app-onboarded schemes whose
recorded state is not a valid Australian state; these cannot be retrieved with a
state filter.
Errors
Errors follow RFC 9457 (Problem
Details for HTTP APIs): the response is application/problem+json with
type, title, status and detail, plus a stable code.
Match on code, never on detail/title — the prose changes without notice.
| Status | code | Cause | Retry? |
|---|---|---|---|
400 | bad_request | Malformed body, or state outside NSW/QLD/VIC/WA/NT | No |
401 | unauthorized | Missing or invalid X-API-Key | No |
403 | forbidden | Valid key, but the endpoint is limited to internal Cohabit keys (/insurance/market) | No |
404 | not_found | No such scheme, or state doesn't match it | No |
409 | ambiguous_scheme | The plan number exists in several states and no state was given (risk, insurance) | No — retry with state |
422 | validation_error | Body/params failed validation (see errors[]) | No |
429 | rate_limited | Too many requests from this key (or IP, if keyless) | Yes — see Retry-After |
500 | internal_error | A fault on our side | Yes — retry once with jitter |
503 | unavailable | An upstream data store is unreachable | Yes — exponential backoff |
503 | risk_data_unavailable | Risk data has not been published yet (risk, insurance) | Yes — try again later |
Rate limiting is enforced per X-API-Key (or per IP for the keyless
/v1/coverage and sandbox routes): 20 requests/minute, fixed window. Over
the limit gets a 429 with a Retry-After header (seconds until the
window resets) and the same envelope shape as every other error.
A 404 carries the current dataset size in a dataset extension, so a caller
who misses learns the dataset is finite rather than assuming a bug:
{
"type": "https://api.cohabit.com.au/problems/not_found",
"title": "Scheme not found",
"status": 404,
"detail": "No scheme matching SP99999999.",
"code": "not_found",
"dataset": { "schemes": 4843 }
}
A 409 lists the candidate states in a states extension:
{
"type": "https://api.cohabit.com.au/problems/ambiguous_scheme",
"title": "Ambiguous scheme",
"status": 409,
"detail": "SP12345 exists in several states (NSW, QLD); pass ?state= to choose one.",
"code": "ambiguous_scheme",
"states": ["NSW", "QLD"]
}
A 422 carries an errors array, each entry a JSON Pointer to the offending input:
{
"type": "https://api.cohabit.com.au/problems/validation_error",
"title": "Invalid request",
"status": 422,
"detail": "The request failed validation.",
"code": "validation_error",
"errors": [
{ "detail": "Input should be a valid string", "pointer": "/body/address" }
]
}
Limits
Rate limiting is enforced today (see the 429 row in Errors above:
20 requests/minute per key or IP, fixed window). Per-team monthly/daily
quotas are also live — all keys and projects under a team share the same
credit pool — see your team's Usage page in the dashboard for the
current plan and remaining allowance.
Responses are dated with as_of and change at most weekly, so cache them
rather than polling.
Versioning
Every endpoint lives under /v1. Additive changes (new fields, new
endpoints) ship without a version bump — tolerate unknown fields. Breaking
changes ship under a new version (/v2); the prior version is supported for a
stated window with notice before removal.