Categories
A category is the outcome bucket a verification lands in. Every
verification resolves to exactly one category, and that single choice
drives both the top-level is_compliant boolean and the color shown on
the SDK scanner's result screen. Where criteria
are the individual rules, categories are the verdicts those rules roll
up into.
Anatomy of a category
Each category is an object on the policy's categories array:
{
"id": "bad_parking",
"label": "Bad Parking",
"color": "#ef4444",
"isCompliant": false,
"description": "One or more clear, unmistakable parking violations detected."
}| Field | Type | Description |
| ------------- | ------- | -------------------------------------------------------------------------------------------- |
| id | string | Stable identifier. This is the value returned in the category field of a verification. |
| label | string | Human-readable name shown in the scanner and dashboards. |
| color | string | Hex color that drives the result-screen accent in the SDK scanner overlay. |
| isCompliant | boolean | Whether landing in this bucket counts as a pass. Becomes the response's is_compliant. |
| description | string | What this bucket means. The model reads it to decide when to choose this category. |
The model is forced to pick exactly one category per verification, so your set should be mutually exclusive and cover every realistic outcome.
isCompliant
This is the single most important flag on a category. The category the
model selects has an isCompliant value, and that value is copied
verbatim onto the response as is_compliant:
{
"category": "good_parking",
"is_compliant": true
}So is_compliant is never a separate judgment — it's just the
isCompliant flag of the chosen bucket. This is why you can have
several non-compliant categories (e.g. bad_parking, poor_photo,
no_vehicle) that all set isCompliant: false but tell you very
different things about why the photo failed.
color
color is a hex string the SDK scanners use to tint the result screen —
green for a pass, red for a hard fail, amber/orange for "fix this",
gray for "couldn't tell". It's purely presentational and has no effect
on the API decision, but keeping colors consistent across categories
makes the scanner instantly readable to end users.
The default buckets
Built-in policies ship with four general-purpose categories:
[
{ "id": "compliant", "label": "Compliant", "color": "#22c55e", "isCompliant": true },
{ "id": "improvable", "label": "Improvable", "color": "#eab308", "isCompliant": false },
{ "id": "unsafe", "label": "Unsafe", "color": "#ef4444", "isCompliant": false },
{ "id": "lacks_info", "label": "Lacks Information", "color": "#6b7280", "isCompliant": false }
]When each is returned
| Category | isCompliant | Returned when |
| ------------ | ------------- | ------------------------------------------------------------------------ |
| compliant | true | Every required criterion passes. The photo is good — proceed. |
| improvable | false | Only warning-level criteria failed. Worth a retake, but not a hard block. |
| unsafe | false | A critical, required criterion failed. Block the action and ask for a retake. |
| lacks_info | false | The photo doesn't contain enough signal to judge — too dark, blurry, or cropped. |
The mapping from failed criteria to a category is handled by the policy engine — see how criteria roll up.
Custom categories
Custom policies override the default list with domain-specific buckets. The scooter/e-bike parking policy, for example, uses four buckets that separate why a photo failed so operations teams can route each case differently:
[
{
"id": "good_parking",
"label": "Good Parking",
"color": "#22c55e",
"isCompliant": true,
"description": "All criteria pass. Vehicle correctly parked and fully visible."
},
{
"id": "bad_parking",
"label": "Bad Parking",
"color": "#ef4444",
"isCompliant": false,
"description": "One or more clear, unmistakable parking violations detected."
},
{
"id": "poor_photo",
"label": "Poor Photo",
"color": "#f97316",
"isCompliant": false,
"description": "Photo quality prevents assessment AND no parking violations are detectable."
},
{
"id": "no_vehicle",
"label": "No Vehicle",
"color": "#6b7280",
"isCompliant": false,
"description": "No scooter or e-bike visible in the photo."
}
]All three of bad_parking, poor_photo, and no_vehicle set
isCompliant: false, so is_compliant is false for any of them — but
the distinct category lets you respond appropriately: a bad_parking
result deserves user-facing feedback, while poor_photo and
no_vehicle are better handled by prompting for a retake.
is_compliant is enough to decide pass/fail, but the category tells
you what to do about a failure. Routing poor_photo to a retake and
bad_parking to a corrective message produces a far better end-user
experience than treating every false the same way.
Using category in your app
The category ID is stable, so it's safe to branch on directly:
switch (result.category) {
case "good_parking":
endRide();
break;
case "bad_parking":
showFeedback(result.feedback);
break;
case "poor_photo":
case "no_vehicle":
promptRetake();
break;
}You can read a policy's full category list — including labels and
colors — from
GET /api/v1/policies/:id/config, which
is exactly what the SDK fetches to render the scanner.
What's next
- Concepts: Criteria — the rules that determine which category a verification lands in.
- Concepts: Policies — how categories, criteria, retries, and UI copy combine.
- API reference: Verify — where
categoryandis_compliantappear on the response.