# Connecticut Property Assessments - Statewide CAMA Roll (`j0401/ct-cama`) Actor

Connecticut's statewide CAMA property roll (public open data, ~1.36M parcels, 153 municipalities): assessed and appraised values, owner and owner mailing address, structure attributes and the recorded sale. Filter by owner, city, town, state-use, valuation year or value.

- **URL**: https://apify.com/j0401/ct-cama.md
- **Developed by:** [Wenhao Yang](https://apify.com/j0401) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.03 / 1,000 connecticut property assessment records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Connecticut Property Assessments - Statewide CAMA Roll & Owner Records

Every Connecticut municipality's assessment roll in one place, straight from
the State's own open data portal. **~1.36M parcel records across all 153
municipalities** - Stamford to Killingly - with the same schema everywhere,
which is what a per-town scraper cannot give you.

### Low cost

**From $0.00005 per record, down to $0.00003 at Gold.** Pay only for the
records you actually receive - the whole roll stays queryable, you are never
billed for the scan behind it.

### What you get

- **The value** - assessed total, assessed building and land, appraised total
  and its split, and the prior year's assessment, so a change is visible
  without a second query
- **The owner** - the owner name on the roll, plus the owner's **mailing
  address**, which is the field that makes out-of-town ownership visible
- **The property** - the site address, map / block / lot / unit, town, ZIP,
  zone and state-use classification with its human label
- **The structure** - year built and effective year built, living area, gross
  area, stories, rooms, bedrooms, baths, heating and cooling, exterior wall,
  roof, attic and basement type
- **The last sale** - sale price, sale date, the grantor and grantee as
  recorded, and the book/page reference

### Modes

- **rows** (default) - property records matching your filters
- **aggregate** - counts by town or by property city

### Filters

Parcel id, map / block / lot / unit, owner name, property city, state, ZIP,
street name, town id, state-use label (e.g. `Single Family`, `Commercial`,
`Vacant Land`), building style, valuation year and minimum assessed value.

### Example inputs

**Out-of-state owners above a threshold** - the mailing address is what makes
this query possible.

```json
{ "owner": "LLC", "minAssessedValue": 500000, "maxResults": 25 }
```

**One municipality** - each town sits on a single roll year (see the note
below), so filtering by town alone is usually enough.

```json
{ "townId": 26, "maxResults": 25 }
```

**Condominiums in one city**

```json
{ "city": "Stamford", "stateUse": "Res. Condo", "maxResults": 25 }
```

**Commercial parcels by value**

```json
{ "stateUse": "Commercial", "minAssessedValue": 1000000, "maxResults": 50 }
```

### Example output

**A condominium owned by an LLC** - `owner` and `city` filtered:

```json
{
 "platform": "ct-cama",
 "source": "ct-ogp-cama",
 "corpus": "cama",
 "recordKind": "property",
 "mode": "rows",
 "groupKey": "",
 "groupCount": "",
 "groupBy": "",
 "pid": "10005",
 "accountNumber": "002-7843",
 "map": "2",
 "block": "7843",
 "lot": "",
 "unit": "29-A3",
 "townId": "135",
 "propertyCity": "Stamford",
 "propertyState": "",
 "propertyZip": "",
 "streetName": "SEATON ROAD",
 "addressNumber": "224",
 "owner": "JBC 1 LLC",
 "mailingAddress": "196 SEATON ROAD #1",
 "mailingCity": "STAMFORD",
 "mailingState": "CT",
 "mailingZip": "06902-3330",
 "valuationYear": "2025",
 "assessedTotal": "127480",
 "appraisedTotal": "182120",
 "stateUse": "105",
 "stateUseDescription": "Res. Condo",
 "styleDescription": "Condominium",
 "yearBuilt": "1951",
 "effectiveYearBuilt": "1994",
 "livingArea": "775",
 "grossArea": "775",
 "stories": "1",
 "totalRooms": "4",
 "bedrooms": "2",
 "baths": "1",
 "heatFuel": "Oil",
 "heatType": "Central Heat",
 "salePrice": "",
 "saleDate": "2020-11-02",
 "saleGrantee": "JBC 1 LLC",
 "saleGrantor": "OLIVEIRA JOAO T",
 "bookPage": "12499/315",
 "sourceUpdatedAt": "2026-09-11"
}
```

**`mode=aggregate`, `groupBy=propertyCity`** - one count row per municipality:

```
Stamford       38,444
Waterbury      34,659
Bridgeport     34,501
Norwalk        30,020
Danbury        28,871
Hartford       28,423
```

### Notes on the data, from the source

- **There is no reliable unique parcel key, and `pid` is the corrupted one.**
  `pid` and `account_number` are rendered in scientific notation by the source
  (a value comes back as the literal string `1.68812E+11`), so every lot under
  one map/block collapses onto the same `pid` - a single `pid` covers up to 376
  rows. `pid` is offered as a search key, never as a primary key, and it is not
  the default sort axis.
- **Sale dates are free text and 7.3% of them are 1900.** The column mixes at
  least six formats (`1/1/1900 12:00:00 AM`, `1900-01-01`, `1/1/1900 0:00`,
  plain `M/D/YYYY`). 99,278 rows carry a 1900 sentinel and 19,218 are blank.
  Values are normalised to ISO where parseable and blanked when they are
  sentinels.
- **A recorded sale exists on 63.8% of rows** (866,979). The rest carry a zero
  price, which is the source's way of saying no price was recorded - output as
  blank rather than `0`.
- **There is no sale-price filter, deliberately.** `sale_price` is a *text*
  column on this source, and every way of comparing it is broken: a numeric
  literal is rejected by the source, and a quoted one compares
  **lexicographically** - `>= '500000'` matches 178,300 rows while the 824,799
  rows whose price has seven or more digits (all of them numerically over a
  million) do not match at all. Rather than ship a filter that silently returns
  the wrong set, the sale price is output-only; filter on it client-side.
- **Three source columns are near-empty and are not published**:
  `property_county` (2.95%, and the values it does carry are dirty - case
  variants plus `CT` and bare ZIPs), the second mailing-address line (0.9%) and
  water frontage (3.7%). The roll's own "137 columns" is not a useful headline;
  this actor publishes the 73 that carry information (screened at build
  time; the runtime guard samples the core fields), and that is what is here.
- **Each municipality sits near its own roll year - `valuationYear` is not a
  statewide snapshot.** Connecticut towns revalue on their own cycles: 85 towns
  are on 2026, 42 on 2025, the rest on revaluation years back to 2021, and nine
  towns carry more than one year in the table. Filtering
  `valuationYear=2026` therefore returns 85 municipalities, not all 153 - pair
  a year filter with a town only if you already know that town's cycle, or omit
  the year and let the town speak for itself.
- **Grouping by town returns a large blank bucket.** 72,709 rows (5.3%) carry
  no `town_id`, so `groupBy=townId` reports `(blank)` above every real
  municipality. Grouping by `propertyCity` does not have this problem (the city
  is populated on 100% of rows) and is the better default for a place-based
  view.
- **`valuation_year` carries data-entry garbage** alongside the real roll years
  (2021-2027): values like `40831900` and `782180` are present. Year filters
  reject anything outside 2000-2100 rather than building a predicate that
  matches nothing.
- **Rows come back in parcel order, not newest-first.** Neither recency axis is
  usable - sale dates are multi-format text, and the garbage years above sort
  above every real year on a descending roll-year sort.

### Notes

- Public open data from data.ct.gov. No login, no scraping.
- The roll is assembled from municipal CAMA systems, which refresh on their own
  schedules; every record carries `sourceUpdatedAt` so you can see the feed's
  currency without running anything.
- Charges are metered per record delivered, so a targeted query costs a
  fraction of a cent.

# Actor input Schema

## `mode` (type: `string`):

rows = records matching your filters (default). aggregate = one count row per group.

## `groupBy` (type: `string`):

Aggregate dimension, only used when mode=aggregate.

## `pid` (type: `string`):

Parcel id as published. NOTE: this source's pid is corrupted (values render in scientific notation and repeat across lots) - it is a search key, not a unique parcel key.

## `map` (type: `string`):

Map number, matched exactly.

## `block` (type: `string`):

Block, matched exactly.

## `lot` (type: `string`):

Lot, matched exactly.

## `unit` (type: `string`):

Unit (populated on condominiums only).

## `owner` (type: `string`):

Owner-name substring, e.g. 'LLC'. Blank = any.

## `city` (type: `string`):

Property city substring, e.g. 'Hartford'. Blank = any.

## `state` (type: `string`):

Two-letter state. Blank = any.

## `zip` (type: `string`):

Property ZIP prefix, e.g. '061'. Blank = any.

## `streetName` (type: `string`):

Street-name substring. Blank = any.

## `townId` (type: `string`):

Municipality id, e.g. 15. Blank = any.

## `stateUse` (type: `string`):

State-use label substring, e.g. 'Single Family', 'Commercial', 'Vacant Land'. Blank = any.

## `styleDescription` (type: `string`):

Building style substring, e.g. 'Colonial'. Blank = any.

## `valuationYear` (type: `string`):

4-digit roll year, e.g. '2026'. Blank = any. Values outside 2000-2100 are rejected (the source carries data-entry garbage years).

## `minAssessedValue` (type: `integer`):

Only properties with an assessed total at least this amount. Blank = any.

## `maxResults` (type: `integer`):

Cap records returned for this run. Default 50 keeps the daily auto-test fast. 0 = no cap (the full matching slice, up to 10,000).

## Actor input object example

```json
{
  "mode": "rows",
  "groupBy": "propertyCity",
  "pid": "",
  "map": "",
  "block": "",
  "lot": "",
  "unit": "",
  "owner": "",
  "city": "",
  "state": "",
  "zip": "",
  "streetName": "",
  "townId": "",
  "stateUse": "",
  "styleDescription": "",
  "valuationYear": "",
  "maxResults": 50
}
```

# Actor output Schema

## `recordsUrl` (type: `string`):

Connecticut Property Assessments - Statewide CAMA Roll & Owner Data - as JSON

## `datasetUrl` (type: `string`):

No description

## `runUrl` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("j0401/ct-cama").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("j0401/ct-cama").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call j0401/ct-cama --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,j0401/ct-cama"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ze3bMSnCrrRxGD7hc/builds/NI3Fb2OLaiIShlpqV/openapi.json
