Condition ratings (PASER & PCI)
Rate pavement condition from one image or a location and get a PASER rating or a PCI score with the distresses found.
View as MarkdownSend an image, or a location for TruTec to pull aerial imagery for, and get back a PASER rating (1 to 10) or a Pavement Condition Index (0 to 100). Each result lists the distresses found and a recommended treatment.
Ratings follow PASER and ASTM D6433. They are AI estimates from imagery: a screening-level rating, not a field survey.
A rating usually takes 10 to 20 seconds. Set your HTTP timeout to at least 90 seconds.
Endpoints#
Rate PASER#
POST /v1/condition/paser
Permission: Read measurements
Rate PCI#
POST /v1/condition/pci
Permission: Read measurements
Both endpoints take the same fields, as JSON or as multipart/form-data for a file upload. In a form, send polygon as a JSON string.
Send one image source. If you send an image, location fields are ignored. Otherwise TruTec uses the first of polygon, lat + lng, address.
| Name | Type | Required | Description |
|---|---|---|---|
image |
file or string | One image source | Your own photo: JPEG, PNG or WebP, up to 15 MB and 60 megapixels, at least 64 px. As a string, plain base64 or a data:image/...;base64, URI. Aerial, drone and ground photos all work. |
image_url |
string | One image source | A public https:// URL to an image (up to 2,000 characters). |
polygon |
array or object | One image source | The area to rate, as [[lat, lng], ...] (3 or more points) or a GeoJSON Polygon or Feature (GeoJSON order [lng, lat]). TruTec frames the aerial to it and rates only pavement inside it. Recommended when you have a boundary. |
lat, lng |
number | One image source | Center point for the aerial. |
address |
string | One image source | Street address, geocoded to a center point. |
imagery_source |
string | No | google (default) or nearmap, when TruTec fetches the aerial. Nearmap uses the newest Nearmap survey at the point and needs a plan that includes Nearmap. |
zoom |
integer | No | 16 to 21. By default, fits the polygon (up to 20), or 20 for a point. |
pavement_type |
string | No | auto (default), asphalt or concrete. |
notes |
string | No | Context for the rating, up to 1,000 characters, e.g. "Seal coated 2021, heavy truck traffic at the dock". |
return_image |
boolean | No | Return the exact JPEG that was rated in image.jpeg_base64, with the polygon outlined. Not available with image_url. |
One call rates one frame. A polygon up to about 120 m (400 ft) across is rated at full detail. Larger polygons are fitted at a lower zoom and lose crack-level detail, so split large properties into sections.
curl https://trutec.ai/api/v1/condition/pci \
-H "X-API-Key: sk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"polygon": [[30.40112, -97.72231], [30.40109, -97.72058], [30.39985, -97.72061], [30.39988, -97.72236]],
"imagery_source": "nearmap"
}'
Rate your own photo:
curl https://trutec.ai/api/v1/condition/paser \
-H "X-API-Key: sk_your_api_key_here" \
-F "[email protected]" \
-F "pavement_type=asphalt"
Python:
import os
import requests
resp = requests.post(
"https://trutec.ai/api/v1/condition/paser",
headers={"X-API-Key": os.environ["TRUTEC_API_KEY"]},
json={"lat": 30.4005, "lng": -97.7215, "imagery_source": "nearmap"},
timeout=120,
)
resp.raise_for_status()
result = resp.json()
print(result["paser"]["rating"], result["paser"]["label"], result["image"]["capture_date"])
Response#
Use paser.rating or pci.score. Everything else explains it.
{
"rating_type": "paser",
"rubric_version": "1.0",
"paser": {
"rating": 6,
"label": "Good",
"description": "Slight raveling and traffic wear; cracks 1/4\"–1/2\" open, some closer than 10 ft; ...",
"recommended_treatment": "Crack fill and seal coat; sound structure.",
"limited_by": [
{"distress": "weathering", "severity": "medium", "density_pct": 70.0, "max_rating": 6, "rule": "Weathered surface, traffic wear"}
]
},
"pavement_type": "asphalt",
"confidence": 0.6,
"summary": "The lot is structurally sound with no visible potholes. The surface has oxidized to a light gray. A seal coat would help protect it.",
"distresses": [
{"type": "weathering", "label": "Weathering / oxidation", "severity": "medium", "density_pct": 70.0, "density_unit": "area", "where": "Most of the lot"},
{"type": "raveling", "label": "Raveling", "severity": "low", "density_pct": 15.0, "density_unit": "area", "where": "Drive aisles"}
],
"limitations": ["Tight or hairline cracks may not be visible at this resolution."],
"image": {
"source": "nearmap",
"capture_date": "2026-03-14",
"survey_id": "88f1e2a0-1234-5678-abcd-1234567890ab",
"lat": 30.4004850,
"lng": -97.7214700,
"zoom": 19,
"width": 1280,
"height": 1280,
"meters_per_pixel": 0.129,
"outlined": true,
"view": "aerial",
"quality": "good",
"visible_pavement_pct": 85
},
"request_id": "9b2f4c1e8a7d4e0f9c3b6a5d2e1f0a9b",
"rated_at": "2026-10-12T15:04:11.512Z"
}
A PCI response has the same envelope with a pci block instead of paser:
"pci": {
"score": 85,
"label": "Satisfactory",
"strategy": "Preventive maintenance",
"color": "#4ade80",
"recommended_treatment": "Crack fill, seal coat and spot repairs.",
"method": "astm_d6433",
"deducts": [
{"distress": "weathering", "severity": "medium", "density_pct": 75.0, "deduct_value": 11.3}
]
}
| Field | Description |
|---|---|
paser.rating |
PASER rating, 1 to 10, with label, description and recommended_treatment. |
paser.limited_by |
The distresses holding the rating at this level. Empty when overall surface condition sets the rating. |
pci.score |
PCI, 0 to 100. |
pci.label, strategy, color |
PCI band: Good 86–100, Satisfactory 71–85, Fair 56–70, Poor 41–55, Very poor 26–40, Serious 11–25, Failed 0–10. |
pci.method |
astm_d6433 for asphalt; visual_estimate for concrete. |
pci.deducts |
The deduct value for each distress (asphalt). |
pavement_type |
asphalt or concrete. |
confidence |
0 to 1, reflecting image quality and resolution. |
summary |
Two or three plain sentences for a property manager. |
distresses[] |
type, label, severity (low, medium, high), density_pct, density_unit and a plain-language where. |
distresses[].density_unit |
area = % of rated pavement; linear = linear feet per 100 sq ft; pothole_area = % of area that is pothole; slabs = % of concrete slabs. |
limitations |
What the image could not show. |
image.source |
google, nearmap, upload or url. |
image.capture_date |
The Nearmap survey's capture date. null for Google, which does not publish dates. Not present for your own images. |
image.survey_id |
The Nearmap survey used. null for Google. |
image.view, quality, visible_pavement_pct |
aerial, ground or other; good, fair or poor; how much pavement could be seen. |
request_id |
Quote this when contacting support. |
rated_at |
When the rating ran (UTC). |
Accuracy#
- Satellite imagery cannot resolve hairline cracks, so aerial ratings can read high on lots with fine cracking. Nearmap, drone imagery or ground photos show more.
- Crack width and rut depth can't be measured from above. Their severity is judged from what is visible and noted in
limitations. - Cars, shadows and trees hide pavement.
visible_pavement_pctandconfidenceshow how much could be assessed. - Aerial imagery may be months or years old. Use Nearmap for a dated capture, or upload a current photo.
Usage#
Each successful rating counts as one call on your API key. Errors are not counted.
Errors#
Errors return {"error": "<message>", "code": "<code>"}. 502 and 503 are safe to retry.
| Status | code |
When |
|---|---|---|
| 400 | missing_image |
No image and no location sent. |
| 400 | invalid_request |
Bad polygon, lat/lng, zoom, imagery_source, pavement_type or image_url. |
| 400 | invalid_image |
The image is empty, can't be decoded, or is under 64 px. |
| 403 | forbidden |
The key lacks Read measurements. |
| 403 | upgrade_required |
Nearmap requested on a plan without Nearmap. |
| 413 | image_too_large |
Over 15 MB or 60 megapixels. |
| 422 | no_pavement_visible |
Nothing ratable in the image. |
| 422 | no_imagery_coverage |
Nearmap has no survey at that location. Retry with google. |
| 422 | address_not_found |
The address could not be found. |
| 422 | image_rejected |
The image could not be rated. Try a clearer or closer image. |
| 500 | imagery_unavailable |
Imagery is not configured on the server. |
| 502 | imagery_unavailable, geocode_failed, rating_failed |
A step failed. Retry. |
| 503 | service_unavailable |
Temporarily unavailable. Retry after a minute. |
Authentication errors (401), plan errors (402/403) and usage-limit errors (429) use the standard API error format without code.