# California evacuation status (`sangria_marzipan/california-evacuation-status`) Actor

Active California evacuation orders and warnings, Caltrans road closures and CAL FIRE incidents in one normalized dataset, with an optional webhook when a zone goes to warning or order.

- **URL**: https://apify.com/sangria_marzipan/california-evacuation-status.md
- **Developed by:** [Jerry Yan](https://apify.com/sangria_marzipan) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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

## California evacuation status

Active California evacuation zones, road closures and wildfire incidents in one dataset with one record shape. It reads public, no-login sources and can notify a webhook the moment a zone goes to warning or order.

### What you get

Each dataset item has the same fields.

| Field | Meaning |
| --- | --- |
| `source`, `sourceUrl` | Where the record came from |
| `kind` | `evacuation_zone`, `road_closure` or `incident` |
| `id`, `name`, `county` | Source id, readable name, county |
| `status` | `order`, `warning`, `advisory`, `lifted`, `closed`, `open` or `unknown` |
| `updatedAt` | When the source last edited the record (ISO 8601) |
| `stale` | Zones only. `true` when `updatedAt` is older than `staleAfterHours` |
| `geometry` | GeoJSON in WGS84: polygon for zones, line or point for closures, point for incidents |
| `properties` | Source details such as raw status, acres burned or lanes closed |

Example zone:

```json
{
  "source": "cal-oes",
  "kind": "evacuation_zone",
  "id": "US-CA-XTU-PVL-E044",
  "name": "US-CA-XTU-PVL-E044",
  "county": "Tulare",
  "status": "order",
  "updatedAt": "2023-09-20T22:33:08.000Z",
  "stale": true,
  "geometry": { "type": "Polygon", "coordinates": [[[-119.3, 36.2], "..."]] }
}
```

### Sources

| Input name | Source | Records |
| --- | --- | --- |
| `calOesZones` | Cal OES statewide active evacuation zones (ArcGIS feature service, aggregates county layers) | Orders and warnings |
| `calFireZones` | CAL FIRE statewide evacuation status (ArcGIS feature service, Genasys Protect schema) | Orders, warnings, advisories |
| `calFireIncidents` | CAL FIRE active incidents (`incidents.fire.ca.gov`) | Point per incident |
| `caltransClosures` | Caltrans lane closure feeds (`cwwp2.dot.ca.gov`, districts 1 to 12) | Closures in effect now |

Zones that appear in both statewide layers are returned once. If one source is down, the run logs the error, skips it and returns the rest. The run fails only when every requested source fails.

County layers are only as fresh as the county that edits them. Counties sometimes leave old orders in the statewide layer, so check `stale` and `updatedAt` before you act on a zone. This data is for reference. Follow your county's official alerts for decisions.

### Input

- `sources`: which sources to read (default all)
- `counties`: for example `["Los Angeles"]` (default whole state)
- `boundingBox`: `{ west, south, east, north }` in degrees
- `closureDetail`: `full` for fully closed roads and ramps only, `all` to add lane closures (reported as `open`)
- `staleAfterHours`: default 72
- `webhookUrl`, `closureWebhookUrl`, `webhookSecret`: see below
- `drill`, `drillPolygon`, `drillZoneName`: add one labelled practice zone

### Webhook

When `webhookUrl` is set, the Actor POSTs zones whose status is `warning` or `order` and that are new or changed since the last run. Stale zones are never sent. The Actor remembers what it sent in the named key-value store `california-evacuation-status-state`, so schedule it (for example every 5 minutes) and each zone is sent once per status change.

```http
POST <webhookUrl>
Authorization: Bearer <webhookSecret>
Content-Type: application/json

{ "zones": [ { "source", "sourceUrl", "kind": "evacuation_zone", "id", "name", "county",
               "status": "warning" | "order", "updatedAt", "geometry", "drill"? } ] }
```

A zone is marked as sent only after your endpoint answers 2xx. A failed post is retried on the next run. Posts are sent in batches of 25 zones.

### Road closure webhook

When `closureWebhookUrl` is set, the Actor POSTs full road closures to it: each one once when it starts, and once more when the source stops reporting it. Lane closures are never sent. It uses the same `webhookSecret` and batches of 25, and it remembers what it sent in the same key-value store, under its own key.

```http
POST <closureWebhookUrl>
Authorization: Bearer <webhookSecret>
Content-Type: application/json

{ "closures": [ { "source", "sourceUrl", "kind": "road_closure", "id", "name", "county",
                  "status": "closed", "updatedAt", "geometry" },
                { "source", "kind": "road_closure", "id", "status": "lifted" } ] }
```

A closure counts as lifted only when the feed it came from was read on this run. If a Caltrans district's feed fails, its closures stay closed until a later run reads that district again.

### Drill mode

Set `drill: true` and give `drillPolygon` (a ring of `[longitude, latitude]` pairs) to add one synthetic zone in `warning` status. It has `source: "drill"` and `drill: true` and is posted to the webhook like any zone, so receivers can tell it from real data. Real records are never changed. Leave it off outside exercises.

### Use with Rider Check

Rider Check is a voice agent that phones every paratransit rider inside a warning area. Its Convex backend exposes `POST /apify/zone-status` and checks the bearer token against `APIFY_WEBHOOK_SECRET`. When a posted zone covers loaded riders, it records the zone and starts the calls. Posting the same zone and status again does nothing. Its `POST /apify/road-closures` takes the closure webhook. It writes a closure only where it runs along a street on the agency's road graph, so dispatch routes around it, and it reopens the road when the closure is lifted.

# Actor input Schema

## `sources` (type: `array`):

Which public sources to read. calOesZones: Cal OES statewide active evacuation zones. calFireZones: CAL FIRE statewide evacuation status zones. calFireIncidents: CAL FIRE active incidents. caltransClosures: Caltrans lane and road closures.

## `counties` (type: `array`):

Only keep records in these California counties (for example Los Angeles). Leave empty for the whole state.

## `boundingBox` (type: `object`):

Only keep records whose geometry touches this box. Degrees in WGS84: west, south, east, north.

## `closureDetail` (type: `string`):

full: only roads, ramps and connectors that are fully closed right now. all: also single-lane closures, reported with status open.

## `staleAfterHours` (type: `integer`):

A zone whose source record has not been edited for this long is marked stale: true and is never sent to the webhook. Counties sometimes leave old orders in the statewide layer.

## `webhookUrl` (type: `string`):

If set, the Actor POSTs { zones: \[...] } here for zones in warning or order that are new or changed since the last run.

## `closureWebhookUrl` (type: `string`):

If set, the Actor POSTs { closures: \[...] } here: each full road closure once when it starts (status closed, with its geometry), and once more with status lifted when the source stops reporting it. A Caltrans district whose feed failed this run never reports a closure as lifted.

## `webhookSecret` (type: `string`):

Sent as Authorization: Bearer <secret>.

## `drill` (type: `boolean`):

Adds a synthetic evacuation zone in warning status from the drill polygon, a synthetic full road closure from the drill closure line, or both. Every drill record has drill: true. Off by default; real records are never altered.

## `drillPolygon` (type: `array`):

Ring of \[longitude, latitude] pairs for the drill zone, at least three.

## `drillZoneName` (type: `string`):

Name shown for the drill zone.

## `drillClosure` (type: `array`):

Line of \[longitude, latitude] pairs along a street, at least two, for a synthetic full road closure sent to the closure webhook. A later drill run without it reports the closure lifted.

## `drillClosureName` (type: `string`):

Name shown for the drill closure.

## `requestTimeoutSecs` (type: `integer`):

Per request timeout for each source.

## Actor input object example

```json
{
  "sources": [
    "calOesZones",
    "calFireZones",
    "calFireIncidents",
    "caltransClosures"
  ],
  "counties": [
    "Los Angeles"
  ],
  "closureDetail": "full",
  "staleAfterHours": 72,
  "drill": false,
  "drillZoneName": "Drill zone",
  "drillClosureName": "Drill closure",
  "requestTimeoutSecs": 45
}
```

# Actor output Schema

## `records` (type: `string`):

One record per evacuation zone, Caltrans closure or CAL FIRE incident, normalized to the dataset schema.

## `summary` (type: `string`):

Record counts per source and status, stale zones, failed sources and what the webhook sent.

# 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 = {
    "sources": [
        "calOesZones",
        "calFireZones",
        "calFireIncidents",
        "caltransClosures"
    ],
    "counties": [
        "Los Angeles"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("sangria_marzipan/california-evacuation-status").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 = {
    "sources": [
        "calOesZones",
        "calFireZones",
        "calFireIncidents",
        "caltransClosures",
    ],
    "counties": ["Los Angeles"],
}

# Run the Actor and wait for it to finish
run = client.actor("sangria_marzipan/california-evacuation-status").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 '{
  "sources": [
    "calOesZones",
    "calFireZones",
    "calFireIncidents",
    "caltransClosures"
  ],
  "counties": [
    "Los Angeles"
  ]
}' |
apify call sangria_marzipan/california-evacuation-status --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sangria_marzipan/california-evacuation-status"
        }
    }
}
```

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/CNrAletV3ZxFBIyzJ/builds/8XmCL0SUirvF34KwR/openapi.json
