CohabitDeveloper Portal
Docs menu · APIs

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, or NT. A filter, not a hint: if you supply it and it doesn't match the scheme, the API returns 404 rather 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.
  • status is active (still a live finding) or remediated (works reported done). Findings the documents negate are never returned. There is one entry per driver key and status; evidence lists every supporting document field and page.
  • Confidence and citations. Ordinary keys only see drivers with confidence at or above the public threshold and at least one cited page. Everything else is dropped and counted in withheld_drivers (its excerpts are dropped too), so a 0 there means nothing was held back. Internal Cohabit keys see all drivers.
  • excerpts[].text is 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.
  • building is a handful of structural and financial facts; any field may be null when 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.

StatuscodeCauseRetry?
400bad_requestMalformed body, or state outside NSW/QLD/VIC/WA/NTNo
401unauthorizedMissing or invalid X-API-KeyNo
403forbiddenValid key, but the endpoint is limited to internal Cohabit keys (/insurance/market)No
404not_foundNo such scheme, or state doesn't match itNo
409ambiguous_schemeThe plan number exists in several states and no state was given (risk, insurance)No — retry with state
422validation_errorBody/params failed validation (see errors[])No
429rate_limitedToo many requests from this key (or IP, if keyless)Yes — see Retry-After
500internal_errorA fault on our sideYes — retry once with jitter
503unavailableAn upstream data store is unreachableYes — exponential backoff
503risk_data_unavailableRisk 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.