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

# Strategy Endpoints

> REST API endpoints for managing extraction strategies

# Strategy Endpoints

Create and manage AI-generated extraction strategies via HTTP.

## Generate strategy

Generate a new extraction strategy using AI.

```http theme={null}
POST /api/strategies/generate
```

### Request body

```json theme={null}
{
  "url": "https://example.com/products",
  "description": "Extract product names and prices",
  "name": "Product Scraper",
  "output_schema": {"title": "string", "price": "number"},
  "filter_config": {
    "mode": "all",
    "conditions": [
      {"field": "price", "operator": "gt", "value": "10"}
    ]
  }
}
```

| Field               | Type   | Required | Description                                                                   |
| ------------------- | ------ | -------- | ----------------------------------------------------------------------------- |
| `url`               | string | Yes      | Target webpage URL                                                            |
| `description`       | string | Yes      | What to extract                                                               |
| `name`              | string | Yes      | Strategy name                                                                 |
| `output_schema`     | object | No       | Desired output JSON structure. See [Output Schemas](/concepts/output-schemas) |
| `filter_config`     | object | No       | Post-extraction filter. See [Filtering](/concepts/filtering)                  |
| `strategy_group_id` | UUID   | No       | Add the strategy to this group on creation                                    |

### Response

```json theme={null}
{
  "strategy_id": "550e8400-e29b-41d4-a716-446655440000",
  "strategy": {
    "container": {...},
    "fields": {...}
  },
  "preview_data": [
    {"name": "Product A", "price": "$19.99"},
    {"name": "Product B", "price": "$29.99"}
  ],
  "attempts": 1
}
```

### Response headers

This endpoint returns [strategy quota headers](#strategy-quota-headers) on
every response (success **and** the `403` thrown when the quota is exceeded),
so clients can render quota state without an extra round-trip.

### Idempotency

Pass an `Idempotency-Key: <string>` header to make retries safe — the server
caches the response for **24 hours** and replays it on subsequent requests with
the same key. Cached replays do **not** run a new LLM job and do **not** consume
additional quota.

| Scenario                                  | Result                                                                           |
| ----------------------------------------- | -------------------------------------------------------------------------------- |
| First request with a given key            | Runs normally; response cached for 24h                                           |
| Retry with same key + same body           | Returns cached response with `Idempotent-Replayed: true` header                  |
| Retry with same key + **different** body  | `422 Idempotency-Key reused with a different request body.`                      |
| Retry while the original is still running | `409 Idempotent request still in progress. Retry shortly.` plus `Retry-After: 5` |
| No header sent                            | No change in behavior — existing clients are unaffected                          |

Notes:

* Keys are **scoped per user / API key**, so two accounts cannot collide on the same string.
* Use a client-generated unique value (e.g. `uuidgen`, or a stable hash of `URL + description + run-id`). Two semantically-equivalent retries must reuse the same key.
* Pair this with your own retry-on-5xx logic to avoid duplicate strategies (and quota burn) on transient timeouts.
* The 24h TTL is fixed and not configurable.

### Example

```bash theme={null}
curl -X POST https://api.meter.sh/api/strategies/generate \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://news.ycombinator.com",
    "description": "Extract post titles and scores",
    "name": "HN Front Page"
  }'
```

Idempotent retry — first call runs, second call replays the cached response:

```bash theme={null}
KEY=$(uuidgen)

# First request — runs normally, returns the new strategy.
curl -X POST https://api.meter.sh/api/strategies/generate \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d '{
    "url": "https://news.ycombinator.com",
    "description": "Extract post titles and scores",
    "name": "HN Front Page"
  }'

# Retry with the same key + same body — replays the cached response.
# Response includes the header `Idempotent-Replayed: true`. No quota is consumed.
curl -X POST https://api.meter.sh/api/strategies/generate \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d '{
    "url": "https://news.ycombinator.com",
    "description": "Extract post titles and scores",
    "name": "HN Front Page"
  }'
```

The same `Idempotency-Key` is also honored on `POST /api/strategies/generate/stream`.

## Refine strategy

Improve an existing strategy with feedback.

```http theme={null}
POST /api/strategies/{strategy_id}/refine
```

### Request body

```json theme={null}
{
  "feedback": "Also extract product images and SKU"
}
```

### Response

Same as generate strategy response with updated preview data.

### Example

```bash theme={null}
curl -X POST https://api.meter.sh/api/strategies/550e8400-e29b-41d4-a716-446655440000/refine \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"feedback": "Also extract product images"}'
```

## List strategies

Get all strategies for the authenticated user.

```http theme={null}
GET /api/strategies?limit=20&offset=0
```

### Query parameters

| Parameter | Type    | Required | Description                  |
| --------- | ------- | -------- | ---------------------------- |
| `limit`   | integer | No       | Max results (default: 20)    |
| `offset`  | integer | No       | Results to skip (default: 0) |

### Response

```json theme={null}
[
  {
    "strategy_id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Product Scraper",
    "description": "Extract product names and prices",
    "url": "https://example.com/products",
    "preview_data": [...],
    "created_at": "2025-01-15T10:30:00Z",
    "updated_at": "2025-01-15T10:30:00Z"
  }
]
```

### Example

```bash theme={null}
curl https://api.meter.sh/api/strategies?limit=10 \
  -H "Authorization: Bearer sk_live_..."
```

## Get strategy

Get details for a specific strategy.

```http theme={null}
GET /api/strategies/{strategy_id}
```

### Response

Same format as list strategies items.

### Example

```bash theme={null}
curl https://api.meter.sh/api/strategies/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer sk_live_..."
```

## Update strategy

Update a strategy's filter configuration and/or output schema.

```http theme={null}
PATCH /api/strategies/{strategy_id}
```

### Request body

```json theme={null}
{
  "filter_config": {
    "mode": "all",
    "conditions": [
      {"field": "price", "operator": "gt", "value": "50"},
      {"field": "in_stock", "operator": "equals", "value": "true"}
    ]
  },
  "output_schema": {"title": "string", "price": "number"}
}
```

| Field           | Type   | Required | Description                                                                   |
| --------------- | ------ | -------- | ----------------------------------------------------------------------------- |
| `filter_config` | object | No       | Post-extraction filter configuration. See [Filtering](/concepts/filtering)    |
| `output_schema` | object | No       | Desired output JSON structure. See [Output Schemas](/concepts/output-schemas) |

### PATCH semantics

Each field follows three-state PATCH semantics:

| Body shape                      | Effect on stored value |
| ------------------------------- | ---------------------- |
| Field **omitted** from body     | Left unchanged         |
| Field present as `null` or `{}` | Cleared                |
| Field present with a value      | Replaced               |

This applies independently to both `filter_config` and `output_schema`. To clear only one, send `null` for that field and omit the other.

<Warning>
  **Behavior change.** Earlier versions of this endpoint cleared `filter_config`
  when it was omitted from the request body. The endpoint now treats omission as
  "leave unchanged" — consistent with standard PATCH semantics. If you were
  relying on the old behavior, send `"filter_config": null` explicitly to clear.
</Warning>

### Response

Updated strategy details.

### Examples

Replace the filter, leave the output schema unchanged:

```bash theme={null}
curl -X PATCH https://api.meter.sh/api/strategies/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "filter_config": {
      "mode": "all",
      "conditions": [
        {"field": "price", "operator": "gt", "value": "50"}
      ]
    }
  }'
```

Clear the output schema, leave the filter unchanged:

```bash theme={null}
curl -X PATCH https://api.meter.sh/api/strategies/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"output_schema": null}'
```

Update both at once:

```bash theme={null}
curl -X PATCH https://api.meter.sh/api/strategies/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "output_schema": {"title": "string", "price": "number"},
    "filter_config": null
  }'
```

## Strategy quota headers

Every response from a strategy-creation endpoint includes headers describing
the caller's current standing against the rolling 30-day strategy quota.
These ride on **successful responses and on the `403` thrown when the quota
is exceeded**, so a client can render quota state from any creation call
without an extra round-trip.

| Header                   | Value                                      | Notes                                                                                                                                       |
| ------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-Strategy-Quota-Used`  | Integer                                    | Strategies counted in the rolling 30-day window. Counts both currently-active strategies and strategies created-then-deleted in the window. |
| `X-Strategy-Quota-Limit` | Integer                                    | Plan limit. `-1` means unlimited (enterprise).                                                                                              |
| `X-Strategy-Quota-Reset` | ISO 8601 UTC timestamp                     | When the next quota slot frees (oldest counted strategy + 30d). Omitted for unlimited plans and when no strategies have been counted yet.   |
| `X-Strategy-Quota-Tier`  | `free` \| `hobby` \| `pro` \| `enterprise` | The plan tier used to compute the limit.                                                                                                    |

These headers are emitted by:

* `POST /api/strategies/generate`
* `POST /api/strategies/generate/stream`
* `POST /api/watch`

For a pure read of the same numbers without making a creation call, use
[`GET /api/account/quota`](/api-reference/rest/account).

## Strategy audit log

Paginated audit log of strategy create and delete events, merged into a
single timeline (most recent first).

```http theme={null}
GET /api/strategies/audit
```

The endpoint merges three event sources:

* `created` events from currently-active strategies
* `created` events from previously-deleted strategies (created within the window, then deleted)
* `deleted` events

All `created` events have `counted_against_quota: true` (every created
strategy consumed one slot of the rolling 30-day quota, whether or not it
was later deleted). `deleted` events have `counted_against_quota: false` —
they are tracking-only and do not consume quota.

### Query parameters

| Parameter | Type                | Required | Description                                                                 |
| --------- | ------------------- | -------- | --------------------------------------------------------------------------- |
| `from`    | datetime (ISO 8601) | No       | Inclusive lower bound. Default: `now - 30 days` (matches the quota window). |
| `to`      | datetime (ISO 8601) | No       | Inclusive upper bound. Default: `now`.                                      |
| `limit`   | integer             | No       | Page size. Default: 100. Max: 500.                                          |
| `offset`  | integer             | No       | Default: 0.                                                                 |

### Response

```json theme={null}
{
  "events": [
    {
      "event_type": "created",
      "strategy_id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Product Scraper",
      "url": "https://example.com/products",
      "scraper_type": "html",
      "timestamp": "2026-05-10T14:32:11Z",
      "counted_against_quota": true
    },
    {
      "event_type": "deleted",
      "strategy_id": "441e8400-e29b-41d4-a716-446655440000",
      "name": "Old Scraper",
      "url": "https://example.com/old",
      "scraper_type": "html",
      "timestamp": "2026-05-09T09:10:00Z",
      "counted_against_quota": false
    }
  ],
  "count": 12,
  "limit": 100,
  "offset": 0,
  "window_start": "2026-04-17T14:32:11Z",
  "window_end": "2026-05-17T14:32:11Z"
}
```

| Field                            | Type     | Description                                              |
| -------------------------------- | -------- | -------------------------------------------------------- |
| `events[].event_type`            | string   | `created` or `deleted`.                                  |
| `events[].counted_against_quota` | boolean  | `true` for every `created` event; `false` for `deleted`. |
| `count`                          | integer  | Total events in the window across all pages.             |
| `window_start` / `window_end`    | datetime | Echo of the resolved `from` / `to` bounds.               |

### Example

```bash theme={null}
# Default last-30-days window, 50 events per page
curl "https://api.meter.sh/api/strategies/audit?limit=50" \
  -H "Authorization: Bearer sk_live_..."

# Custom window
curl "https://api.meter.sh/api/strategies/audit?from=2026-01-01T00:00:00Z&to=2026-02-01T00:00:00Z" \
  -H "Authorization: Bearer sk_live_..."
```

## Compare manifest

Compare a manifest of known items against the latest scrape results for a strategy. This is a convenience endpoint that automatically uses the most recent completed job.

```http theme={null}
POST /api/strategies/{strategy_id}/compare-manifest
```

### Request body

```json theme={null}
{
  "manifest": [
    {"name": "Acme Corp"},
    {"name": "Beta Industries"},
    {"name": "Gamma Solutions"}
  ],
  "match_fields": ["name"],
  "threshold": 80
}
```

| Field          | Type   | Required | Description                                                         |
| -------------- | ------ | -------- | ------------------------------------------------------------------- |
| `manifest`     | array  | Yes      | List of known items (objects with at least the `match_fields` keys) |
| `match_fields` | array  | Yes      | Field name(s) to fuzzy-match on (e.g., `["name"]`)                  |
| `threshold`    | number | No       | Minimum match score 0-100 (default: 80)                             |

### Response

```json theme={null}
{
  "matched": [
    {
      "manifest_item": {"name": "Acme Corp"},
      "scraped_item": {"name": "Acme Corporation", "website": "acme.com"},
      "score": 90.0,
      "matched_on": "name"
    }
  ],
  "added": [
    {"name": "Delta Partners", "website": "delta.com"}
  ],
  "removed": [
    {"name": "Beta Industries"}
  ],
  "summary": {
    "matched": 1,
    "added": 1,
    "removed": 1,
    "manifest_count": 2,
    "scraped_count": 2
  },
  "threshold_used": 80.0,
  "match_fields_used": ["name"],
  "job_id": "660e8400-e29b-41d4-a716-446655440000"
}
```

### Example

```bash theme={null}
curl -X POST https://api.meter.sh/api/strategies/550e8400-e29b-41d4-a716-446655440000/compare-manifest \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "manifest": [
      {"name": "Acme Corp"},
      {"name": "Beta Industries"}
    ],
    "match_fields": ["name"],
    "threshold": 80
  }'
```

<Tip>
  Learn more about how fuzzy matching works and best practices for tuning the threshold in the [Manifest Comparison concept guide](/concepts/manifest-comparison).
</Tip>

## Delete strategy

Delete a strategy and all associated jobs and schedules.

```http theme={null}
DELETE /api/strategies/{strategy_id}
```

### Response

```json theme={null}
{
  "message": "Strategy deleted successfully"
}
```

### Example

```bash theme={null}
curl -X DELETE https://api.meter.sh/api/strategies/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer sk_live_..."
```

<Warning>
  This action is irreversible and deletes all associated resources.
</Warning>

## Error responses

| Status | Description                                                   |
| ------ | ------------------------------------------------------------- |
| `400`  | Invalid request (missing required fields, invalid URL format) |
| `401`  | Invalid or missing API key                                    |
| `404`  | Strategy not found                                            |
| `429`  | Rate limit exceeded (strategy generation is rate-limited)     |
| `500`  | Internal server error                                         |
| `503`  | Service temporarily unavailable (AI service issues)           |

See [REST API Errors](/api-reference/rest/errors) for detailed error handling.

## Next steps

<CardGroup cols={2}>
  <Card title="Job Endpoints" icon="gears" href="/api-reference/rest/jobs">
    Execute scrapes using strategies
  </Card>

  <Card title="Python SDK" icon="python" href="/api-reference/python/strategies">
    Use the Python SDK instead
  </Card>

  <Card title="Strategies Concept" icon="brain" href="/concepts/strategies">
    Learn about strategies
  </Card>
</CardGroup>

## Need help?

Email me at [mckinnon@meter.sh](mailto:mckinnon@meter.sh)
