API documentation

Reference for Roof Pitch API: quickstart, authentication, rate limits per API key, every endpoint and response field, error codes, conventions and coverage, plus an OpenAPI 3.1 specification.

1Quickstart

One call. Send the address as a person would type it; the response carries the footprint, the pitch and the surface area. Replace the key with the one you were sent, or use a test key to get the fixed sample response.

curl
curl "https://api.roofpitchapi.co.nz/v1/roofs?address=12%20Example%20Street%2C%20Northwood%2C%20Christchurch" \
  -H "Authorization: Bearer rp_test_example"
JavaScript (Node 18+)
const address = "12 Example Street, Northwood, Christchurch";
const url = new URL("https://api.roofpitchapi.co.nz/v1/roofs");
url.searchParams.set("address", address);

const response = await fetch(url, {
  headers: { Authorization: `Bearer ${process.env.ROOF_PITCH_API_KEY}` },
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.code}: ${error.message}`);
}

const { building } = await response.json();
console.log(building.surfaceAreaM2); // 204.1
Python 3
import os
import requests

response = requests.get(
    "https://api.roofpitchapi.co.nz/v1/roofs",
    params={"address": "12 Example Street, Northwood, Christchurch"},
    headers={"Authorization": f"Bearer {os.environ['ROOF_PITCH_API_KEY']}"},
    timeout=10,
)
response.raise_for_status()
building = response.json()["building"]
print(building["pitchDegrees"], building["surfaceAreaM2"])  # 24 204.1

2Authentication

Every request carries an API key as a bearer token. Keys are issued by email once a quote is agreed; there is no self-serve signup during the private preview.

Authenticated request
curl "https://api.roofpitchapi.co.nz/v1/roofs?address=12%20Example%20Street%2C%20Northwood%2C%20Christchurch" \
  -H "Authorization: Bearer rp_live_your_key_here"
  • Send the key in the Authorization header: Authorization: Bearer rp_live_… . Keys are never accepted in the query string.
  • Two kinds of key: rp_live_ keys count against your quota and return real measurements; rp_test_ keys return fixed sample responses for the example address and never count.
  • Keys are server-side secrets. Call the API from your backend, never from a browser or mobile app, and proxy the result to your own front end.
  • Each key belongs to one customer and one environment. Ask for a second key for staging so a test harness can never exhaust production quota.
  • Rotate a key by email: a new key is issued, both work for seven days, then the old one is revoked.

3Rate limits and quotas

Limits are set per API key and written into your agreement. The tiers below are the indicative starting points; a key can be moved between them without a code change on your side.

TierRequests per minuteConcurrent requestsLookups per monthIntended for
PreviewPer minute60Concurrent5Per month2,000Evaluating the data and building an integration.
StandardPer minute300Concurrent20Per month20,000A production website or quoting tool.
VolumePer minute1,000Concurrent50Per monthAgreed per contractBulk enrichment of a property dataset.
  • Per-minute limits apply to every request. Monthly quota is only used by successful (200) responses from /roofs and /buildings/{id}; address search, coverage and every error are free.
  • The per-minute window is a sliding 60 seconds, not a calendar minute, so a burst at 11:59:58 does not reset at 12:00:00.
  • Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (a Unix timestamp in seconds) for the per-minute window, plus X-Quota-Remaining for the month.
  • Past a limit the API answers 429 RATE_LIMITED with a Retry-After header in seconds. Wait that long; retrying sooner is counted and extends the wait.
  • Responses for the same address are safe to cache on your side for 30 days. LINZ building outlines and LiDAR change on survey cycles measured in years, so a cached roof is as good as a fresh one.
  • Need more? Email with the number you need and the pattern (steady traffic or a one-off bulk run). Bulk runs can be scheduled off-peak against a temporary higher limit.
Rate-limit headers on every response
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 1762041600
X-Quota-Remaining: 18211
Cache-Control: private, max-age=2592000
When a limit is reached
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1762041612

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Per-minute limit reached for this key. Retry after 12 seconds.",
    "requestId": "req_01J9X4M2R7T3"
  }
}

4Endpoints

Base URL https://api.roofpitchapi.co.nz/v1. Every endpoint is a GET that returns JSON. Query parameters are URL-encoded; the address parameter accepts anything a person would type, including unit numbers and common abbreviations.

GET/roofs

Address in, measured roof out

The main endpoint. Resolves the address, finds the buildings on the section, measures the dominant pitch of the primary building and returns footprint, pitch and surface area in one call.

Request
GET /v1/roofs?address=12%20Example%20Street%2C%20Northwood%2C%20Christchurch
Authorization: Bearer rp_live_...
Response
{
  "address": {
    "full": "12 Example Street, Northwood, Christchurch 8051",
    "territorialAuthority": "Christchurch City",
    "latitude": -43.4731,
    "longitude": 172.6163
  },
  "building": {
    "id": 2048173,
    "footprintM2": 186.4,
    "pitchDegrees": 24,
    "pitchSource": "lidar",
    "pitchConfidence": "high",
    "surfaceAreaM2": 204.1
  },
  "otherBuildings": [
    {
      "id": 2048174,
      "footprintM2": 38.2,
      "pitchDegrees": 15,
      "pitchSource": "lidar",
      "pitchConfidence": "medium",
      "surfaceAreaM2": 39.6
    }
  ],
  "attribution": "Contains data sourced from LINZ, CC BY 4.0"
}

GET/addresses

Address search and autocomplete

Type-ahead search over the LINZ national address dataset. Each candidate says whether it sits inside a covered territorial authority, so you can tell the user before they commit.

Request
GET /v1/addresses?q=12%20Example%20St
Authorization: Bearer rp_live_...
Response
{
  "candidates": [
    {
      "full": "12 Example Street, Northwood, Christchurch 8051",
      "territorialAuthority": "Christchurch City",
      "covered": true
    },
    {
      "full": "12 Example Street, Mosgiel, Dunedin 9024",
      "territorialAuthority": "Dunedin City",
      "covered": false
    }
  ]
}

GET/buildings/{id}

Measure one specific building

When your user has confirmed which building is theirs, fetch the measurement for that LINZ building outline directly, without resolving the address again.

Request
GET /v1/buildings/2048173
Authorization: Bearer rp_live_...
Response
{
  "id": 2048173,
  "footprintM2": 186.4,
  "pitchDegrees": 24,
  "pitchSource": "lidar",
  "pitchConfidence": "high",
  "surfaceAreaM2": 204.1,
  "territorialAuthority": "Christchurch City"
}

GET/coverage

Where measured pitch is available

The territorial authorities with LiDAR-measured pitch today. Check this rather than hardcoding a list: coverage grows as new districts are processed.

Request
GET /v1/coverage
Response
{
  "territorialAuthorities": [
    "Christchurch City",
    "Selwyn District",
    "Waimakariri District",
    "Timaru District",
    "Ashburton District",
    "Hurunui District",
    "Waimate District",
    "Mackenzie District",
    "Kaikoura District"
  ]
}

5Response fields

The shape of a /roofs response. /buildings/{id} returns a single building object with the same fields plus territorialAuthority. Unknown fields may appear in future; read by name and ignore the rest.

address.fullstring
The matched LINZ address, formatted the way NZ Post writes it.
address.territorialAuthoritystring
LINZ's territorial authority name, exactly as /coverage lists it. Use it to decide coverage without a second call.
address.latitudenumber
WGS 84 latitude of the address point, in decimal degrees.
address.longitudenumber
WGS 84 longitude of the address point, in decimal degrees.
building.idinteger
The LINZ building outline id. Stable across releases; pass it to /buildings/{id} once a user has confirmed their house.
building.footprintM2number
Plan area of the building outline polygon in square metres, to one decimal place. This is the roof's shadow on the ground, not its surface.
building.pitchDegreesnumber
Dominant roof slope from horizontal, in whole degrees, 0 to 59. A flat roof returns 0.
building.pitchSource"lidar" | "default"
lidar: measured from the LINZ 1 m digital surface model over this footprint. default: no usable LiDAR cells, so a regional default pitch was applied and pitchConfidence is absent. Treat default as an estimate and say so to your user.
building.pitchConfidence"high" | "medium" | "low"
How planar the roof is: the share of LiDAR cells within 5 degrees of the dominant slope. high is a simple gable or hip; low is a complex or part-flat roof where the dominant plane covers less than half the area. Present only when pitchSource is lidar.
building.surfaceAreaM2number
footprintM2 / cos(pitchDegrees), to one decimal place. The true pitched surface, with no eaves or waste allowance.
otherBuildings[]Building[]
Every other building outline within 40 m of the address point, largest first, each with the same fields as building. Garages, sleepouts and sometimes a neighbour: let your user tick the ones that are theirs.
attributionstring
The LINZ CC BY 4.0 credit. Display it wherever you show the measurement.

6Errors

Every error has the same envelope: a stable machine-readable code, a sentence for a log, an optional list of issues for validation failures, and a requestId to quote in support. The HTTP status tells you whether to retry; the code tells you why.

Error envelope
{
  "error": {
    "code": "INVALID_REQUEST",
    "message": "The request is missing a required parameter.",
    "issues": [
      { "path": "address", "message": "Required" }
    ],
    "requestId": "req_01J9X4K7Q2M8"
  }
}
StatusCodeMeaningRetry
404ADDRESS_NOT_FOUNDNo LINZ address matched. Try the /addresses search first and send the candidate back.Retry: no
422OUT_OF_AREAThe address is real but sits outside a covered territorial authority. No pitch is guessed.Retry: no
422NO_BUILDING_FOUNDNo building outline near the address point. About 7% of Canterbury addresses: bare sections, new subdivisions, houses set far back from the road.Retry: no
401UNAUTHORIZEDMissing, malformed or revoked API key.Retry: no
400INVALID_REQUESTA required parameter is missing or malformed. The issues array names each one.Retry: no
404BUILDING_NOT_FOUNDNo building outline has that id. Ids come from /roofs and are stable across LINZ releases.Retry: no
429RATE_LIMITEDThe key's per-minute limit or monthly quota is used up. Retry-After says how long to wait.Retry: after Retry-After
503MEASUREMENT_UNAVAILABLEA LINZ upstream service did not answer in time. Nothing was counted against your quota.Retry: yes, with backoff
500INTERNAL_ERROROur fault. The response carries a requestId to quote when you report it.Retry: yes, with backoff

7Conventions

Units and precision
Areas are square metres to one decimal place. Pitch is whole degrees from horizontal. Coordinates are WGS 84 decimal degrees to four places (about 10 m), which is the address point, not a rooftop centroid. Nothing is rounded for you beyond that; apply your own allowances and rounding at the point of quoting.
Versioning
The version is in the path (/v1). Within a version, changes are additive only: new fields and new enum values may appear, existing ones never change meaning or disappear. Read fields by name and ignore ones you do not know. A breaking change ships as /v2 with at least twelve months of overlap, announced by email to every key holder.
Idempotency and caching
Every endpoint is a GET with no side effects, so retries are always safe. Successful responses carry Cache-Control: private, max-age=2592000 (30 days). Cache by the normalised address.full, not by the raw query string, so spelling variants share one entry.
Latency and timeouts
A /roofs call typically answers in under one second and in under two at the 95th percentile; address search is faster. Set a client timeout of ten seconds. The API itself gives up on LINZ after eight seconds and returns 503 MEASUREMENT_UNAVAILABLE rather than a partial answer.
Data sources and freshness
Addresses come from the LINZ NZ Street Address dataset, refreshed weekly. Building outlines and the 1 m LiDAR digital surface model come from LINZ survey releases, which cover a district every few years. Each response reflects the newest release we have processed; the /coverage endpoint is the authority on where measured pitch exists.
Attribution
LINZ data is licensed CC BY 4.0, which permits commercial use provided LINZ is credited. Show the attribution string from the response wherever a measurement is displayed, including quotes and PDFs. That is the only licence condition passed on to you.
Privacy
A request contains an address, not a person. We log the address, the key id, the timestamp and the response code for 90 days for quota and support, and nothing else. Do not send personal data in the query; there is nowhere for it to go.
Support and status
Email is the support channel during the preview; replies within one New Zealand business day. Planned maintenance is announced by email at least 48 hours ahead and never scheduled inside New Zealand business hours.

8Coverage

Measured pitch exists where LINZ LiDAR has been processed. The /coverage endpoint is the live list; this page is updated when a region goes live. An address outside coverage returns 422 OUT_OF_AREA.

Available now
Canterbury: Christchurch City, Selwyn District, Waimakariri District, Timaru District, Ashburton District, Hurunui District, Waimate District, Mackenzie District, Kaikoura District.
Coming soon
Auckland, Wellington, Waikato, Bay of Plenty, Otago, Manawatū-Whanganui, Northland, Hawke's Bay, Taranaki, Southland, Nelson, Tasman, Marlborough, Gisborne, West Coast. Regions are processed in order of demand.

9Changelog

November 2026Private preview opens for Canterbury
/roofs, /addresses, /buildings/{id} and /coverage, with LiDAR-measured pitch across the nine Canterbury territorial authorities. Test keys return the fixed sample response.