> ## Documentation Index
> Fetch the complete documentation index at: https://docs.axiomancer.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Scheduled exports

> Schedule recurring CSV or JSON exports of Locus cell scores, delivered by email — automate weekly territory rollups for your sales and analytics teams.

Schedule recurring exports of cell scores from Locus and have them emailed to you on a weekly or monthly cadence. Each delivery runs the saved filter against the latest `cell_scores` and attaches the result as a CSV or JSON file.

For one-off downloads, use the on-demand exports endpoint described in [ML exports](/locus/api/ml-exports).

## When to use it

Use scheduled exports when you want a hands-off feed of Locus scores for a specific market, score band, or analyst inbox — for example:

* A weekly CSV of all cells in a metro for territory planning.
* A monthly JSON snapshot of high-composite cells (e.g. composite ≥ 70) for portfolio review.
* Recurring deliveries to a shared analyst alias so the same dataset lands in the same place every week.

Each schedule is owned by the user that creates it and can only be read, updated, or deleted by that user.

## Endpoints

All endpoints require an authenticated session. Schedules are scoped to the calling user.

### `GET` `/api/scheduled-exports`

List scheduled exports owned by the current user.

**Response**

```json theme={null}
{
  "schedules": [
    {
      "id": "8c1f9f0a-3a9b-4c7e-9f4d-2b8c4d3e1a90",
      "name": "Weekly SF scores",
      "filters": { "metro": "sf", "compositeMin": 70 },
      "format": "csv",
      "frequency": "weekly",
      "target_email": "analyst@yourcompany.com",
      "last_sent_at": "2026-04-22T08:00:12Z",
      "last_error": null,
      "created_at": "2026-04-15T14:22:01Z"
    }
  ]
}
```

| Field          | Type           | Description                                                                         |
| -------------- | -------------- | ----------------------------------------------------------------------------------- |
| `id`           | string (UUID)  | Schedule identifier. Pass to `DELETE` to remove.                                    |
| `name`         | string         | Human-readable label, used in the email subject and attachment filename.            |
| `filters`      | object         | Filter blob applied to `cell_scores` on each run. See [Filters](#filters).          |
| `format`       | string         | `csv` or `json`.                                                                    |
| `frequency`    | string         | `weekly` or `monthly`.                                                              |
| `target_email` | string         | Address that receives each delivery.                                                |
| `last_sent_at` | string \| null | Timestamp of the last successful run. `null` if it has never run.                   |
| `last_error`   | string \| null | Error message from the most recent failed run, or `null` if the last run succeeded. |
| `created_at`   | string         | When the schedule was created.                                                      |

### `POST` `/api/scheduled-exports`

Create a new scheduled export.

**Body parameters**

| Name           | Type   | Required | Description                                                                               |
| -------------- | ------ | -------- | ----------------------------------------------------------------------------------------- |
| `name`         | string | ✓        | Schedule name (≤120 characters).                                                          |
| `target_email` | string | ✓        | Address to deliver the export to. Must contain `@`.                                       |
| `format`       | string |          | `csv` (default) or `json`.                                                                |
| `frequency`    | string |          | `weekly` (default) or `monthly`.                                                          |
| `filters`      | object |          | Filter blob applied to `cell_scores`. Defaults to `{}` (all cells). Maximum 8 KB of JSON. |

**Filters**

The filter blob is stored as JSON and re-evaluated on every run, so updates to `cell_scores` show up automatically. The cron worker currently honors:

| Key            | Type   | Description                                     |
| -------------- | ------ | ----------------------------------------------- |
| `metro`        | string | Metro slug (e.g. `sf`, `nyc`).                  |
| `compositeMin` | number | Lower bound on the composite score (inclusive). |
| `compositeMax` | number | Upper bound on the composite score (inclusive). |

Each delivery is capped at 5,000 rows, ordered by descending composite. If the filter matches no rows, the run is recorded as successful and no email is sent.

**Example — create a weekly schedule**

```bash theme={null}
curl -X POST https://axiomlocus.io/api/scheduled-exports \
  -H "Content-Type: application/json" \
  --cookie "sb-access-token=..." \
  -d '{
    "name": "Weekly SF scores",
    "target_email": "analyst@yourcompany.com",
    "format": "csv",
    "frequency": "weekly",
    "filters": { "metro": "sf", "compositeMin": 70 }
  }'
```

**Response**

```json theme={null}
{
  "schedule": {
    "id": "8c1f9f0a-3a9b-4c7e-9f4d-2b8c4d3e1a90",
    "name": "Weekly SF scores",
    "filters": { "metro": "sf", "compositeMin": 70 },
    "format": "csv",
    "frequency": "weekly",
    "target_email": "analyst@yourcompany.com",
    "created_at": "2026-04-29T19:00:00Z"
  }
}
```

### `DELETE` `/api/scheduled-exports?id=<uuid>`

Delete a scheduled export. The `id` must be a UUID owned by the current user.

```bash theme={null}
curl -X DELETE \
  "https://axiomlocus.io/api/scheduled-exports?id=8c1f9f0a-3a9b-4c7e-9f4d-2b8c4d3e1a90" \
  --cookie "sb-access-token=..."
```

**Response**

```json theme={null}
{ "success": true }
```

## Delivery cadence

A cron worker evaluates schedules every Monday at 08:00 UTC and runs any schedule whose `last_sent_at` is older than its cadence (or has never run):

* `weekly` — runs once `last_sent_at` is at least 7 days old.
* `monthly` — runs once `last_sent_at` is at least 30 days old.

Because the worker fires weekly, weekly is the floor cadence — monthly schedules will fire on the first Monday after their 30-day window elapses.

## Email format

Each successful run sends an email from `Axiom Locus <reports@axiomlocus.io>` with:

* **Subject:** `Your scheduled Axiom Locus export: <name> (<row_count> rows)`
* **Attachment:** `<slugified-name>-<YYYY-MM-DD>.<csv|json>`

CSV attachments include a header row and the columns `h3_index`, `metro_slug`, `neighborhood`, `lat`, `lng`, `composite`, `confidence`, the eight signal-group scores (`business_vitality`, `population_momentum`, `demographics`, `economic_strength`, `development_pipeline`, `accessibility`, `safety_environment`, `amenity_demand`), and `computed_at`. JSON attachments contain an array of objects with the same keys.

## Failure handling

If a run fails (for example, an upstream Resend outage), the error message is written to `last_error` on the schedule and the schedule remains due — the worker will retry on the next cron tick. A successful run clears `last_error` and bumps `last_sent_at`. List the schedule with `GET` to see whether the last run succeeded.

## Limits

| Limit                | Value                |
| -------------------- | -------------------- |
| Filter blob size     | 8 KB of JSON         |
| Schedule name length | 120 characters       |
| Rows per delivery    | 5,000                |
| Cron cadence         | Mondays at 08:00 UTC |

<Note>S3 delivery is not yet supported — schedules deliver to email only.</Note>
