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.
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
{
"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. |
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
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
{
"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.
curl -X DELETE \
"https://axiomlocus.io/api/scheduled-exports?id=8c1f9f0a-3a9b-4c7e-9f4d-2b8c4d3e1a90" \
--cookie "sb-access-token=..."
Response
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.
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 |
S3 delivery is not yet supported — schedules deliver to email only.