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

# Portfolios

> Manage location portfolios, add and score locations, run bulk CSV imports, and trigger scoring refreshes — all the building blocks for territory analysis.

Portfolios group H3 cell locations into named collections for ongoing monitoring and scoring. Each portfolio can hold up to 2,000 locations, apply a custom [scoring formula](/locus/api/formulas), and track weighted composite scores with 30-day movement trends.

Portfolios support three visibility levels — `private` (owner only), `team`, and `org` — so you can share scoring results across your workspace.

## `GET` `/api/v1/locus/portfolios`

<Note>Requires session authentication.</Note>

List all portfolios you have access to, including aggregate scoring metrics.

**Response**

```json theme={null}
{
  "portfolios": [
    {
      "id": "p_abc123",
      "name": "West Coast QSR Sites",
      "description": "Quick-service restaurant expansion candidates",
      "asset_class": "qsr",
      "active_formula_id": "f_def456",
      "visibility": "team",
      "member_count": 42,
      "weighted_composite": 71.8,
      "formula": {
        "id": "f_def456",
        "name": "QSR v2",
        "asset_class": "qsr",
        "version": 2
      },
      "latest_refresh_run": {
        "status": "completed",
        "requested_at": "2026-04-19T10:00:00Z",
        "completed_at": "2026-04-19T10:02:34Z"
      },
      "metrics": {
        "weighted_composite_30d_start": 69.2,
        "weighted_composite_30d_end": 71.8,
        "weighted_delta_30d": 2.6,
        "coverage_pct": 95.2,
        "weighted_coverage_pct": 96.1,
        "severe_exposure_pct": 4.8,
        "elevated_exposure_pct": 14.3,
        "stable_exposure_pct": 80.9
      },
      "created_at": "2026-03-01T08:00:00Z",
      "updated_at": "2026-04-19T10:02:34Z"
    }
  ]
}
```

| Field                           | Type           | Description                                           |
| ------------------------------- | -------------- | ----------------------------------------------------- |
| `member_count`                  | int            | Number of locations in the portfolio.                 |
| `weighted_composite`            | float \| null  | Weight-adjusted composite score across all locations. |
| `formula`                       | object \| null | Active scoring formula applied to this portfolio.     |
| `metrics.weighted_delta_30d`    | float \| null  | 30-day score movement (positive = improving).         |
| `metrics.coverage_pct`          | float          | Percentage of locations with scoring data.            |
| `metrics.severe_exposure_pct`   | float          | Weighted percentage of locations scoring below 30.    |
| `metrics.elevated_exposure_pct` | float          | Weighted percentage of locations scoring 30–50.       |
| `metrics.stable_exposure_pct`   | float          | Weighted percentage of locations scoring 50+.         |

**Example**

```bash theme={null}
curl https://axiomlocus.io/api/v1/locus/portfolios \
  -H "Authorization: Bearer YOUR_TOKEN"
```

## `POST` `/api/v1/locus/portfolios`

<Note>Requires session authentication.</Note>

Create a new portfolio. If you provide an `asset_class`, Locus automatically assigns the matching system scoring formula.

**Parameters**

| Name          | Type   | Required | Description                                                                                                                 |
| ------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `name`        | string | ✓        | Portfolio name.                                                                                                             |
| `description` | string |          | Human-readable description.                                                                                                 |
| `asset_class` | string |          | Asset class (e.g. `qsr`, `retail`, `office`, `industrial`, `self_storage`, `data_center`). Auto-assigns the system formula. |
| `visibility`  | string |          | `private` (default), `team`, or `org`.                                                                                      |
| `org_id`      | string |          | Workspace org id. Required when visibility is `team` or `org`.                                                              |
| `team_id`     | string |          | Workspace team id. Optional, used with `team` visibility.                                                                   |

**Example**

```bash theme={null}
curl -X POST https://axiomlocus.io/api/v1/locus/portfolios \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Midwest Industrial Sites",
    "asset_class": "industrial",
    "description": "Distribution center candidates in the Midwest corridor"
  }'
```

**Response**

```json theme={null}
{
  "portfolio": {
    "id": "p_new789",
    "name": "Midwest Industrial Sites",
    "asset_class": "industrial",
    "active_formula_id": "f_sys_industrial_v1",
    "visibility": "private",
    "member_count": 0,
    "weighted_composite": null,
    "created_at": "2026-04-20T12:00:00Z"
  }
}
```

***

## Portfolio locations

Manage the H3 cell locations within a portfolio. Each location is identified by an `h3_index` and `profile` pair.

### `GET` `/api/v1/locus/portfolios/{portfolioId}/locations`

<Note>Requires session authentication.</Note>

List all locations in a portfolio with their current scores. If the portfolio has an active formula, scores are computed using the formula weights rather than the default composite.

**Response**

```json theme={null}
{
  "locations": [
    {
      "id": "loc_001",
      "h3_index": "882a100d63fffff",
      "profile": "qsr",
      "weight": 1.5,
      "asset_class": "qsr",
      "source": "manual",
      "address_input": "123 Market St, San Francisco, CA",
      "latitude": 37.7749,
      "longitude": -122.4194,
      "composite": 78.4,
      "metro_slug": "sf",
      "neighborhood": "Financial District",
      "formula_id_used": "f_def456",
      "formula_name_used": "QSR v2",
      "formula_version_used": 2,
      "created_at": "2026-03-15T09:00:00Z"
    }
  ]
}
```

| Field       | Type          | Description                                                                            |
| ----------- | ------------- | -------------------------------------------------------------------------------------- |
| `weight`    | float         | Relative importance of this location in portfolio aggregations (default 1).            |
| `source`    | string        | How the location was added: `manual` or `import`.                                      |
| `composite` | float \| null | Score computed with the active formula, or the default composite if no formula is set. |

### `POST` `/api/v1/locus/portfolios/{portfolioId}/locations`

<Note>Requires session authentication.</Note>

Add a location to the portfolio. If a location with the same `h3_index` and `profile` already exists, it is updated (upsert).

**Parameters**

| Name            | Type   | Required | Description                                            |
| --------------- | ------ | -------- | ------------------------------------------------------ |
| `h3_index`      | string | ✓        | H3 cell id at resolution 8.                            |
| `profile`       | string |          | Scoring profile (default `general`).                   |
| `weight`        | number |          | Relative weight for portfolio aggregation (default 1). |
| `asset_class`   | string |          | Asset class label for this location.                   |
| `address_input` | string |          | Original address for reference.                        |
| `latitude`      | number |          | Latitude for display.                                  |
| `longitude`     | number |          | Longitude for display.                                 |

**Example**

```bash theme={null}
curl -X POST https://axiomlocus.io/api/v1/locus/portfolios/p_abc123/locations \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "h3_index": "882a100d63fffff",
    "profile": "qsr",
    "weight": 1.5,
    "address_input": "123 Market St, San Francisco, CA",
    "latitude": 37.7749,
    "longitude": -122.4194
  }'
```

### `PATCH` `/api/v1/locus/portfolios/{portfolioId}/locations`

<Note>Requires session authentication.</Note>

Update a location's weight or asset class.

**Parameters**

| Name          | Type   | Required | Description                          |
| ------------- | ------ | -------- | ------------------------------------ |
| `h3_index`    | string | ✓        | H3 cell id to update.                |
| `profile`     | string |          | Scoring profile (default `general`). |
| `weight`      | number |          | New weight value (must be positive). |
| `asset_class` | string |          | New asset class label.               |

### `DELETE` `/api/v1/locus/portfolios/{portfolioId}/locations`

<Note>Requires session authentication.</Note>

Remove a location from the portfolio.

**Query parameters**

| Name       | Type   | Required | Description                          |
| ---------- | ------ | -------- | ------------------------------------ |
| `h3_index` | string | ✓        | H3 cell id to remove.                |
| `profile`  | string |          | Scoring profile (default `general`). |

**Example**

```bash theme={null}
curl -X DELETE "https://axiomlocus.io/api/v1/locus/portfolios/p_abc123/locations?h3_index=882a100d63fffff&profile=qsr" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

***

## Bulk import

### `POST` `/api/v1/locus/portfolios/{portfolioId}/locations/import`

<Note>Requires session authentication.</Note>

Import multiple locations into a portfolio in a single request. Accepts a JSON array of location objects. Existing locations (matching `h3_index` + `profile`) are updated; new ones are created.

**Request body**

```json theme={null}
{
  "locations": [
    { "h3_index": "882a100d63fffff", "profile": "retail", "weight": 2.0, "address_input": "123 Market St" },
    { "h3_index": "882a100d65fffff", "profile": "retail", "weight": 1.0, "address_input": "456 Oak Ave" }
  ]
}
```

***

## Scoring refresh

### `GET` `/api/v1/locus/portfolios/{portfolioId}/refresh`

<Note>Requires session authentication.</Note>

List recent scoring refresh runs for the portfolio.

**Query parameters**

| Name    | Type   | Required | Description                               |
| ------- | ------ | -------- | ----------------------------------------- |
| `limit` | number |          | Max runs to return (default 20, max 100). |

**Response**

```json theme={null}
{
  "runs": [
    {
      "id": "run_001",
      "status": "completed",
      "requested_at": "2026-04-19T10:00:00Z",
      "started_at": "2026-04-19T10:00:05Z",
      "completed_at": "2026-04-19T10:02:34Z",
      "error_message": null
    }
  ]
}
```

### `POST` `/api/v1/locus/portfolios/{portfolioId}/refresh`

<Note>Requires session authentication.</Note>

Queue a scoring refresh. The refresh is processed asynchronously — scores for all locations in the portfolio are recomputed with the latest data and the active formula.

**Response** (HTTP 202)

```json theme={null}
{
  "run": {
    "id": "run_002",
    "status": "queued",
    "requested_at": "2026-04-20T12:00:00Z"
  }
}
```

| Status      | Description                               |
| ----------- | ----------------------------------------- |
| `queued`    | Waiting for the next processing cycle.    |
| `running`   | Scoring is in progress.                   |
| `completed` | All locations have been rescored.         |
| `failed`    | An error occurred. Check `error_message`. |
